blob: 308fb4ea3819943fb0023ad6180f40bcab8bb431 [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.
// These micro benchmarks track the speed of reading and writing C memory from
// Dart with a specific marshalling and unmarshalling of data.
import 'dart:ffi';
import 'dart:io';
import 'package:args/args.dart';
import 'dlopen_helper.dart';
// The native library that holds all the native functions being called.
DynamicLibrary ffiTestFunctions = dlopenPlatformSpecific(
'native_functions',
path: Platform.script.resolve('../native/out/').path,
);
abstract class FfiCallbackBenchmark {
final String name;
FfiCallbackBenchmark(this.name);
// Returns ns per callback.
double measureFor(Duration duration) {
const int batchSize = 100000;
int numberOfCalls = 0;
int totalMicroseconds = 0;
final sw = Stopwatch()..start();
final durationInMicroseconds = duration.inMicroseconds;
do {
run(batchSize);
numberOfCalls += batchSize;
totalMicroseconds = sw.elapsedMicroseconds;
} while (totalMicroseconds < durationInMicroseconds);
final totalNanoSeconds = totalMicroseconds * 1000;
return totalNanoSeconds / numberOfCalls;
}
// Runs warmup phase, runs benchmark and reports result.
void report({bool verbose = false}) {
// Warmup for 100 ms.
measureFor(const Duration(milliseconds: 100));
// Run benchmark for 2 seconds.
final double nsPerCall = measureFor(const Duration(seconds: 2));
// Report result.
print('$name(RunTimeRaw): $nsPerCall ns.');
if (verbose) {
final callsPerSecond = (1000 * 1000 * 1000 / nsPerCall).toInt();
print('$name: $callsPerSecond calls per second.');
}
shutdown();
}
void run(int batchSize);
void shutdown();
void expectEquals(actual, expected) {
if (actual != expected) {
throw Exception('$name: Unexpected result: $actual, expected $expected');
}
}
}
final class Uint8x1 extends FfiCallbackBenchmark {
Uint8x1() : super('FfiCallbackBenchmark.Uint8x1');
final function = ffiTestFunctions
.lookupFunction<
Void Function(Pointer<NativeFunction<Void Function(Uint8)>>, Uint32),
void Function(Pointer<NativeFunction<Void Function(Uint8)>>, int)
>('CallFunction1Uint8');
static int x = 0;
static void callback(int value) {
x += value;
}
static final nativeCallable =
NativeCallable<Void Function(Uint8)>.isolateLocal(callback);
static final pointer = nativeCallable.nativeFunction;
@override
void run(int batchSize) {
x = 0;
function(pointer, batchSize);
expectEquals(x, batchSize);
}
@override
void shutdown() {
nativeCallable.close();
}
}
final argParser = ArgParser()
..addFlag('verbose', abbr: 'v', help: 'Verbose output', defaultsTo: false);
void main(List<String> args) {
final results = argParser.parse(args);
final benchmarks = [Uint8x1.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']);
}
}
}