[io] Add a benchmark for the IOSink returned by File.openWrite

Change-Id: Ic27d24633a305d0567c19f3694b1a477b3a1eef2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/285706
Commit-Queue: Brian Quinlan <bquinlan@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
diff --git a/benchmarks/FileIOSink/dart/FileIOSink.dart b/benchmarks/FileIOSink/dart/FileIOSink.dart
new file mode 100644
index 0000000..85c0cc7
--- /dev/null
+++ b/benchmarks/FileIOSink/dart/FileIOSink.dart
@@ -0,0 +1,114 @@
+// Copyright (c) 2023, 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.
+
+/// Micro-benchmark for the [IOSink] returned by [File.openWrite].
+
+import 'dart:io';
+import 'dart:typed_data';
+
+import 'package:benchmark_harness/benchmark_harness.dart';
+
+const numBytesToWrite = 200000; // Write 200KiB in all benchmarks runs.
+
+/// Benchmark building a [numBytesToWrite] byte file a single byte at a time.
+///
+/// Based on https://github.com/dart-lang/sdk/issues/32874#issue-314022259
+class BenchmarkManySmallAdds extends AsyncBenchmarkBase {
+  late Directory _tempDir;
+  late IOSink _ioSink;
+  final _singleByte = Uint8List(1);
+
+  BenchmarkManySmallAdds() : super('FileIOSink.Add.ManySmall');
+
+  @override
+  Future<void> setup() async {
+    _tempDir = Directory.systemTemp.createTempSync();
+    _ioSink = File(_tempDir.uri.resolve('many-small').toFilePath()).openWrite();
+  }
+
+  @override
+  Future<void> teardown() async {
+    await _ioSink.close();
+    _tempDir.deleteSync(recursive: true);
+  }
+
+  @override
+  Future<void> run() async {
+    for (var i = 0; i < numBytesToWrite; ++i) {
+      _ioSink.add(_singleByte);
+    }
+    await _ioSink.flush();
+  }
+}
+
+/// Benchmark building a [numBytesToWrite] byte file with a single call to
+/// [IOSink.add].
+class BenchmarkOneLargeAdd extends AsyncBenchmarkBase {
+  late final Directory _tempDir;
+  late final IOSink _ioSink;
+  final _largeData = Uint8List(numBytesToWrite);
+
+  BenchmarkOneLargeAdd() : super('FileIOSink.Add.OneLarge');
+
+  @override
+  Future<void> setup() async {
+    _tempDir = Directory.systemTemp.createTempSync();
+    _ioSink = File(_tempDir.uri.resolve('one-large').toFilePath()).openWrite();
+  }
+
+  @override
+  Future<void> teardown() async {
+    await _ioSink.close();
+    _tempDir.deleteSync(recursive: true);
+  }
+
+  @override
+  Future<void> run() async {
+    _ioSink.add(_largeData);
+    await _ioSink.flush();
+  }
+}
+
+/// Benchmark building a file by alternating calls to [IOSink.add] with large
+/// and small amounts of data.
+class BenchmarkAlternatingSizedAdd extends AsyncBenchmarkBase {
+  late final Directory _tempDir;
+  late final IOSink _ioSink;
+  final _largeData = Uint8List(numBytesToWrite - 1);
+  final _smallData = Uint8List(1);
+
+  BenchmarkAlternatingSizedAdd() : super('FileIOSink.Add.AlternatingAddSize');
+
+  @override
+  Future<void> setup() async {
+    _tempDir = Directory.systemTemp.createTempSync();
+    _ioSink = File(_tempDir.uri.resolve('alternative-add-size').toFilePath())
+        .openWrite();
+  }
+
+  @override
+  Future<void> teardown() async {
+    await _ioSink.close();
+    _tempDir.deleteSync(recursive: true);
+  }
+
+  @override
+  Future<void> run() async {
+    _ioSink.add(_largeData);
+    _ioSink.add(_smallData);
+    await _ioSink.flush();
+  }
+}
+
+void main() {
+  final benchmarks = [
+    BenchmarkManySmallAdds(),
+    BenchmarkOneLargeAdd(),
+    BenchmarkAlternatingSizedAdd(),
+  ];
+
+  for (final benchmark in benchmarks) {
+    benchmark.report();
+  }
+}