| // Copyright (c) 2025, 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. | |
| import 'dart:io' show exit, stderr; | |
| import 'dart:typed_data'; | |
| import 'package:convert/convert.dart'; | |
| import 'package:crypto/crypto.dart'; | |
| void main(List<String> args) { | |
| Hash? function; | |
| int? customSize; | |
| void setFunction(Hash newFunction, String message) { | |
| if (function != null) { | |
| stderr.writeln('Hash function already set.'); | |
| exit(1); | |
| } | |
| function = newFunction; | |
| print('Using hash function $message'); | |
| } | |
| for (var arg in args) { | |
| if (arg == 'md5') { | |
| setFunction(md5, 'md5'); | |
| } else if (arg == 'sha1') { | |
| setFunction(sha1, 'sha1'); | |
| } else if (arg == 'sha256') { | |
| setFunction(sha256, 'sha256'); | |
| } else if (arg == 'sha224') { | |
| setFunction(sha224, 'sha224'); | |
| } else if (arg == 'sha384') { | |
| setFunction(sha384, 'sha384'); | |
| } else if (arg == 'sha512') { | |
| setFunction(sha512, 'sha512'); | |
| } else if (arg == 'sha512224') { | |
| setFunction(sha512224, 'sha512/224'); | |
| } else if (arg == 'sha512256') { | |
| setFunction(sha512256, 'sha512/256'); | |
| } else if (arg.startsWith('--custom=')) { | |
| customSize = int.parse(arg.substring('--custom='.length)); | |
| } else { | |
| stderr.writeln('Unknown argument: $arg'); | |
| exit(1); | |
| } | |
| } | |
| if (function == null) { | |
| setFunction(md5, 'md5'); | |
| } | |
| if (customSize != null) { | |
| doIterationsChunk(function!, mb: customSize, iterations: 1, doPrint: true); | |
| return; | |
| } | |
| // Warmup. | |
| doIterationsChunk(function!, mb: 1, iterations: 100, doPrint: false); | |
| // Benchmarks. | |
| print('One chunk input'); | |
| doIterationsChunk(function!, mb: 1, iterations: 1000, doPrint: true); | |
| doIterationsChunk(function!, mb: 10, iterations: 100, doPrint: true); | |
| doIterationsChunk(function!, mb: 100, iterations: 10, doPrint: true); | |
| doIterationsChunk(function!, mb: 1000, iterations: 1, doPrint: true); | |
| print(''); | |
| print('Add in 1024 byte chunks:'); | |
| doIterationsSmallChunks(function!, | |
| chunkSize: 1024, mb: 1, iterations: 1000, doPrint: true); | |
| print(''); | |
| print('Add in 100 byte chunks:'); | |
| doIterationsSmallChunks(function!, | |
| chunkSize: 100, mb: 1, iterations: 1000, doPrint: true); | |
| print(''); | |
| print('Add in 4 byte chunks:'); | |
| doIterationsSmallChunks(function!, | |
| chunkSize: 4, mb: 1, iterations: 1000, doPrint: true); | |
| } | |
| void doIterationsChunk(Hash function, | |
| {required int mb, required int iterations, required bool doPrint}) { | |
| var data = Uint8List(1024 * 1024 * mb); | |
| var runtimesInMs = <double>[]; | |
| for (var i = 0; i < iterations; i++) { | |
| runtimesInMs.add(hashChunk(data, function)); | |
| } | |
| if (doPrint) { | |
| printStats(runtimesInMs, data.length, iterations); | |
| } | |
| } | |
| void doIterationsSmallChunks(Hash function, | |
| {required int chunkSize, | |
| required int mb, | |
| required int iterations, | |
| required bool doPrint}) { | |
| var data = Uint8List(chunkSize); | |
| var runtimesInMs = <double>[]; | |
| var addIterations = mb * 1024 * 1024 ~/ chunkSize; | |
| for (var i = 0; i < iterations; i++) { | |
| runtimesInMs.add(hashSmallChunks(data, addIterations, function)); | |
| } | |
| if (doPrint) { | |
| printStats(runtimesInMs, data.length * addIterations, iterations); | |
| } | |
| } | |
| double hashChunk(Uint8List data, Hash function) { | |
| var stopwatch = Stopwatch()..start(); | |
| var hash = function.convert(data); | |
| stopwatch.stop(); | |
| if (hash.bytes.isEmpty) throw StateError('This should never happen'); | |
| return stopwatch.elapsedMicroseconds / 1000; | |
| } | |
| double hashSmallChunks(Uint8List data, int addTimes, Hash function) { | |
| var stopwatch = Stopwatch()..start(); | |
| var output = AccumulatorSink<Digest>(); | |
| var input = function.startChunkedConversion(output); | |
| for (var i = 0; i < addTimes; i++) { | |
| input.add(data); | |
| } | |
| input.close(); | |
| var hash = output.events.single; | |
| stopwatch.stop(); | |
| if (hash.bytes.isEmpty) throw StateError('This should never happen'); | |
| return stopwatch.elapsedMicroseconds / 1000; | |
| } | |
| void printStats(List<double> runtimesInMs, int dataLength, int iterations) { | |
| var mb = dataLength / 1024 / 1024; | |
| runtimesInMs.sort(); | |
| var sum = runtimesInMs.reduce((value, element) => value + element); | |
| var averageRuntimeInMs = sum / runtimesInMs.length; | |
| var averageKbPerMs = dataLength / 1024 / averageRuntimeInMs; | |
| var medianRuntimeInMs = runtimesInMs[runtimesInMs.length ~/ 2]; | |
| var medianKbPerMs = dataLength / 1024 / medianRuntimeInMs; | |
| print( | |
| 'Processed ${mb.toStringAsFixed(2)} mb of data with an average/median of ' | |
| '${averageKbPerMs.toStringAsFixed(2)} / ' | |
| '${medianKbPerMs.toStringAsFixed(2)} ' | |
| 'kb per ms.'); | |
| } |