blob: 6e41d1f959f2fb5dcdb4dce4bc75e70fb3f900ca [file] [log] [blame]
// 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.');
}