// Copyright (c) 2019, 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 of efficiency of grapheme cluster operations. import 'package:characters/characters.dart'; import '../test/src/text_samples.dart'; double bench(int Function() action, int ms) { var elapsed = 0; var count = 0; var stopwatch = Stopwatch()..start(); do { count += action(); elapsed = stopwatch.elapsedMilliseconds; } while (elapsed < ms); return count / elapsed; } int iterateIndicesOnly() { var graphemeClusters = 0; var char = Characters(hangul).iterator; while (char.moveNext()) { graphemeClusters++; } char = Characters(genesis).iterator; while (char.moveNext()) { graphemeClusters++; } return graphemeClusters; } int iterateStrings() { var codeUnits = 0; var char = Characters(hangul).iterator; while (char.moveNext()) { codeUnits += char.current.length; } char = Characters(genesis).iterator; while (char.moveNext()) { codeUnits += char.current.length; } return codeUnits; } int reverseStrings() { var revHangul = reverse(hangul); var rev2Hangul = reverse(revHangul); if (hangul != rev2Hangul || hangul == revHangul) { throw AssertionError('Bad reverse'); } var revGenesis = reverse(genesis); var rev2Genesis = reverse(revGenesis); if (genesis != rev2Genesis || genesis == revGenesis) { throw AssertionError('Bad reverse'); } return (hangul.length + genesis.length) * 2; } int replaceStrings() { var count = 0; { const language = '한글'; assert(language.length == 6); var chars = Characters(hangul); var replaced = chars.replaceAll( Characters(language), Characters('Hangul!'), ); count += replaced.string.length - hangul.length; } { var chars = Characters(genesis); var replaced = chars.replaceAll(Characters('And'), Characters('Also')); count += replaced.string.length - genesis.length; } return count; } String reverse(String input) { var chars = Characters(input); var buffer = StringBuffer(); for (var it = chars.iteratorAtEnd; it.moveBack();) { buffer.write(it.current); } return buffer.toString(); } void main(List args) { var count = 1; if (args.isNotEmpty) count = int.tryParse(args[0]) ?? 1; // Warmup. bench(iterateIndicesOnly, 250); bench(iterateStrings, 250); bench(reverseStrings, 250); bench(replaceStrings, 250); var bestIterateIndices = 0.0; var bestIterateStrings = 0.0; var bestReverseStrings = 0.0; var bestReplaceStrings = 0.0; String toDigits(double d) { const n = 5; var s = d.round().toString(); if (s.length >= n) return s; return d.toStringAsFixed(n - s.length); } for (var i = 0; i < count; i++) { var performance = bench(iterateIndicesOnly, 2000); print('Index Iteration: ${toDigits(performance)} gc/ms'); if (performance > bestIterateIndices) bestIterateIndices = performance; performance = bench(iterateStrings, 2000); print('String Iteration: ${toDigits(performance)} cu/ms'); if (performance > bestIterateStrings) bestIterateStrings = performance; performance = bench(reverseStrings, 2000); print('String Reversing: ${toDigits(performance)} cu/ms'); if (performance > bestReverseStrings) bestReverseStrings = performance; performance = bench(replaceStrings, 2000); print('String Replacing: ${toDigits(performance)} changes/ms'); if (performance > bestReplaceStrings) bestReplaceStrings = performance; } if (count > 1) { print('Best: '); print('Index Iteration: ${toDigits(bestIterateIndices)} gc/ms'); print('String Iteration: ${toDigits(bestIterateStrings)} cu/ms'); print('String Reversing: ${toDigits(bestReverseStrings)} cu/ms'); print('String Replacing: ${toDigits(bestReplaceStrings)} changes/ms'); } }