[benchmark][ffi] Add simple callback benchmark
A basic benchmark for FFI callbacks. I'm currently not planning on extending it to cover more calling conventions or running it on our benchmark infra. We can leave that for when we want to do it. Bug: https://github.com/dart-lang/sdk/issues/38171 Change-Id: I0c5fec81333e9a8da092b4f51ceb9f80a2eb2cf7 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/407020 Reviewed-by: Liam Appelbe <liama@google.com> Reviewed-by: Slava Egorov <vegorov@google.com>
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
// 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']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// 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:ffi';
|
||||
import 'dart:io';
|
||||
|
||||
const arm = 'arm';
|
||||
const arm64 = 'arm64';
|
||||
const ia32 = 'ia32';
|
||||
const x64 = 'x64';
|
||||
|
||||
// https://stackoverflow.com/questions/45125516/possible-values-for-uname-m
|
||||
final _unames = {
|
||||
'arm': arm,
|
||||
'arm64': arm64,
|
||||
'aarch64_be': arm64,
|
||||
'aarch64': arm64,
|
||||
'armv8b': arm64,
|
||||
'armv8l': arm64,
|
||||
'i386': ia32,
|
||||
'i686': ia32,
|
||||
'x86_64': x64,
|
||||
};
|
||||
|
||||
String _checkRunningMode(String architecture) {
|
||||
// Check if we're running in 32bit mode.
|
||||
final int pointerSize = sizeOf<IntPtr>();
|
||||
if (pointerSize == 4 && architecture == x64) return ia32;
|
||||
if (pointerSize == 4 && architecture == arm64) return arm;
|
||||
|
||||
return architecture;
|
||||
}
|
||||
|
||||
String _architecture() {
|
||||
final String uname = Process.runSync('uname', ['-m']).stdout.trim();
|
||||
final String? architecture = _unames[uname];
|
||||
if (architecture == null) {
|
||||
throw Exception('Unrecognized architecture: "$uname"');
|
||||
}
|
||||
|
||||
// Check if we're running in 32bit mode.
|
||||
return _checkRunningMode(architecture);
|
||||
}
|
||||
|
||||
String _platformPath(String name, String path) {
|
||||
if (Platform.isMacOS || Platform.isIOS) {
|
||||
return '${path}mac/${_architecture()}/lib$name.dylib';
|
||||
}
|
||||
|
||||
if (Platform.isWindows) {
|
||||
return '${path}win/${_checkRunningMode(x64)}/$name.dll';
|
||||
}
|
||||
|
||||
// Unknown platforms default to Unix implementation.
|
||||
return '${path}linux/${_architecture()}/lib$name.so';
|
||||
}
|
||||
|
||||
DynamicLibrary dlopenPlatformSpecific(String name, {String path = ''}) {
|
||||
final String fullPath = _platformPath(name, path);
|
||||
return DynamicLibrary.open(fullPath);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
# 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.
|
||||
|
||||
out/
|
||||
@@ -0,0 +1,67 @@
|
||||
# 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.
|
||||
|
||||
# TODO(37531): Remove this makefile and build with sdk instead when
|
||||
# benchmark runner gets support for that.
|
||||
|
||||
CC=gcc
|
||||
CCARM=arm-linux-gnueabihf-gcc
|
||||
CCARM64=aarch64-linux-gnu-gcc
|
||||
CFLAGS=-Wall -g -O -fPIC
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: out/linux/x64/libnative_functions.so out/linux/ia32/libnative_functions.so out/linux/arm64/libnative_functions.so out/linux/arm/libnative_functions.so
|
||||
|
||||
cipd:
|
||||
cipd create -name dart/benchmarks/fficall -in out -install-mode copy
|
||||
|
||||
clean:
|
||||
rm -rf *.o *.so out
|
||||
|
||||
out/linux/x64:
|
||||
mkdir -p out/linux/x64
|
||||
|
||||
out/linux/x64/native_functions.o: native_functions.c | out/linux/x64
|
||||
$(CC) $(CFLAGS) -c -o $@ native_functions.c
|
||||
|
||||
out/linux/x64/libnative_functions.so: out/linux/x64/native_functions.o
|
||||
$(CC) $(CFLAGS) -s -shared -o $@ out/linux/x64/native_functions.o
|
||||
|
||||
out/linux/ia32:
|
||||
mkdir -p out/linux/ia32
|
||||
|
||||
out/linux/ia32/native_functions.o: native_functions.c | out/linux/ia32
|
||||
$(CC) $(CFLAGS) -m32 -c -o $@ native_functions.c
|
||||
|
||||
out/linux/ia32/libnative_functions.so: out/linux/ia32/native_functions.o
|
||||
$(CC) $(CFLAGS) -m32 -s -shared -o $@ out/linux/ia32/native_functions.o
|
||||
|
||||
out/linux/arm64:
|
||||
mkdir -p out/linux/arm64
|
||||
|
||||
out/linux/arm64/native_functions.o: native_functions.c | out/linux/arm64
|
||||
$(CCARM64) $(CFLAGS) -c -o $@ native_functions.c
|
||||
|
||||
out/linux/arm64/libnative_functions.so: out/linux/arm64/native_functions.o
|
||||
$(CCARM64) $(CFLAGS) -s -shared -o $@ out/linux/arm64/native_functions.o
|
||||
|
||||
out/linux/arm:
|
||||
mkdir -p out/linux/arm
|
||||
|
||||
out/linux/arm/native_functions.o: native_functions.c | out/linux/arm
|
||||
$(CCARM) $(CFLAGS) -c -o $@ native_functions.c
|
||||
|
||||
out/linux/arm/libnative_functions.so: out/linux/arm/native_functions.o
|
||||
$(CCARM) $(CFLAGS) -s -shared -o $@ out/linux/arm/native_functions.o
|
||||
|
||||
# On M1 Machine.
|
||||
out/mac/arm64:
|
||||
mkdir -p out/mac/arm64
|
||||
|
||||
out/mac/arm64/native_functions.o: native_functions.c | out/mac/arm64
|
||||
$(CC) $(CFLAGS) -c -o $@ native_functions.c
|
||||
|
||||
out/mac/arm64/libnative_functions.dylib: out/mac/arm64/native_functions.o
|
||||
$(CC) $(CFLAGS) -s -shared -o $@ out/mac/arm64/native_functions.o
|
||||
@@ -0,0 +1,13 @@
|
||||
To upload a new package to CIPD:
|
||||
```
|
||||
$ cd benchmarks/FfiCallback/native
|
||||
$ make
|
||||
$ find . -name "*.o" -type f -delete
|
||||
$ cipd create -pkg-def=cipd.yaml
|
||||
```
|
||||
|
||||
Then update the top level DEPS file with the new hash.
|
||||
The new hash can be found with:
|
||||
```
|
||||
$ cipd instances dart/benchmarks/fficallback
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
package: dart/benchmarks/fficallback
|
||||
description: Dynamic libaries for running the benchmarks/FfiCallback.
|
||||
install_mode: copy
|
||||
data:
|
||||
- dir: out/
|
||||
@@ -0,0 +1,11 @@
|
||||
// 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.
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void CallFunction1Uint8(void (*callback)(uint8_t), uint32_t batch_size) {
|
||||
for (uint32_t i = 0; i < batch_size; i++) {
|
||||
callback(1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user