Skip to content

Commit a7163db

Browse files
dcharkesCommit Queue
authored and
Commit Queue
committed
[benchmarks/ffi] Add FfiStructCopy benchmark
`_memCopy` inside `dart:ffi` is currently doing a per-byte copy in Dart. This is rather slow, we should optimize this with the `MemoryCopyInstr` in the VM. This CL adds benchmarks to report the number of bytes copied per second. Adds only benchmarks with copies of 32^(0..3), as non-power-of-two benchmarks did not seem to behave differently. Since legacy mode is no longer benchmarked, the dart2 version of this benchmark is omitted. Benchmarks set up according to https://dart-review.googlesource.com/c/sdk/+/200188 Bug: #43967 Change-Id: I3d9be8de725820fd3365a7dc85d15174bddc1ae6 Cq-Include-Trybots: luci.dart.try:benchmark-linux-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/277522 Reviewed-by: Jonas Termansen <[email protected]> Commit-Queue: Daco Harkes <[email protected]>
1 parent 0633041 commit a7163db

File tree

3 files changed

+325
-0
lines changed

3 files changed

+325
-0
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright (c) 2022, 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+
// Micro-benchmarks for ffi memory copies.
6+
//
7+
// These micro benchmarks track the speed of doing mem-copies when copying
8+
// structs.
9+
10+
import 'dart:ffi';
11+
import 'dart:math';
12+
13+
import 'package:args/args.dart';
14+
import 'package:ffi/ffi.dart';
15+
16+
import 'benchmark_generated.dart';
17+
18+
abstract class StructCopyBenchmark {
19+
final String name;
20+
StructCopyBenchmark(this.name);
21+
22+
int get copySizeInBytes;
23+
Pointer get from;
24+
Pointer get to;
25+
26+
static const targetBatchSizeInBytes = 32 * 1024;
27+
28+
late final int batchSize = max(targetBatchSizeInBytes ~/ copySizeInBytes, 1);
29+
30+
// Returns the number of bytes copied per second.
31+
double measureFor(Duration duration) {
32+
// Prevent `sw.elapsedMicroseconds` from dominating with maps with a
33+
// small number of elements.
34+
final int batchSizeInBytes = batchSize * copySizeInBytes;
35+
36+
int numberOfBytesCopied = 0;
37+
int totalMicroseconds = 0;
38+
39+
final sw = Stopwatch()..start();
40+
final durationInMicroseconds = duration.inMicroseconds;
41+
42+
do {
43+
run(batchSize);
44+
numberOfBytesCopied += batchSizeInBytes;
45+
totalMicroseconds = sw.elapsedMicroseconds;
46+
} while (totalMicroseconds < durationInMicroseconds);
47+
48+
const microsecondsInSecond = 1000 * 1000;
49+
return numberOfBytesCopied * microsecondsInSecond / totalMicroseconds;
50+
}
51+
52+
// Runs warmup phase, runs benchmark and reports result.
53+
void report({bool verbose = false}) {
54+
setup(batchSize);
55+
56+
// Warmup for 100 ms.
57+
measureFor(const Duration(milliseconds: 100));
58+
59+
// Run benchmark for 2 seconds.
60+
final double bytesPerSecond = measureFor(const Duration(seconds: 2));
61+
62+
// Report result.
63+
print('$name(BytesPerSecond): $bytesPerSecond');
64+
if (verbose) {
65+
const nanoSecondsPerSecond = 1000 * 1000 * 1000;
66+
final nanosecondsPerByte = nanoSecondsPerSecond / bytesPerSecond;
67+
print('$name(NanosecondsPerChar): $nanosecondsPerByte');
68+
const bytesPerMegaByte = 1024 * 1024;
69+
final mbPerSecond = bytesPerSecond / bytesPerMegaByte;
70+
print('$name: $mbPerSecond MB per second copied.');
71+
}
72+
73+
teardown();
74+
}
75+
76+
void teardown() {
77+
calloc.free(from);
78+
calloc.free(to);
79+
}
80+
81+
void setup(int batchSize);
82+
83+
void run(int batchSize);
84+
}
85+
86+
void main(List<String> args) {
87+
final argParser = ArgParser();
88+
argParser.addFlag('verbose', abbr: 'v');
89+
final argsParsed = argParser.parse(args);
90+
final verbose = argsParsed['verbose'] as bool;
91+
final rest = argsParsed.rest;
92+
String? filter;
93+
if (rest.isNotEmpty) {
94+
filter = rest.first;
95+
}
96+
97+
final benchmarks = [
98+
Copy1Bytes.new,
99+
Copy32Bytes.new,
100+
Copy1024Bytes.new,
101+
Copy32768Bytes.new,
102+
];
103+
for (final benchmark in benchmarks) {
104+
final b = benchmark();
105+
if (filter == null || b.name.contains(filter)) {
106+
b.report(verbose: verbose);
107+
}
108+
}
109+
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// Copyright (c) 2022, 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+
7+
import 'package:ffi/ffi.dart';
8+
9+
import 'FfiStructCopy.dart';
10+
11+
class Struct1Bytes extends Struct {
12+
@Array(1)
13+
external Array<Uint8> a0;
14+
}
15+
16+
class Struct1BytesWrapper extends Struct {
17+
external Struct1Bytes nested;
18+
}
19+
20+
class Copy1Bytes extends StructCopyBenchmark {
21+
@override
22+
Pointer<Struct1BytesWrapper> from = nullptr;
23+
@override
24+
Pointer<Struct1BytesWrapper> to = nullptr;
25+
26+
Copy1Bytes() : super('FfiStructCopy.Copy1Bytes');
27+
28+
@override
29+
int get copySizeInBytes => sizeOf<Struct1BytesWrapper>();
30+
31+
@override
32+
void setup(int batchSize) {
33+
from = calloc(batchSize);
34+
to = calloc(batchSize);
35+
}
36+
37+
@override
38+
void run(int batchSize) {
39+
for (int i = 0; i < batchSize; i++) {
40+
to[i].nested = from[i].nested;
41+
}
42+
}
43+
}
44+
45+
class Struct32Bytes extends Struct {
46+
@Array(32)
47+
external Array<Uint8> a0;
48+
}
49+
50+
class Struct32BytesWrapper extends Struct {
51+
external Struct32Bytes nested;
52+
}
53+
54+
class Copy32Bytes extends StructCopyBenchmark {
55+
@override
56+
Pointer<Struct32BytesWrapper> from = nullptr;
57+
@override
58+
Pointer<Struct32BytesWrapper> to = nullptr;
59+
60+
Copy32Bytes() : super('FfiStructCopy.Copy32Bytes');
61+
62+
@override
63+
int get copySizeInBytes => sizeOf<Struct32BytesWrapper>();
64+
65+
@override
66+
void setup(int batchSize) {
67+
from = calloc(batchSize);
68+
to = calloc(batchSize);
69+
}
70+
71+
@override
72+
void run(int batchSize) {
73+
for (int i = 0; i < batchSize; i++) {
74+
to[i].nested = from[i].nested;
75+
}
76+
}
77+
}
78+
79+
class Struct1024Bytes extends Struct {
80+
@Array(1024)
81+
external Array<Uint8> a0;
82+
}
83+
84+
class Struct1024BytesWrapper extends Struct {
85+
external Struct1024Bytes nested;
86+
}
87+
88+
class Copy1024Bytes extends StructCopyBenchmark {
89+
@override
90+
Pointer<Struct1024BytesWrapper> from = nullptr;
91+
@override
92+
Pointer<Struct1024BytesWrapper> to = nullptr;
93+
94+
Copy1024Bytes() : super('FfiStructCopy.Copy1024Bytes');
95+
96+
@override
97+
int get copySizeInBytes => sizeOf<Struct1024BytesWrapper>();
98+
99+
@override
100+
void setup(int batchSize) {
101+
from = calloc(batchSize);
102+
to = calloc(batchSize);
103+
}
104+
105+
@override
106+
void run(int batchSize) {
107+
for (int i = 0; i < batchSize; i++) {
108+
to[i].nested = from[i].nested;
109+
}
110+
}
111+
}
112+
113+
class Struct32768Bytes extends Struct {
114+
@Array(32768)
115+
external Array<Uint8> a0;
116+
}
117+
118+
class Struct32768BytesWrapper extends Struct {
119+
external Struct32768Bytes nested;
120+
}
121+
122+
class Copy32768Bytes extends StructCopyBenchmark {
123+
@override
124+
Pointer<Struct32768BytesWrapper> from = nullptr;
125+
@override
126+
Pointer<Struct32768BytesWrapper> to = nullptr;
127+
128+
Copy32768Bytes() : super('FfiStructCopy.Copy32768Bytes');
129+
130+
@override
131+
int get copySizeInBytes => sizeOf<Struct32768BytesWrapper>();
132+
133+
@override
134+
void setup(int batchSize) {
135+
from = calloc(batchSize);
136+
to = calloc(batchSize);
137+
}
138+
139+
@override
140+
void run(int batchSize) {
141+
for (int i = 0; i < batchSize; i++) {
142+
to[i].nested = from[i].nested;
143+
}
144+
}
145+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright (c) 2022, 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:io';
6+
7+
Future<void> main() async {
8+
final contents = [
9+
header,
10+
for (final size in sizes) generateSize(size),
11+
].join('\n');
12+
13+
final uri = Platform.script.resolve('dart/benchmark_generated.dart');
14+
15+
await File.fromUri(uri).writeAsString(contents);
16+
}
17+
18+
const sizes = [
19+
1,
20+
32,
21+
1024,
22+
1024 * 32,
23+
];
24+
25+
const header =
26+
'''// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
27+
// for details. All rights reserved. Use of this source code is governed by a
28+
// BSD-style license that can be found in the LICENSE file.
29+
30+
import 'dart:ffi';
31+
32+
import 'package:ffi/ffi.dart';
33+
34+
import 'FfiStructCopy.dart';
35+
''';
36+
37+
String generateSize(int size) => '''
38+
class Struct${size}Bytes extends Struct {
39+
@Array($size)
40+
external Array<Uint8> a0;
41+
}
42+
43+
class Struct${size}BytesWrapper extends Struct {
44+
external Struct${size}Bytes nested;
45+
}
46+
47+
class Copy${size}Bytes extends StructCopyBenchmark {
48+
@override
49+
Pointer<Struct${size}BytesWrapper> from = nullptr;
50+
@override
51+
Pointer<Struct${size}BytesWrapper> to = nullptr;
52+
53+
Copy${size}Bytes() : super('FfiStructCopy.Copy${size}Bytes');
54+
55+
@override
56+
int get copySizeInBytes => sizeOf<Struct${size}BytesWrapper>();
57+
58+
@override
59+
void setup(int batchSize) {
60+
from = calloc(batchSize);
61+
to = calloc(batchSize);
62+
}
63+
64+
@override
65+
void run(int batchSize) {
66+
for (int i = 0; i < batchSize; i++) {
67+
to[i].nested = from[i].nested;
68+
}
69+
}
70+
}
71+
''';

0 commit comments

Comments
 (0)