Skip to content

Commit fbf13f5

Browse files
dcharkessortie
authored andcommitted
[benchmarks/ffi] Add micro and macro benchmarks for dart:ffi
Adds micro benchmarks to measure low level (1) C memory reads and writes from Dart and (2) calls from Dart into C. This CL also adds a macro benchmark to measure overall performance using BoringSSL to digest data. The shared libraries are precompiled for Linux and live in cipd packages. The benchmarks run on all hardware architectures (with the exception of Linux'es hardfp on Arm32: #36309). Issue: #36247 Change-Id: I8dfb30cc66a26a2942bb09194c5eb0da0b6ca1b5 Cq-Include-Trybots: luci.dart.try:benchmark-linux-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/108724 Commit-Queue: Daco Harkes <[email protected]> Reviewed-by: Jonas Termansen <[email protected]> Auto-Submit: Daco Harkes <[email protected]>
1 parent c1bb024 commit fbf13f5

19 files changed

+2432
-5
lines changed

DEPS

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,27 @@ deps = {
408408
],
409409
"dep_type": "cipd",
410410
},
411+
412+
# TODO(37531): Remove these cipd packages and build with sdk instead when
413+
# benchmark runner gets support for that.
414+
Var("dart_root") + "/benchmarks/FfiBoringssl/dart/native/out/": {
415+
"packages": [
416+
{
417+
"package": "dart/benchmarks/ffiboringssl",
418+
"version": "commit:a86c69888b9a416f5249aacb4690a765be064969",
419+
},
420+
],
421+
"dep_type": "cipd",
422+
},
423+
Var("dart_root") + "/benchmarks/FfiCall/dart/native/out/": {
424+
"packages": [
425+
{
426+
"package": "dart/benchmarks/fficall",
427+
"version": "version:1",
428+
},
429+
],
430+
"dep_type": "cipd",
431+
},
411432
}
412433

413434
deps_os = {
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// Macro-benchmark for ffi with boringssl.
6+
7+
import 'dart:convert';
8+
import 'dart:ffi';
9+
import 'dart:typed_data';
10+
11+
import 'package:benchmark_harness/benchmark_harness.dart';
12+
13+
import 'digest.dart';
14+
import 'types.dart';
15+
16+
//
17+
// BoringSSL functions
18+
//
19+
20+
Uint8List inventData(int length) {
21+
final result = Uint8List(length);
22+
for (int i = 0; i < length; i++) {
23+
result[i] = i % 256;
24+
}
25+
return result;
26+
}
27+
28+
Uint8List toUint8List(Bytes bytes, int length) {
29+
final result = Uint8List(length);
30+
final uint8bytes = bytes.asUint8Pointer();
31+
for (int i = 0; i < length; i++) {
32+
result[i] = uint8bytes.elementAt(i).load<int>();
33+
}
34+
return result;
35+
}
36+
37+
void copyFromUint8ListToTarget(Uint8List source, Data target) {
38+
final int length = source.length;
39+
final uint8target = target.asUint8Pointer();
40+
for (int i = 0; i < length; i++) {
41+
uint8target.offsetBy(i).store(source[i]);
42+
}
43+
}
44+
45+
String hash(Pointer<Data> data, int length, Pointer<EVP_MD> hashAlgorithm) {
46+
final context = EVP_MD_CTX_new();
47+
EVP_DigestInit(context, hashAlgorithm);
48+
EVP_DigestUpdate(context, data, length);
49+
final int resultSize = EVP_MD_CTX_size(context);
50+
final Pointer<Bytes> result =
51+
Pointer<Uint8>.allocate(count: resultSize).cast();
52+
EVP_DigestFinal(context, result, nullptr.cast());
53+
EVP_MD_CTX_free(context);
54+
final String hash = base64Encode(toUint8List(result.load(), resultSize));
55+
result.free();
56+
return hash;
57+
}
58+
59+
//
60+
// Benchmark fixtures.
61+
//
62+
63+
// Number of repeats: 1 && Length in bytes: 10000000
64+
// * CPU: Intel(R) Xeon(R) Gold 6154
65+
// * Architecture: x64
66+
// * 23000 - 52000000 us (without optimizations)
67+
// * 23000 - 30000 us (with optimizations)
68+
// * Architecture: SimDBC64
69+
// * 23000 - 5500000 us (without optimizations)
70+
// * 23000 - 30000 us (with optimizations)
71+
const int L = 1000; // Length of data in bytes.
72+
73+
final hashAlgorithm = EVP_sha512();
74+
75+
// Hash of generated data of `L` bytes with `hashAlgorithm`.
76+
const String expectedHash =
77+
"bNLtqb+cBZcSkCmwBUuB5DP2uLe0madetwXv10usGUFJg1sdGhTEi+aW5NWIRW1RKiLq56obV74rVurn014Iyw==";
78+
79+
/// This benchmark runs a digest algorithm on data residing in C memory.
80+
///
81+
/// This benchmark is intended as macro benchmark with a realistic workload.
82+
class DigestCMemory extends BenchmarkBase {
83+
DigestCMemory() : super("FfiBoringssl.DigestCMemory");
84+
85+
Pointer<Data> data; // Data in C memory that we want to digest.
86+
87+
void setup() {
88+
data = Pointer<Uint8>.allocate(count: L).cast();
89+
copyFromUint8ListToTarget(inventData(L), data.load());
90+
hash(data, L, hashAlgorithm);
91+
}
92+
93+
void teardown() {
94+
data.free();
95+
}
96+
97+
void run() {
98+
final String result = hash(data, L, hashAlgorithm);
99+
if (result != expectedHash) {
100+
throw Exception("$name: Unexpected result: $result");
101+
}
102+
}
103+
}
104+
105+
/// This benchmark runs a digest algorithm on data residing in Dart memory.
106+
///
107+
/// This benchmark is intended as macro benchmark with a realistic workload.
108+
class DigestDartMemory extends BenchmarkBase {
109+
DigestDartMemory() : super("FfiBoringssl.DigestDartMemory");
110+
111+
Uint8List data; // Data in C memory that we want to digest.
112+
113+
void setup() {
114+
data = inventData(L);
115+
final Pointer<Data> dataInC = Pointer<Uint8>.allocate(count: L).cast();
116+
copyFromUint8ListToTarget(data, dataInC.load());
117+
hash(dataInC, L, hashAlgorithm);
118+
dataInC.free();
119+
}
120+
121+
void teardown() {}
122+
123+
void run() {
124+
final Pointer<Data> dataInC = Pointer<Uint8>.allocate(count: L).cast();
125+
copyFromUint8ListToTarget(data, dataInC.load());
126+
final String result = hash(dataInC, L, hashAlgorithm);
127+
dataInC.free();
128+
if (result != expectedHash) {
129+
throw Exception("$name: Unexpected result: $result");
130+
}
131+
}
132+
}
133+
134+
//
135+
// Main driver.
136+
//
137+
138+
main() {
139+
final benchmarks = [
140+
() => DigestCMemory(),
141+
() => DigestDartMemory(),
142+
];
143+
benchmarks.forEach((benchmark) => benchmark().report());
144+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:ffi';
6+
import 'dart:io';
7+
8+
import 'dlopen_helper.dart';
9+
import 'types.dart';
10+
11+
// See:
12+
// https://commondatastorage.googleapis.com/chromium-boringssl-docs/digest.h.html
13+
14+
DynamicLibrary openSsl() {
15+
// Force load crypto.
16+
dlopenPlatformSpecific("crypto",
17+
path: Platform.script.resolve("native/out/").path);
18+
DynamicLibrary ssl = dlopenPlatformSpecific("ssl",
19+
path: Platform.script.resolve("native/out/").path);
20+
return ssl;
21+
}
22+
23+
final DynamicLibrary ssl = openSsl();
24+
25+
/// The following functions return EVP_MD objects that implement the named
26+
/// hash function.
27+
///
28+
/// ```c
29+
/// const EVP_MD *EVP_sha512(void);
30+
/// ```
31+
final Pointer<EVP_MD> Function() EVP_sha512 =
32+
ssl.lookupFunction<Pointer<EVP_MD> Function(), Pointer<EVP_MD> Function()>(
33+
'EVP_sha512');
34+
35+
/// EVP_MD_CTX_new allocates and initialises a fresh EVP_MD_CTX and returns it,
36+
/// or NULL on allocation failure. The caller must use EVP_MD_CTX_free to
37+
/// release the resulting object.
38+
///
39+
/// ```c
40+
/// EVP_MD_CTX *EVP_MD_CTX_new(void);
41+
/// ```
42+
final Pointer<EVP_MD_CTX> Function() EVP_MD_CTX_new = ssl.lookupFunction<
43+
Pointer<EVP_MD_CTX> Function(),
44+
Pointer<EVP_MD_CTX> Function()>('EVP_MD_CTX_new');
45+
46+
/// EVP_MD_CTX_free calls EVP_MD_CTX_cleanup and then frees ctx itself.
47+
///
48+
/// ```c
49+
/// void EVP_MD_CTX_free(EVP_MD_CTX *ctx);
50+
/// ```
51+
final void Function(Pointer<EVP_MD_CTX>) EVP_MD_CTX_free = ssl.lookupFunction<
52+
Void Function(Pointer<EVP_MD_CTX>),
53+
void Function(Pointer<EVP_MD_CTX>)>('EVP_MD_CTX_free');
54+
55+
/// EVP_DigestInit acts like EVP_DigestInit_ex except that ctx is initialised
56+
/// before use.
57+
///
58+
/// ```c
59+
/// int EVP_DigestInit(EVP_MD_CTX *ctx, const EVP_MD *type);
60+
/// ```
61+
final int Function(Pointer<EVP_MD_CTX>, Pointer<EVP_MD>) EVP_DigestInit =
62+
ssl.lookupFunction<Int32 Function(Pointer<EVP_MD_CTX>, Pointer<EVP_MD>),
63+
int Function(Pointer<EVP_MD_CTX>, Pointer<EVP_MD>)>('EVP_DigestInit');
64+
65+
/// EVP_DigestUpdate hashes len bytes from data into the hashing operation
66+
/// in ctx. It returns one.
67+
///
68+
/// ```c
69+
/// int EVP_DigestUpdate(EVP_MD_CTX *ctx, const void *data,
70+
/// size_t len);
71+
/// ```
72+
final int Function(Pointer<EVP_MD_CTX>, Pointer<Data>, int) EVP_DigestUpdate =
73+
ssl.lookupFunction<
74+
Int32 Function(Pointer<EVP_MD_CTX>, Pointer<Data>, IntPtr),
75+
int Function(
76+
Pointer<EVP_MD_CTX>, Pointer<Data>, int)>('EVP_DigestUpdate');
77+
78+
/// EVP_DigestFinal acts like EVP_DigestFinal_ex except that EVP_MD_CTX_cleanup
79+
/// is called on ctx before returning.
80+
///
81+
/// ```c
82+
/// int EVP_DigestFinal(EVP_MD_CTX *ctx, uint8_t *md_out,
83+
/// unsigned int *out_size);
84+
/// ```
85+
final int Function(Pointer<EVP_MD_CTX>, Pointer<Bytes>, Pointer<Uint32>)
86+
EVP_DigestFinal = ssl.lookupFunction<
87+
Int32 Function(Pointer<EVP_MD_CTX>, Pointer<Bytes>, Pointer<Uint32>),
88+
int Function(Pointer<EVP_MD_CTX>, Pointer<Bytes>,
89+
Pointer<Uint32>)>('EVP_DigestFinal');
90+
91+
/// EVP_MD_CTX_size returns the digest size of ctx, in bytes. It will crash if
92+
/// a digest hasn't been set on ctx.
93+
///
94+
/// ```c
95+
/// size_t EVP_MD_CTX_size(const EVP_MD_CTX *ctx);
96+
/// ```
97+
final int Function(Pointer<EVP_MD_CTX>) EVP_MD_CTX_size = ssl.lookupFunction<
98+
IntPtr Function(Pointer<EVP_MD_CTX>),
99+
int Function(Pointer<EVP_MD_CTX>)>('EVP_MD_CTX_size');
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:ffi';
6+
import 'dart:io';
7+
8+
const kArm = "arm";
9+
const kArm64 = "arm64";
10+
const kIa32 = "ia32";
11+
const kX64 = "x64";
12+
13+
// https://stackoverflow.com/questions/45125516/possible-values-for-uname-m
14+
final _unames = {
15+
"arm": kArm,
16+
"aarch64_be": kArm64,
17+
"aarch64": kArm64,
18+
"armv8b": kArm64,
19+
"armv8l": kArm64,
20+
"i386": kIa32,
21+
"i686": kIa32,
22+
"x86_64": kX64,
23+
};
24+
25+
String _checkRunningMode(String architecture) {
26+
// Check if we're running in 32bit mode.
27+
final int pointerSize = sizeOf<IntPtr>();
28+
if (pointerSize == 4 && architecture == kX64) return kIa32;
29+
if (pointerSize == 4 && architecture == kArm64) return kArm;
30+
31+
return architecture;
32+
}
33+
34+
String _architecture() {
35+
final String uname = Process.runSync("uname", ["-m"]).stdout.trim();
36+
final String architecture = _unames[uname];
37+
if (architecture == null)
38+
throw Exception("Unrecognized architecture: '$uname'");
39+
40+
// Check if we're running in 32bit mode.
41+
return _checkRunningMode(architecture);
42+
}
43+
44+
String _platformPath(String name, {String path = ""}) {
45+
if (Platform.isMacOS || Platform.isIOS)
46+
return "${path}mac/${_architecture()}/lib$name.dylib";
47+
48+
if (Platform.isWindows)
49+
return "${path}win/${_checkRunningMode(kX64)}/$name.dll";
50+
51+
// Unknown platforms default to Unix implementation.
52+
return "${path}linux/${_architecture()}/lib$name.so";
53+
}
54+
55+
DynamicLibrary dlopenPlatformSpecific(String name, {String path}) {
56+
final String fullPath = _platformPath(name, path: path);
57+
return DynamicLibrary.open(fullPath);
58+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
2+
# for details. All rights reserved. Use of this source code is governed by a
3+
# BSD-style license that can be found in the LICENSE file.
4+
5+
build/
6+
out/
7+
src/

0 commit comments

Comments
 (0)