mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 08:20:31 +00:00
1c08a41f16
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>
441 lines
14 KiB
Dart
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';
|