-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[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 <dacoharkes@google.com> Reviewed-by: Jonas Termansen <sortie@google.com> Auto-Submit: Daco Harkes <dacoharkes@google.com>
- Loading branch information
Showing
19 changed files
with
2,432 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
// Copyright (c) 2019, 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. | ||
|
||
// Macro-benchmark for ffi with boringssl. | ||
|
||
import 'dart:convert'; | ||
import 'dart:ffi'; | ||
import 'dart:typed_data'; | ||
|
||
import 'package:benchmark_harness/benchmark_harness.dart'; | ||
|
||
import 'digest.dart'; | ||
import 'types.dart'; | ||
|
||
// | ||
// BoringSSL functions | ||
// | ||
|
||
Uint8List inventData(int length) { | ||
final result = Uint8List(length); | ||
for (int i = 0; i < length; i++) { | ||
result[i] = i % 256; | ||
} | ||
return result; | ||
} | ||
|
||
Uint8List toUint8List(Bytes bytes, int length) { | ||
final result = Uint8List(length); | ||
final uint8bytes = bytes.asUint8Pointer(); | ||
for (int i = 0; i < length; i++) { | ||
result[i] = uint8bytes.elementAt(i).load<int>(); | ||
} | ||
return result; | ||
} | ||
|
||
void copyFromUint8ListToTarget(Uint8List source, Data target) { | ||
final int length = source.length; | ||
final uint8target = target.asUint8Pointer(); | ||
for (int i = 0; i < length; i++) { | ||
uint8target.offsetBy(i).store(source[i]); | ||
} | ||
} | ||
|
||
String hash(Pointer<Data> data, int length, Pointer<EVP_MD> hashAlgorithm) { | ||
final context = EVP_MD_CTX_new(); | ||
EVP_DigestInit(context, hashAlgorithm); | ||
EVP_DigestUpdate(context, data, length); | ||
final int resultSize = EVP_MD_CTX_size(context); | ||
final Pointer<Bytes> result = | ||
Pointer<Uint8>.allocate(count: resultSize).cast(); | ||
EVP_DigestFinal(context, result, nullptr.cast()); | ||
EVP_MD_CTX_free(context); | ||
final String hash = base64Encode(toUint8List(result.load(), resultSize)); | ||
result.free(); | ||
return hash; | ||
} | ||
|
||
// | ||
// Benchmark fixtures. | ||
// | ||
|
||
// Number of repeats: 1 && Length in bytes: 10000000 | ||
// * CPU: Intel(R) Xeon(R) Gold 6154 | ||
// * Architecture: x64 | ||
// * 23000 - 52000000 us (without optimizations) | ||
// * 23000 - 30000 us (with optimizations) | ||
// * Architecture: SimDBC64 | ||
// * 23000 - 5500000 us (without optimizations) | ||
// * 23000 - 30000 us (with optimizations) | ||
const int L = 1000; // Length of data in bytes. | ||
|
||
final hashAlgorithm = EVP_sha512(); | ||
|
||
// Hash of generated data of `L` bytes with `hashAlgorithm`. | ||
const String expectedHash = | ||
"bNLtqb+cBZcSkCmwBUuB5DP2uLe0madetwXv10usGUFJg1sdGhTEi+aW5NWIRW1RKiLq56obV74rVurn014Iyw=="; | ||
|
||
/// This benchmark runs a digest algorithm on data residing in C memory. | ||
/// | ||
/// This benchmark is intended as macro benchmark with a realistic workload. | ||
class DigestCMemory extends BenchmarkBase { | ||
DigestCMemory() : super("FfiBoringssl.DigestCMemory"); | ||
|
||
Pointer<Data> data; // Data in C memory that we want to digest. | ||
|
||
void setup() { | ||
data = Pointer<Uint8>.allocate(count: L).cast(); | ||
copyFromUint8ListToTarget(inventData(L), data.load()); | ||
hash(data, L, hashAlgorithm); | ||
} | ||
|
||
void teardown() { | ||
data.free(); | ||
} | ||
|
||
void run() { | ||
final String result = hash(data, L, hashAlgorithm); | ||
if (result != expectedHash) { | ||
throw Exception("$name: Unexpected result: $result"); | ||
} | ||
} | ||
} | ||
|
||
/// This benchmark runs a digest algorithm on data residing in Dart memory. | ||
/// | ||
/// This benchmark is intended as macro benchmark with a realistic workload. | ||
class DigestDartMemory extends BenchmarkBase { | ||
DigestDartMemory() : super("FfiBoringssl.DigestDartMemory"); | ||
|
||
Uint8List data; // Data in C memory that we want to digest. | ||
|
||
void setup() { | ||
data = inventData(L); | ||
final Pointer<Data> dataInC = Pointer<Uint8>.allocate(count: L).cast(); | ||
copyFromUint8ListToTarget(data, dataInC.load()); | ||
hash(dataInC, L, hashAlgorithm); | ||
dataInC.free(); | ||
} | ||
|
||
void teardown() {} | ||
|
||
void run() { | ||
final Pointer<Data> dataInC = Pointer<Uint8>.allocate(count: L).cast(); | ||
copyFromUint8ListToTarget(data, dataInC.load()); | ||
final String result = hash(dataInC, L, hashAlgorithm); | ||
dataInC.free(); | ||
if (result != expectedHash) { | ||
throw Exception("$name: Unexpected result: $result"); | ||
} | ||
} | ||
} | ||
|
||
// | ||
// Main driver. | ||
// | ||
|
||
main() { | ||
final benchmarks = [ | ||
() => DigestCMemory(), | ||
() => DigestDartMemory(), | ||
]; | ||
benchmarks.forEach((benchmark) => benchmark().report()); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
// Copyright (c) 2019, 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'; | ||
|
||
import 'dlopen_helper.dart'; | ||
import 'types.dart'; | ||
|
||
// See: | ||
// https://commondatastorage.googleapis.com/chromium-boringssl-docs/digest.h.html | ||
|
||
DynamicLibrary openSsl() { | ||
// Force load crypto. | ||
dlopenPlatformSpecific("crypto", | ||
path: Platform.script.resolve("native/out/").path); | ||
DynamicLibrary ssl = dlopenPlatformSpecific("ssl", | ||
path: Platform.script.resolve("native/out/").path); | ||
return ssl; | ||
} | ||
|
||
final DynamicLibrary ssl = openSsl(); | ||
|
||
/// The following functions return EVP_MD objects that implement the named | ||
/// hash function. | ||
/// | ||
/// ```c | ||
/// const EVP_MD *EVP_sha512(void); | ||
/// ``` | ||
final Pointer<EVP_MD> Function() EVP_sha512 = | ||
ssl.lookupFunction<Pointer<EVP_MD> Function(), Pointer<EVP_MD> Function()>( | ||
'EVP_sha512'); | ||
|
||
/// EVP_MD_CTX_new allocates and initialises a fresh EVP_MD_CTX and returns it, | ||
/// or NULL on allocation failure. The caller must use EVP_MD_CTX_free to | ||
/// release the resulting object. | ||
/// | ||
/// ```c | ||
/// EVP_MD_CTX *EVP_MD_CTX_new(void); | ||
/// ``` | ||
final Pointer<EVP_MD_CTX> Function() EVP_MD_CTX_new = ssl.lookupFunction< | ||
Pointer<EVP_MD_CTX> Function(), | ||
Pointer<EVP_MD_CTX> Function()>('EVP_MD_CTX_new'); | ||
|
||
/// EVP_MD_CTX_free calls EVP_MD_CTX_cleanup and then frees ctx itself. | ||
/// | ||
/// ```c | ||
/// void EVP_MD_CTX_free(EVP_MD_CTX *ctx); | ||
/// ``` | ||
final void Function(Pointer<EVP_MD_CTX>) EVP_MD_CTX_free = ssl.lookupFunction< | ||
Void Function(Pointer<EVP_MD_CTX>), | ||
void Function(Pointer<EVP_MD_CTX>)>('EVP_MD_CTX_free'); | ||
|
||
/// EVP_DigestInit acts like EVP_DigestInit_ex except that ctx is initialised | ||
/// before use. | ||
/// | ||
/// ```c | ||
/// int EVP_DigestInit(EVP_MD_CTX *ctx, const EVP_MD *type); | ||
/// ``` | ||
final int Function(Pointer<EVP_MD_CTX>, Pointer<EVP_MD>) EVP_DigestInit = | ||
ssl.lookupFunction<Int32 Function(Pointer<EVP_MD_CTX>, Pointer<EVP_MD>), | ||
int Function(Pointer<EVP_MD_CTX>, Pointer<EVP_MD>)>('EVP_DigestInit'); | ||
|
||
/// EVP_DigestUpdate hashes len bytes from data into the hashing operation | ||
/// in ctx. It returns one. | ||
/// | ||
/// ```c | ||
/// int EVP_DigestUpdate(EVP_MD_CTX *ctx, const void *data, | ||
/// size_t len); | ||
/// ``` | ||
final int Function(Pointer<EVP_MD_CTX>, Pointer<Data>, int) EVP_DigestUpdate = | ||
ssl.lookupFunction< | ||
Int32 Function(Pointer<EVP_MD_CTX>, Pointer<Data>, IntPtr), | ||
int Function( | ||
Pointer<EVP_MD_CTX>, Pointer<Data>, int)>('EVP_DigestUpdate'); | ||
|
||
/// EVP_DigestFinal acts like EVP_DigestFinal_ex except that EVP_MD_CTX_cleanup | ||
/// is called on ctx before returning. | ||
/// | ||
/// ```c | ||
/// int EVP_DigestFinal(EVP_MD_CTX *ctx, uint8_t *md_out, | ||
/// unsigned int *out_size); | ||
/// ``` | ||
final int Function(Pointer<EVP_MD_CTX>, Pointer<Bytes>, Pointer<Uint32>) | ||
EVP_DigestFinal = ssl.lookupFunction< | ||
Int32 Function(Pointer<EVP_MD_CTX>, Pointer<Bytes>, Pointer<Uint32>), | ||
int Function(Pointer<EVP_MD_CTX>, Pointer<Bytes>, | ||
Pointer<Uint32>)>('EVP_DigestFinal'); | ||
|
||
/// EVP_MD_CTX_size returns the digest size of ctx, in bytes. It will crash if | ||
/// a digest hasn't been set on ctx. | ||
/// | ||
/// ```c | ||
/// size_t EVP_MD_CTX_size(const EVP_MD_CTX *ctx); | ||
/// ``` | ||
final int Function(Pointer<EVP_MD_CTX>) EVP_MD_CTX_size = ssl.lookupFunction< | ||
IntPtr Function(Pointer<EVP_MD_CTX>), | ||
int Function(Pointer<EVP_MD_CTX>)>('EVP_MD_CTX_size'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
// Copyright (c) 2019, 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 kArm = "arm"; | ||
const kArm64 = "arm64"; | ||
const kIa32 = "ia32"; | ||
const kX64 = "x64"; | ||
|
||
// https://stackoverflow.com/questions/45125516/possible-values-for-uname-m | ||
final _unames = { | ||
"arm": kArm, | ||
"aarch64_be": kArm64, | ||
"aarch64": kArm64, | ||
"armv8b": kArm64, | ||
"armv8l": kArm64, | ||
"i386": kIa32, | ||
"i686": kIa32, | ||
"x86_64": kX64, | ||
}; | ||
|
||
String _checkRunningMode(String architecture) { | ||
// Check if we're running in 32bit mode. | ||
final int pointerSize = sizeOf<IntPtr>(); | ||
if (pointerSize == 4 && architecture == kX64) return kIa32; | ||
if (pointerSize == 4 && architecture == kArm64) return kArm; | ||
|
||
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(kX64)}/$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: path); | ||
return DynamicLibrary.open(fullPath); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# Copyright (c) 2019, 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. | ||
|
||
build/ | ||
out/ | ||
src/ |
Oops, something went wrong.