From d2a43581a3af72f7a26a5739274f01b22661c0b3 Mon Sep 17 00:00:00 2001 From: Ryan Macnak Date: Mon, 17 Oct 2022 17:42:54 +0000 Subject: [PATCH] [io] Avoid serializing unwritten bytes in async I/O. TEST=ci Bug: https://github.com/dart-lang/sdk/issues/50206 Change-Id: Ibe6c326578ec4a2ca90634714e4628c02a5e5bcc Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/264260 Reviewed-by: Alexander Markov Commit-Queue: Ryan Macnak --- runtime/vm/message_snapshot.cc | 14 +++ sdk/lib/io/common.dart | 9 +- tests/standalone/io/regress_50206_test.dart | 91 ++++++++++++++++++ tests/standalone_2/io/regress_50206_test.dart | 93 +++++++++++++++++++ 4 files changed, 203 insertions(+), 4 deletions(-) create mode 100644 tests/standalone/io/regress_50206_test.dart create mode 100644 tests/standalone_2/io/regress_50206_test.dart diff --git a/runtime/vm/message_snapshot.cc b/runtime/vm/message_snapshot.cc index 389d347d39f..dedd94d12c1 100644 --- a/runtime/vm/message_snapshot.cc +++ b/runtime/vm/message_snapshot.cc @@ -2140,45 +2140,59 @@ class TypedDataViewMessageDeserializationCluster Dart_TypedData_Type type; switch (cid_) { case kTypedDataInt8ArrayViewCid: + case kUnmodifiableTypedDataInt8ArrayViewCid: type = Dart_TypedData_kInt8; break; case kTypedDataUint8ArrayViewCid: + case kUnmodifiableTypedDataUint8ArrayViewCid: type = Dart_TypedData_kUint8; break; case kTypedDataUint8ClampedArrayViewCid: + case kUnmodifiableTypedDataUint8ClampedArrayViewCid: type = Dart_TypedData_kUint8Clamped; break; case kTypedDataInt16ArrayViewCid: + case kUnmodifiableTypedDataInt16ArrayViewCid: type = Dart_TypedData_kInt16; break; case kTypedDataUint16ArrayViewCid: + case kUnmodifiableTypedDataUint16ArrayViewCid: type = Dart_TypedData_kUint16; break; case kTypedDataInt32ArrayViewCid: + case kUnmodifiableTypedDataInt32ArrayViewCid: type = Dart_TypedData_kInt32; break; case kTypedDataUint32ArrayViewCid: + case kUnmodifiableTypedDataUint32ArrayViewCid: type = Dart_TypedData_kUint32; break; case kTypedDataInt64ArrayViewCid: + case kUnmodifiableTypedDataInt64ArrayViewCid: type = Dart_TypedData_kInt64; break; case kTypedDataUint64ArrayViewCid: + case kUnmodifiableTypedDataUint64ArrayViewCid: type = Dart_TypedData_kUint64; break; case kTypedDataFloat32ArrayViewCid: + case kUnmodifiableTypedDataFloat32ArrayViewCid: type = Dart_TypedData_kFloat32; break; case kTypedDataFloat64ArrayViewCid: + case kUnmodifiableTypedDataFloat64ArrayViewCid: type = Dart_TypedData_kFloat64; break; case kTypedDataInt32x4ArrayViewCid: + case kUnmodifiableTypedDataInt32x4ArrayViewCid: type = Dart_TypedData_kInt32x4; break; case kTypedDataFloat32x4ArrayViewCid: + case kUnmodifiableTypedDataFloat32x4ArrayViewCid: type = Dart_TypedData_kFloat32x4; break; case kTypedDataFloat64x2ArrayViewCid: + case kUnmodifiableTypedDataFloat64x2ArrayViewCid: type = Dart_TypedData_kFloat64x2; break; default: diff --git a/sdk/lib/io/common.dart b/sdk/lib/io/common.dart index 491104ac2b5..3684b1fdb8c 100644 --- a/sdk/lib/io/common.dart +++ b/sdk/lib/io/common.dart @@ -83,12 +83,13 @@ class _BufferAndStart { } // Ensure that the input List can be serialized through a native port. -// Only Int8List and Uint8List Lists are serialized directly. -// All other lists are first copied into a Uint8List. This has the added -// benefit that it is faster to access from the C code as well. _BufferAndStart _ensureFastAndSerializableByteData( List buffer, int start, int end) { - if (buffer is Uint8List) { + if ((buffer is Uint8List) && + (buffer.buffer.lengthInBytes == buffer.length)) { + // Send typed data directly, unless it is a partial view, in which case we + // would rather copy than drag in the potentially much large backing store. + // See issue 50206. return new _BufferAndStart(buffer, start); } int length = end - start; diff --git a/tests/standalone/io/regress_50206_test.dart b/tests/standalone/io/regress_50206_test.dart new file mode 100644 index 00000000000..860777851f7 --- /dev/null +++ b/tests/standalone/io/regress_50206_test.dart @@ -0,0 +1,91 @@ +// Copyright (c) 2022, 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:io"; +import "dart:typed_data"; + +import "package:expect/expect.dart"; +import "test_utils.dart" show withTempDir; + +const chunkCount = 8192; +const chunkSize = 8192; + +Future timeWrite(File file, chunks) async { + final sink = file.openWrite(); + + final Stopwatch stopwatch = new Stopwatch()..start(); + for (var chunk in chunks) { + sink.add(chunk); + } + await sink.close(); + stopwatch.stop(); + + Expect.equals(chunkCount * chunkSize, await file.length()); + + await file.delete(); + + return stopwatch.elapsedMilliseconds; +} + +main() async { + await withTempDir("regress50206", (Directory tempDir) async { + File file = new File("${tempDir.path}/file.tmp"); + + int arrayTime = 0; + { + var chunks = []; + for (var i = 0; i < chunkCount; i++) { + var chunk = new Uint8List(chunkSize); + chunks.add(chunk); + } + arrayTime = await timeWrite(file, chunks); + print("arrays: $arrayTime ms"); + } + + int unmodifiableArrayTime = 0; + { + var chunks = []; + for (var i = 0; i < chunkCount; i++) { + var chunk = new UnmodifiableUint8ListView(new Uint8List(chunkSize)); + chunks.add(chunk); + } + unmodifiableArrayTime = await timeWrite(file, chunks); + print("unmodifiable arrays: $unmodifiableArrayTime ms"); + } + + int viewTime = 0; + { + var chunks = []; + var backing = new Uint8List(chunkSize * chunkCount); + for (var i = 0; i < chunkCount; i++) { + var chunk = + new Uint8List.view(backing.buffer, i * chunkSize, chunkSize); + chunks.add(chunk); + } + viewTime = await timeWrite(file, chunks); + print("views: $viewTime ms"); + } + + int unmodifiableViewTime = 0; + { + var chunks = []; + var backing = new Uint8List(chunkSize * chunkCount); + for (var i = 0; i < chunkCount; i++) { + var chunk = new UnmodifiableUint8ListView( + new Uint8List.view(backing.buffer, i * chunkSize, chunkSize)); + chunks.add(chunk); + } + unmodifiableViewTime = await timeWrite(file, chunks); + print("unmodifiable views: $unmodifiableViewTime ms"); + } + + // Assert with factor a 1000 to avoid the test being flaky from I/O + // variance. If we copy the whole backing store for each view chunk, things + // will be quadratically slower, i.e. more than a factor of 1000. + Expect.isTrue(unmodifiableArrayTime / arrayTime < 1000); + Expect.isTrue(viewTime / arrayTime < 1000); + Expect.isTrue(unmodifiableViewTime / arrayTime < 1000); + }); +} diff --git a/tests/standalone_2/io/regress_50206_test.dart b/tests/standalone_2/io/regress_50206_test.dart new file mode 100644 index 00000000000..b67518ebfee --- /dev/null +++ b/tests/standalone_2/io/regress_50206_test.dart @@ -0,0 +1,93 @@ +// Copyright (c) 2022, 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. + +// @dart = 2.9 + +import "dart:async"; +import "dart:io"; +import "dart:typed_data"; + +import "package:expect/expect.dart"; +import "test_utils.dart" show withTempDir; + +const chunkCount = 8192; +const chunkSize = 8192; + +Future timeWrite(File file, chunks) async { + final sink = file.openWrite(); + + final Stopwatch stopwatch = new Stopwatch()..start(); + for (var chunk in chunks) { + sink.add(chunk); + } + await sink.close(); + stopwatch.stop(); + + Expect.equals(chunkCount * chunkSize, await file.length()); + + await file.delete(); + + return stopwatch.elapsedMilliseconds; +} + +main() async { + await withTempDir("regress50206", (Directory tempDir) async { + File file = new File("${tempDir.path}/file.tmp"); + + int arrayTime = 0; + { + var chunks = []; + for (var i = 0; i < chunkCount; i++) { + var chunk = new Uint8List(chunkSize); + chunks.add(chunk); + } + arrayTime = await timeWrite(file, chunks); + print("arrays: $arrayTime ms"); + } + + int unmodifiableArrayTime = 0; + { + var chunks = []; + for (var i = 0; i < chunkCount; i++) { + var chunk = new UnmodifiableUint8ListView(new Uint8List(chunkSize)); + chunks.add(chunk); + } + unmodifiableArrayTime = await timeWrite(file, chunks); + print("unmodifiable arrays: $unmodifiableArrayTime ms"); + } + + int viewTime = 0; + { + var chunks = []; + var backing = new Uint8List(chunkSize * chunkCount); + for (var i = 0; i < chunkCount; i++) { + var chunk = + new Uint8List.view(backing.buffer, i * chunkSize, chunkSize); + chunks.add(chunk); + } + viewTime = await timeWrite(file, chunks); + print("views: $viewTime ms"); + } + + int unmodifiableViewTime = 0; + { + var chunks = []; + var backing = new Uint8List(chunkSize * chunkCount); + for (var i = 0; i < chunkCount; i++) { + var chunk = new UnmodifiableUint8ListView( + new Uint8List.view(backing.buffer, i * chunkSize, chunkSize)); + chunks.add(chunk); + } + unmodifiableViewTime = await timeWrite(file, chunks); + print("unmodifiable views: $unmodifiableViewTime ms"); + } + + // Assert with factor a 1000 to avoid the test being flaky from I/O + // variance. If we copy the whole backing store for each view chunk, things + // will be quadratically slower, i.e. more than a factor of 1000. + Expect.isTrue(unmodifiableArrayTime / arrayTime < 1000); + Expect.isTrue(viewTime / arrayTime < 1000); + Expect.isTrue(unmodifiableViewTime / arrayTime < 1000); + }); +}