| // 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']); |
| } |
| } |
| } |