| // Copyright (c) 2022, 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. | |
| // Micro-benchmarks for ffi memory copies. | |
| // | |
| // These micro benchmarks track the speed of doing mem-copies when copying | |
| // structs. | |
| import 'dart:ffi'; | |
| import 'dart:math'; | |
| import 'package:args/args.dart'; | |
| import 'package:ffi/ffi.dart'; | |
| import 'benchmark_generated.dart'; | |
| abstract class StructCopyBenchmark { | |
| final String name; | |
| StructCopyBenchmark(this.name); | |
| int get copySizeInBytes; | |
| Pointer get from; | |
| Pointer get to; | |
| static const targetBatchSizeInBytes = 32 * 1024; | |
| late final int batchSize = max(targetBatchSizeInBytes ~/ copySizeInBytes, 1); | |
| // Returns the number of bytes copied per second. | |
| double measureFor(Duration duration) { | |
| // Prevent `sw.elapsedMicroseconds` from dominating with maps with a | |
| // small number of elements. | |
| final int batchSizeInBytes = batchSize * copySizeInBytes; | |
| int numberOfBytesCopied = 0; | |
| int totalMicroseconds = 0; | |
| final sw = Stopwatch()..start(); | |
| final durationInMicroseconds = duration.inMicroseconds; | |
| do { | |
| run(batchSize); | |
| numberOfBytesCopied += batchSizeInBytes; | |
| totalMicroseconds = sw.elapsedMicroseconds; | |
| } while (totalMicroseconds < durationInMicroseconds); | |
| const microsecondsInSecond = 1000 * 1000; | |
| return numberOfBytesCopied * microsecondsInSecond / totalMicroseconds; | |
| } | |
| // Runs warmup phase, runs benchmark and reports result. | |
| void report({bool verbose = false}) { | |
| setup(batchSize); | |
| // Warmup for 100 ms. | |
| measureFor(const Duration(milliseconds: 100)); | |
| // Run benchmark for 2 seconds. | |
| final double bytesPerSecond = measureFor(const Duration(seconds: 2)); | |
| // Report result. | |
| print('$name(BytesPerSecond): $bytesPerSecond'); | |
| if (verbose) { | |
| const nanoSecondsPerSecond = 1000 * 1000 * 1000; | |
| final nanosecondsPerByte = nanoSecondsPerSecond / bytesPerSecond; | |
| print('$name(NanosecondsPerChar): $nanosecondsPerByte'); | |
| const bytesPerMegaByte = 1024 * 1024; | |
| final mbPerSecond = bytesPerSecond / bytesPerMegaByte; | |
| print('$name: $mbPerSecond MB per second copied.'); | |
| } | |
| teardown(); | |
| } | |
| void teardown() { | |
| calloc.free(from); | |
| calloc.free(to); | |
| } | |
| void setup(int batchSize); | |
| void run(int batchSize); | |
| } | |
| final argParser = ArgParser() | |
| ..addFlag('verbose', abbr: 'v', help: 'Verbose output', defaultsTo: false); | |
| void main(List<String> args) { | |
| final results = argParser.parse(args); | |
| final benchmarks = [ | |
| Copy1Bytes.new, | |
| Copy32Bytes.new, | |
| Copy1024Bytes.new, | |
| Copy32768Bytes.new, | |
| ]; | |
| final filter = results.rest.firstOrNull; | |
| for (var constructor in benchmarks) { | |
| final benchmark = constructor(); | |
| if (filter == null || benchmark.name.contains(filter)) { | |
| benchmark.report(verbose: results['verbose']); | |
| } | |
| } | |
| } |