Keep a `ByteData` around in `CodedBufferReader` to avoid repeated `ByteData` allocs (#890)
In Dart, the only way to convert a list of bytes to an `int` or `double` is by using a `ByteData`.
Currently `CodedBufferReader` allocates a new `ByteData` for every fixed-size `int` or `float` decoding.
With this change we now allocate a `ByteData` once initialization and reuse it.
Benchmark results, using the same benchmark reported in #888:
| | Before | After | Diff |
|------------------------------|------------|------------|---------------------|
| AOT | 26,763 us | 25,305 us | - 1,458 us, - 5.4% |
| JIT | 24,573 us | 25,521 us | + 948 us, + 3.8% |
| dart2js -O4 | 212,000 us | 199,300 us | -12,700 us, - 5.9% |
| dart2wasm --omit-type-checks | 89,750 us | 78,958 us | -10,792 us, -12.0% |
diff --git a/protobuf/lib/src/protobuf/coded_buffer_reader.dart b/protobuf/lib/src/protobuf/coded_buffer_reader.dart
index 3c2165c..cc7e3dd 100644
--- a/protobuf/lib/src/protobuf/coded_buffer_reader.dart
+++ b/protobuf/lib/src/protobuf/coded_buffer_reader.dart
@@ -13,6 +13,11 @@
static const int DEFAULT_SIZE_LIMIT = 64 << 20;
final Uint8List _buffer;
+
+ /// [ByteData] of [_buffer], created once to be able to decode fixed-size
+ /// integers and floats without having to allocate a [ByteData] every time.
+ late final ByteData _byteData = ByteData.sublistView(_buffer);
+
int _bufferPos = 0;
int _currentLimit = -1;
int _lastTag = 0;
@@ -123,9 +128,20 @@
Int64 readUint64() => _readRawVarint64();
int readSint32() => _decodeZigZag32(readUint32());
Int64 readSint64() => _decodeZigZag64(readUint64());
- int readFixed32() => _readByteData(4).getUint32(0, Endian.little);
+
+ int readFixed32() {
+ final pos = _bufferPos;
+ _checkLimit(4);
+ return _byteData.getUint32(pos, Endian.little);
+ }
+
Int64 readFixed64() => readSfixed64();
- int readSfixed32() => _readByteData(4).getInt32(0, Endian.little);
+
+ int readSfixed32() {
+ final pos = _bufferPos;
+ _checkLimit(4);
+ return _byteData.getInt32(pos, Endian.little);
+ }
Int64 readSfixed64() {
final pos = _bufferPos;
@@ -158,8 +174,17 @@
.convert(_buffer, stringPos, stringPos + length);
}
- double readFloat() => _readByteData(4).getFloat32(0, Endian.little);
- double readDouble() => _readByteData(8).getFloat64(0, Endian.little);
+ double readFloat() {
+ final pos = _bufferPos;
+ _checkLimit(4);
+ return _byteData.getFloat32(pos, Endian.little);
+ }
+
+ double readDouble() {
+ final pos = _bufferPos;
+ _checkLimit(8);
+ return _byteData.getFloat64(pos, Endian.little);
+ }
int readTag() {
if (isAtEnd()) {
@@ -270,10 +295,4 @@
}
throw InvalidProtocolBufferException.malformedVarint();
}
-
- ByteData _readByteData(int sizeInBytes) {
- _checkLimit(sizeInBytes);
- return ByteData.view(_buffer.buffer,
- _buffer.offsetInBytes + _bufferPos - sizeInBytes, sizeInBytes);
- }
}