[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 <alexmarkov@google.com>
Commit-Queue: Ryan Macnak <rmacnak@google.com>
This commit is contained in:
Ryan Macnak 2022-10-17 17:42:54 +00:00 committed by Commit Queue
parent 63f957a687
commit d2a43581a3
4 changed files with 203 additions and 4 deletions

View file

@ -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:

View file

@ -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<int> 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;

View file

@ -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<int> 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);
});
}

View file

@ -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<int> 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);
});
}