| // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| // | |
| // Benchmark for UTF-8 decoding | |
| import 'dart:convert'; | |
| import 'dart:typed_data'; | |
| import 'package:benchmark_harness/benchmark_harness.dart'; | |
| import 'datext_latin1_10k.dart'; | |
| import 'entext_ascii_10k.dart'; | |
| import 'netext_3_10k.dart'; | |
| import 'rutext_2_10k.dart'; | |
| import 'sktext_10k.dart'; | |
| import 'zhtext_10k.dart'; | |
| class Utf8Decode extends BenchmarkBase { | |
| final String language; | |
| final String text; | |
| final int size; | |
| final bool allowMalformed; | |
| late List<Uint8List> chunks; | |
| int totalInputSize = 0; | |
| int totalOutputSize = 0; | |
| static String _makeName(String language, int size, bool allowMalformed) { | |
| String name = 'Utf8Decode.$language.'; | |
| name += size >= 1000000 | |
| ? '${size ~/ 1000000}M' | |
| : size >= 1000 | |
| ? '${size ~/ 1000}k' | |
| : '$size'; | |
| if (allowMalformed) name += '.malformed'; | |
| return name; | |
| } | |
| Utf8Decode(this.language, this.text, this.size, this.allowMalformed) | |
| : super(_makeName(language, size, allowMalformed)); | |
| @override | |
| void setup() { | |
| final Uint8List data = utf8.encode(text); | |
| if (data.length != 10000) { | |
| throw 'Expected input data of exactly 10000 bytes.'; | |
| } | |
| if (size < data.length) { | |
| // Split into chunks. | |
| chunks = <Uint8List>[]; | |
| int startPos = 0; | |
| for (int pos = size; pos < data.length; pos += size) { | |
| int endPos = pos; | |
| while ((data[endPos] & 0xc0) == 0x80) { | |
| endPos--; | |
| } | |
| chunks.add(Uint8List.fromList(data.sublist(startPos, endPos))); | |
| startPos = endPos; | |
| } | |
| chunks.add(Uint8List.fromList(data.sublist(startPos, data.length))); | |
| totalInputSize = data.length; | |
| totalOutputSize = text.length; | |
| } else if (size > data.length) { | |
| // Repeat data to the desired length. | |
| final Uint8List expanded = Uint8List(size); | |
| for (int i = 0; i < size; i++) { | |
| expanded[i] = data[i % data.length]; | |
| } | |
| chunks = <Uint8List>[expanded]; | |
| totalInputSize = size; | |
| totalOutputSize = text.length * size ~/ data.length; | |
| } else { | |
| // Use data as is. | |
| chunks = <Uint8List>[data]; | |
| totalInputSize = data.length; | |
| totalOutputSize = text.length; | |
| } | |
| } | |
| @override | |
| void run() { | |
| int lengthSum = 0; | |
| for (int i = 0; i < chunks.length; i++) { | |
| final String s = utf8.decode(chunks[i], allowMalformed: allowMalformed); | |
| lengthSum += s.length; | |
| } | |
| if (lengthSum != totalOutputSize) { | |
| throw 'Output length doesn\'t match expected.'; | |
| } | |
| } | |
| @override | |
| void exercise() { | |
| // Only a single run per measurement. | |
| run(); | |
| } | |
| @override | |
| double measure() { | |
| // Report time per input byte. | |
| return super.measure() / totalInputSize; | |
| } | |
| @override | |
| void report() { | |
| // Report time in nanoseconds. | |
| final double score = measure() * 1000.0; | |
| print('$name(RunTime): $score ns.'); | |
| } | |
| } | |
| void main(List<String> args) { | |
| const texts = {'en': en, 'da': da, 'sk': sk, 'ru': ru, 'ne': ne, 'zh': zh}; | |
| final bool testMalformed = args.isNotEmpty && args.first == 'malformed'; | |
| final benchmarks = [ | |
| // Only benchmark with allowMalformed: false unless specified otherwise. | |
| for (bool allowMalformed in [false, if (testMalformed) true]) | |
| for (int size in [10, 10000, 10000000]) | |
| for (String language in texts.keys) | |
| () => Utf8Decode(language, texts[language]!, size, allowMalformed), | |
| ]; | |
| for (var bm in benchmarks) { | |
| bm().report(); | |
| } | |
| } |