-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for running perftools to use hardware performance counter…
…s when benchmarking. (dart-archive/benchmark_harness#98) Add a new class PerfBenchmarkBase that extends BenchmarkBase, with a new asynchronous reportPerf() method that runs the benchmark while attached to a "perf stat" process that measures performance with CPU hardware performance counters.
- Loading branch information
Showing
11 changed files
with
234 additions
and
38 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
26 changes: 26 additions & 0 deletions
26
pkgs/benchmark_harness/integration_test/perf_benchmark_test.dart
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,26 @@ | ||
// Copyright 2024, 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 'package:benchmark_harness/perf_benchmark_harness.dart'; | ||
import 'package:test/test.dart'; | ||
|
||
class PerfBenchmark extends PerfBenchmarkBase { | ||
PerfBenchmark(super.name); | ||
int runCount = 0; | ||
|
||
@override | ||
void run() { | ||
runCount++; | ||
for (final i in List.filled(1000, 7)) { | ||
runCount += i - i; | ||
} | ||
} | ||
} | ||
|
||
void main() { | ||
test('run is called', () async { | ||
final benchmark = PerfBenchmark('ForLoop'); | ||
await benchmark.reportPerf(); | ||
}); | ||
} |
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,7 @@ | ||
// Copyright (c) 2024, 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. | ||
|
||
export 'src/perf_benchmark_base_stub.dart' | ||
if (dart.library.io) 'src/perf_benchmark_base.dart'; | ||
export 'src/score_emitter.dart'; |
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
132 changes: 132 additions & 0 deletions
132
pkgs/benchmark_harness/lib/src/perf_benchmark_base.dart
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,132 @@ | ||
// Copyright (c) 2024, 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:async'; | ||
import 'dart:convert'; | ||
import 'dart:io'; | ||
|
||
import 'benchmark_base.dart'; | ||
import 'score_emitter.dart'; | ||
|
||
class PerfBenchmarkBase extends BenchmarkBase { | ||
late final Directory fifoDir; | ||
late final String perfControlFifo; | ||
late final RandomAccessFile openedFifo; | ||
late final String perfControlAck; | ||
late final RandomAccessFile openedAck; | ||
late final Process perfProcess; | ||
late final List<String> perfProcessArgs; | ||
|
||
PerfBenchmarkBase(super.name, {super.emitter = const PrintEmitter()}); | ||
|
||
Future<void> _createFifos() async { | ||
perfControlFifo = '${fifoDir.path}/perf_control_fifo'; | ||
perfControlAck = '${fifoDir.path}/perf_control_ack'; | ||
for (final path in [perfControlFifo, perfControlAck]) { | ||
final fifoResult = await Process.run('mkfifo', [path]); | ||
if (fifoResult.exitCode != 0) { | ||
throw ProcessException('mkfifo', [path], | ||
'Cannot create fifo: ${fifoResult.stderr}', fifoResult.exitCode); | ||
} | ||
} | ||
} | ||
|
||
Future<void> _startPerfStat() async { | ||
await _createFifos(); | ||
perfProcessArgs = [ | ||
'stat', | ||
'--delay=-1', | ||
'--control=fifo:$perfControlFifo,$perfControlAck', | ||
'-x\\t', | ||
'--pid=$pid', | ||
]; | ||
perfProcess = await Process.start('perf', perfProcessArgs); | ||
} | ||
|
||
void _enablePerf() { | ||
openedFifo = File(perfControlFifo).openSync(mode: FileMode.writeOnly); | ||
openedAck = File(perfControlAck).openSync(); | ||
openedFifo.writeStringSync('enable\n'); | ||
_waitForAck(); | ||
} | ||
|
||
Future<void> _stopPerfStat(int totalIterations) async { | ||
openedFifo.writeStringSync('disable\n'); | ||
openedFifo.closeSync(); | ||
_waitForAck(); | ||
openedAck.closeSync(); | ||
perfProcess.kill(ProcessSignal.sigint); | ||
unawaited(perfProcess.stdout.drain()); | ||
final lines = await perfProcess.stderr | ||
.transform(utf8.decoder) | ||
.transform(const LineSplitter()) | ||
.toList(); | ||
final exitCode = await perfProcess.exitCode; | ||
// Exit code from perf is -SIGINT when terminated with SIGINT. | ||
if (exitCode != 0 && exitCode != -ProcessSignal.sigint.signalNumber) { | ||
throw ProcessException( | ||
'perf', perfProcessArgs, lines.join('\n'), exitCode); | ||
} | ||
|
||
const metrics = { | ||
'cycles': 'CpuCycles', | ||
'page-faults': 'MajorPageFaults', | ||
}; | ||
for (final line in lines) { | ||
if (line.split('\t') | ||
case [ | ||
String counter, | ||
_, | ||
String event && ('cycles' || 'page-faults'), | ||
... | ||
]) { | ||
emitter.emit(name, double.parse(counter) / totalIterations, | ||
metric: metrics[event]!); | ||
} | ||
} | ||
emitter.emit('$name.totalIterations', totalIterations.toDouble(), | ||
metric: 'Count'); | ||
} | ||
|
||
/// Measures the score for the benchmark and returns it. | ||
Future<double> measurePerf() async { | ||
Measurement result; | ||
setup(); | ||
try { | ||
fifoDir = await Directory.systemTemp.createTemp('fifo'); | ||
try { | ||
// Warmup for at least 100ms. Discard result. | ||
measureForImpl(warmup, 100); | ||
await _startPerfStat(); | ||
try { | ||
_enablePerf(); | ||
// Run the benchmark for at least 2000ms. | ||
result = measureForImpl(exercise, minimumMeasureDurationMillis); | ||
await _stopPerfStat(result.totalIterations); | ||
} catch (_) { | ||
perfProcess.kill(ProcessSignal.sigkill); | ||
rethrow; | ||
} | ||
} finally { | ||
await fifoDir.delete(recursive: true); | ||
} | ||
} finally { | ||
teardown(); | ||
} | ||
return result.score; | ||
} | ||
|
||
Future<void> reportPerf() async { | ||
emitter.emit(name, await measurePerf(), unit: 'us.'); | ||
} | ||
|
||
void _waitForAck() { | ||
// Perf writes 'ack\n\x00' to the acknowledgement fifo. | ||
const ackLength = 'ack\n\x00'.length; | ||
var ack = <int>[...openedAck.readSync(ackLength)]; | ||
while (ack.length < ackLength) { | ||
ack.addAll(openedAck.readSync(ackLength - ack.length)); | ||
} | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
pkgs/benchmark_harness/lib/src/perf_benchmark_base_stub.dart
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,18 @@ | ||
// Copyright (c) 2024, 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 'benchmark_base.dart'; | ||
import 'score_emitter.dart'; | ||
|
||
class PerfBenchmarkBase extends BenchmarkBase { | ||
PerfBenchmarkBase(super.name, {super.emitter = const PrintEmitter()}); | ||
|
||
Future<double> measurePerf() async { | ||
return super.measure(); | ||
} | ||
|
||
Future<void> reportPerf() async { | ||
super.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
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