dart-sdk/benchmarks/FfiCall/generate_benchmarks.dart
Daco Harkes 1c08a41f16 [benchmarks/ffi] Add @Native calls to FfiCall benchmark
The benchmarks contain some duplicated code, but we want to avoid
starting to measure indirection and the compiler not inlining in
these benchmarks, so it's better to keep them as plain as possible.

Bug: https://github.com/dart-lang/sdk/issues/47625
Change-Id: I7fedd31ce6df83bde3931835ae4e493a252d25b6
Cq-Include-Trybots: luci.dart.try:benchmark-linux-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/284340
Auto-Submit: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Daco Harkes <dacoharkes@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2023-03-02 15:23:16 +00:00

441 lines
14 KiB
Dart

// Copyright (c) 2023, 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';
//
// Configuration.
//
const nativeToDartType = {
'Int8': 'int',
'Int16': 'int',
'Int32': 'int',
'Int64': 'int',
'Uint8': 'int',
'Uint16': 'int',
'Uint32': 'int',
'Uint64': 'int',
'Float': 'double',
'Double': 'double',
'Pointer<Uint8>': 'Pointer<Uint8>',
'Handle': 'Object',
};
const generateFor = {
'Int8': [1],
'Int16': [1],
'Int32': [1, 2, 4, 10, 20],
'Int64': [1, 2, 4, 10, 20],
'Uint8': [1],
'Uint16': [1],
'Uint32': [1],
'Uint64': [1],
'Float': [1, 2, 4, 10, 20],
'Double': [1, 2, 4, 10, 20],
'Pointer<Uint8>': [1, 2, 4, 10, 20],
'Handle': [1, 2, 4, 10, 20],
};
const allNumbers = [1, 2, 4, 10, 20];
//
// Generator.
//
void main() {
final List<String> nativeTypes = nativeToDartType.keys.toList();
final List<String> dartTypes = nativeToDartType.values.toSet().toList();
final List<String> nativeIntTypes = nativeTypes.where(isInt).toList();
final List<String> nativeDoubleTypes = nativeTypes.where(isDouble).toList();
final List<String> nativePointerTypes = nativeTypes.where(isPointer).toList();
final List<String> nativeHandleTypes = nativeTypes.where(isHandle).toList();
final StringBuffer buffer = StringBuffer();
buffer.write(header);
generateTypedefs(buffer, 'Function', dartTypes, allNumbers);
generateTypedefs(buffer, 'NativeFunction', nativeTypes, allNumbers);
generateBenchmarkInt(buffer, nativeIntTypes);
generateBenchmarkDouble(buffer, nativeDoubleTypes);
generateBenchmarkPointer(buffer, nativePointerTypes);
generateBenchmarkHandle(buffer, nativeHandleTypes);
final path = Platform.script.resolve('dart/benchmark_generated.dart').path;
File(path).writeAsStringSync(buffer.toString());
print(Process.runSync('dart', ['format', path]).stderr);
}
const header = '''
// Copyright (c) 2023, 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.
//
// This file has been automatically generated. Please do not edit it manually.
// To regenerate the file, run the following script:
//
// > dart benchmarks/FfiCall/generate_benchmarks.dart
// Using part of, so that the library uri is identical to the main file.
// That way the FfiNativeResolver works for the main uri.
part of 'FfiCall.dart';
''';
void generateTypedefs(StringBuffer buffer, String namePrefix,
List<String> types, List<int> numbers) {
for (String type in types) {
for (int number in numbers) {
final String name = '$namePrefix$number${toIdentifier(type)}';
final String arguments = repeat(type, number, ', ');
buffer.write('typedef $name = $type Function($arguments);');
}
}
}
void generateBenchmarkInt(StringBuffer buffer, List<String> types) {
for (String type in types) {
final String typeName = toIdentifier(type);
final String dartType = toIdentifier(nativeToDartType[type]!);
for (int number in generateFor[type]!) {
final String name = '${typeName}x${'$number'.padLeft(2, '0')}';
final String expected = IntVariation(type, number).expectedValue(number);
final String functionType = 'Function$number$dartType';
final String functionNativeType = 'NativeFunction$number$typeName';
final String functionNameC = 'Function$number$typeName';
final String argument = IntVariation(type, number).argument;
final String arguments = repeat(argument, number, ', ');
final String functionNameDart = 'function$number$typeName';
final String dartArguments =
List.generate(number, (i) => '$dartType a$i').join(', ');
buffer.write('''
class $name extends FfiBenchmarkBase {
final $functionType f;
$name({bool isLeaf = false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<$functionNativeType,$functionType>('$functionNameC', isLeaf: true)
: ffiTestFunctions.lookupFunction<$functionNativeType,$functionType>('$functionNameC', isLeaf: false),
super('FfiCall.$name', isLeaf: isLeaf);
@override
void run() {
int x = 0;
for (int i = 0; i < N; i++) {
x += f($arguments);
}
expectEquals(x, $expected);
}
}
''');
for (bool isLeaf in [false, true]) {
final leaf = isLeaf ? 'Leaf' : '';
buffer.write('''
@Native<$functionNativeType>(symbol: '$functionNameC', isLeaf: $isLeaf)
external $dartType $functionNameDart$leaf($dartArguments);
class ${name}Native$leaf extends FfiBenchmarkBase {
${name}Native$leaf() : super('FfiCall.${name}Native', isLeaf: $isLeaf);
@override
void run() {
int x = 0;
for (int i = 0; i < N; i++) {
x += $functionNameDart$leaf($arguments);
}
expectEquals(x, $expected);
}
}
''');
}
}
}
}
void generateBenchmarkDouble(StringBuffer buffer, List<String> types) {
for (String type in types) {
final String typeName = toIdentifier(type);
final String dartType = toIdentifier(nativeToDartType[type]!);
for (int number in generateFor[type]!) {
final String name = '${typeName}x${'$number'.padLeft(2, '0')}';
final String expected = number == 1
? 'N + N * 42.0' // Do work with single arg.
: 'N * $number * ($number + 1) / 2 '; // The rest sums arguments.
final String functionType = 'Function$number$dartType';
final String functionNativeType = 'NativeFunction$number$typeName';
final String functionNameC = 'Function$number$typeName';
final List<double> argVals = List.generate(number, (i) => 1.0 * (i + 1));
final String arguments = argVals.join(', ');
final String functionNameDart = 'function$number$typeName';
final String dartArguments =
List.generate(number, (i) => '$dartType a$i').join(', ');
buffer.write('''
class $name extends FfiBenchmarkBase {
final $functionType f;
$name({bool isLeaf = false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<$functionNativeType,$functionType>('$functionNameC', isLeaf: true)
: ffiTestFunctions.lookupFunction<$functionNativeType,$functionType>('$functionNameC', isLeaf: false),
super('FfiCall.$name', isLeaf: isLeaf);
@override
void run() {
double x = 0;
for (int i = 0; i < N; i++) {
x += f($arguments);
}
final double expected = $expected;
expectApprox(x, expected);
}
}
''');
for (bool isLeaf in [false, true]) {
final leaf = isLeaf ? 'Leaf' : '';
buffer.write('''
@Native<$functionNativeType>(symbol: '$functionNameC', isLeaf: $isLeaf)
external $dartType $functionNameDart$leaf($dartArguments);
class ${name}Native$leaf extends FfiBenchmarkBase {
${name}Native$leaf() : super('FfiCall.${name}Native', isLeaf: $isLeaf);
@override
void run() {
double x = 0;
for (int i = 0; i < N; i++) {
x += $functionNameDart$leaf($arguments);
}
final double expected = $expected;
expectApprox(x, expected);
}
}
''');
}
}
}
}
void generateBenchmarkPointer(StringBuffer buffer, List<String> types) {
for (String type in types) {
if (type != 'Pointer<Uint8>') throw Exception('Not implemented for $type.');
final String typeName = toIdentifier(type);
final String dartType = nativeToDartType[type]!;
final String dartTypeName = toIdentifier(dartType);
for (int number in generateFor[type]!) {
final String name = '${typeName}x${'$number'.padLeft(2, '0')}';
final List<String> pointerNames =
List.generate(number, (i) => 'p${i + 1}');
final String pointers =
pointerNames.map((n) => '$type $n = nullptr;').join('\n');
final String setup = List.generate(
number - 1, (i) => 'p${i + 2} = p1.elementAt(${i + 1});').join();
final String functionType = 'Function$number$dartTypeName';
final String functionNativeType = 'NativeFunction$number$typeName';
final String functionNameC = 'Function$number$typeName';
final String arguments = pointerNames.skip(1).join(', ');
final String functionNameDart = 'function$number$typeName';
final String dartArguments =
List.generate(number, (i) => '$dartType a$i').join(', ');
buffer.write('''
class $name extends FfiBenchmarkBase {
final $functionType f;
$name({bool isLeaf = false})
: f = isLeaf
? ffiTestFunctions.lookupFunction<$functionNativeType,$functionType>('$functionNameC', isLeaf: true)
: ffiTestFunctions.lookupFunction<$functionNativeType,$functionType>('$functionNameC', isLeaf: false),
super('FfiCall.$name', isLeaf: isLeaf);
$pointers
@override
void setup() {
p1 = calloc(N + 1);
$setup
}
@override
void teardown() {
calloc.free(p1);
}
@override
void run() {
$type x = p1;
for (int i = 0; i < N; i++) {
x = f(x, $arguments);
}
expectEquals(x.address, p1.address + N * sizeOf<Uint8>());
}
}
''');
for (bool isLeaf in [false, true]) {
final leaf = isLeaf ? 'Leaf' : '';
buffer.write('''
@Native<$functionNativeType>(symbol: '$functionNameC', isLeaf: $isLeaf)
external $dartType $functionNameDart$leaf($dartArguments);
class ${name}Native$leaf extends FfiBenchmarkBase {
${name}Native$leaf() : super('FfiCall.${name}Native', isLeaf: $isLeaf);
$pointers
@override
void setup() {
p1 = calloc(N + 1);
$setup
}
@override
void teardown() {
calloc.free(p1);
}
@override
void run() {
$type x = p1;
for (int i = 0; i < N; i++) {
x = $functionNameDart$leaf(x, $arguments);
}
expectEquals(x.address, p1.address + N * sizeOf<Uint8>());
}
}
''');
}
}
}
}
void generateBenchmarkHandle(StringBuffer buffer, List<String> types) {
for (String type in types) {
if (type != 'Handle') throw Exception('Not implemented for $type.');
final String typeName = toIdentifier(type);
final String dartType = toIdentifier(nativeToDartType[type]!);
for (int number in generateFor[type]!) {
final String name = '${typeName}x${'$number'.padLeft(2, '0')}';
final String setup =
List.generate(number + 1, (i) => 'final m$i = MyClass($i);')
.skip(2)
.join('\n');
final String functionType = 'Function$number$dartType';
final String functionNativeType = 'NativeFunction$number$typeName';
final String functionNameC = 'Function$number$typeName';
final String arguments =
List.generate(number - 1, (i) => 'm${i + 2}').join(', ');
final String functionNameDart = 'function$number$typeName';
final String dartArguments =
List.generate(number, (i) => '$dartType a$i').join(', ');
buffer.write('''
class $name extends FfiBenchmarkBase {
final $functionType f;
$name()
: f = ffiTestFunctions.lookupFunction<$functionNativeType,$functionType>('$functionNameC', isLeaf: false),
super('FfiCall.$name', isLeaf: false);
@override
void run() {
final m1 = MyClass(123);
$setup
Object x = m1;
for (int i = 0; i < N; i++) {
x = f(x, $arguments);
}
expectIdentical(x, m1);
}
}
@Native<$functionNativeType>(symbol: '$functionNameC', isLeaf: false)
external $dartType $functionNameDart($dartArguments);
class ${name}Native extends FfiBenchmarkBase {
${name}Native() : super('FfiCall.${name}Native', isLeaf: false);
@override
void run() {
final m1 = MyClass(123);
$setup
Object x = m1;
for (int i = 0; i < N; i++) {
x = $functionNameDart(x, $arguments);
}
expectIdentical(x, m1);
}
}
''');
}
}
}
//
// Benchmark variations.
//
class IntVariation {
/// The argument passed to the C function, the same value is passed if the C
/// function has multiple parameters.
final String argument;
/// The expected value of summation of all return values.
final String Function(int number) expectedValue;
/// These benchmarks sum all arguments over all iterations.
IntVariation.LargeIntManyArguments()
: argument = 'i',
expectedValue = ((int number) => 'N * (N - 1) * $number / 2');
/// Benchmarks with only one argument return 42 added to the argument.
IntVariation.LargeIntOneArgument()
: argument = 'i',
expectedValue = ((int number) => 'N * (N - 1) / 2 + N * 42');
/// The benchmarks with small ints (`int8_t`, `uint8_t`, etc.) we pass an
/// arbitrary fixed argument between 0 and 127 to prevent truncation.
///
/// The C function returns 42 added to the argument.
IntVariation.SmallInt()
: argument = '17',
expectedValue = ((int number) => 'N * 17 + N * 42');
factory IntVariation(String type, int number) {
if (isSmallInt(type)) {
return IntVariation.SmallInt();
}
if (number == 1) {
return IntVariation.LargeIntOneArgument();
}
return IntVariation.LargeIntManyArguments();
}
}
//
// Helper functions.
//
String toIdentifier(String type) =>
type.replaceAll('<', '').replaceAll('>', '');
String repeat(String input, int n, String separator) {
if (n == 0) {
return '';
}
return (input + separator) * (n - 1) + input;
}
bool isInt(String type) => type.startsWith('Int') || type.startsWith('Uint');
/// True for `int8_t`, `uint8_t`, `int16_t`, and `uint16_t`.
bool isSmallInt(String type) =>
isInt(type) && (type.contains('8') || type.contains('16'));
bool isDouble(String type) =>
type.startsWith('Float') || type.startsWith('Double');
bool isPointer(String type) => type.startsWith('Pointer');
bool isHandle(String type) => type == 'Handle';