Skip to content

Commit

Permalink
[benchmarks/ffi] Add micro and macro benchmarks for dart:ffi
Browse files Browse the repository at this point in the history
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
dcharkes authored and sortie committed Jul 19, 2019
1 parent c1bb024 commit fbf13f5
Show file tree
Hide file tree
Showing 19 changed files with 2,432 additions and 5 deletions.
21 changes: 21 additions & 0 deletions DEPS
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,27 @@ deps = {
],
"dep_type": "cipd",
},

# TODO(37531): Remove these cipd packages and build with sdk instead when
# benchmark runner gets support for that.
Var("dart_root") + "/benchmarks/FfiBoringssl/dart/native/out/": {
"packages": [
{
"package": "dart/benchmarks/ffiboringssl",
"version": "commit:a86c69888b9a416f5249aacb4690a765be064969",
},
],
"dep_type": "cipd",
},
Var("dart_root") + "/benchmarks/FfiCall/dart/native/out/": {
"packages": [
{
"package": "dart/benchmarks/fficall",
"version": "version:1",
},
],
"dep_type": "cipd",
},
}

deps_os = {
Expand Down
144 changes: 144 additions & 0 deletions benchmarks/FfiBoringssl/dart/FfiBoringssl.dart
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());
}
99 changes: 99 additions & 0 deletions benchmarks/FfiBoringssl/dart/digest.dart
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');
58 changes: 58 additions & 0 deletions benchmarks/FfiBoringssl/dart/dlopen_helper.dart
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);
}
7 changes: 7 additions & 0 deletions benchmarks/FfiBoringssl/dart/native/.gitignore
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/
Loading

0 comments on commit fbf13f5

Please sign in to comment.