Dart - add custom allocator support (#6711)

* Dart - add custom allocator support

* Dart - only copy written bytes during resize, not the whole old buffer.
This commit is contained in:
Ivan Dlugos
2021-07-08 22:02:09 +02:00
committed by GitHub
parent c0ba2870c9
commit 8ab35b2a5f
2 changed files with 88 additions and 14 deletions

View File

@@ -111,6 +111,8 @@ class Builder {
ByteData _buf; ByteData _buf;
final Allocator _allocator;
/// The maximum alignment that has been seen so far. If [_buf] has to be /// The maximum alignment that has been seen so far. If [_buf] has to be
/// reallocated in the future (to insert room at its start for more bytes) the /// reallocated in the future (to insert room at its start for more bytes) the
/// reallocation will need to be a multiple of this many bytes. /// reallocation will need to be a multiple of this many bytes.
@@ -138,9 +140,13 @@ class Builder {
/// automatically grow the array if/as needed. `internStrings`, if set to /// automatically grow the array if/as needed. `internStrings`, if set to
/// true, will cause [writeString] to pool strings in the buffer so that /// true, will cause [writeString] to pool strings in the buffer so that
/// identical strings will always use the same offset in tables. /// identical strings will always use the same offset in tables.
Builder({this.initialSize: 1024, bool internStrings = false}) Builder({
: _buf = ByteData(initialSize) { this.initialSize: 1024,
if (internStrings == true) { bool internStrings = false,
Allocator allocator = const DefaultAllocator(),
}) : _allocator = allocator,
_buf = allocator.allocate(initialSize) {
if (internStrings) {
_strings = new Map<String, int>(); _strings = new Map<String, int>();
} }
} }
@@ -330,12 +336,14 @@ class Builder {
return tableTail; return tableTail;
} }
/// This method low level method can be used to return a raw piece of the buffer /// This method low level method can be used to return a raw piece of the
/// after using the the put* methods. /// buffer after using the put* methods.
/// ///
/// Most clients should prefer calling [finish]. /// Most clients should prefer calling [finish].
Uint8List lowFinish() { Uint8List lowFinish() {
return _buf.buffer.asUint8List(_buf.lengthInBytes - size()); final finishedSize = size();
return _buf.buffer
.asUint8List(_buf.lengthInBytes - finishedSize, finishedSize);
} }
/// Finish off the creation of the buffer. The given [offset] is used as the /// Finish off the creation of the buffer. The given [offset] is used as the
@@ -353,7 +361,8 @@ class Builder {
fileIdentifier.codeUnitAt(i)); fileIdentifier.codeUnitAt(i));
} }
} }
return _buf.buffer.asUint8List(_buf.lengthInBytes - finishedSize); return _buf.buffer
.asUint8List(_buf.lengthInBytes - finishedSize, finishedSize);
} }
/// Writes a Float64 to the tail of the buffer after preparing space for it. /// Writes a Float64 to the tail of the buffer after preparing space for it.
@@ -716,11 +725,7 @@ class Builder {
int deltaCapacity = desiredNewCapacity - oldCapacity; int deltaCapacity = desiredNewCapacity - oldCapacity;
deltaCapacity += (-deltaCapacity) % _maxAlign; deltaCapacity += (-deltaCapacity) % _maxAlign;
int newCapacity = oldCapacity + deltaCapacity; int newCapacity = oldCapacity + deltaCapacity;
ByteData newBuf = new ByteData(newCapacity); _buf = _allocator.resize(_buf, newCapacity, _tail, 0);
newBuf.buffer
.asUint8List()
.setAll(deltaCapacity, _buf.buffer.asUint8List());
_buf = newBuf;
} }
} }
// Update the tail pointer. // Update the tail pointer.
@@ -1243,3 +1248,56 @@ class _VTable {
} }
} }
} }
/// The interface that [Builder] uses to allocate buffers for encoding.
abstract class Allocator {
const Allocator();
/// Allocate a [ByteData] buffer of a given size.
ByteData allocate(int size);
/// Free the given [ByteData] buffer previously allocated by [allocate].
void deallocate(ByteData data);
/// Reallocate [newSize] bytes of memory, replacing the old [oldData]. This
/// grows downwards, and is intended specifically for use with [Builder].
/// Params [inUseBack] and [inUseFront] indicate how much of [oldData] is
/// actually in use at each end, and needs to be copied.
ByteData resize(
ByteData oldData, int newSize, int inUseBack, int inUseFront) {
final newData = allocate(newSize);
_copyDownward(oldData, newData, inUseBack, inUseFront);
deallocate(oldData);
return newData;
}
/// Called by [resize] to copy memory from [oldData] to [newData]. Only
/// memory of size [inUseFront] and [inUseBack] will be copied from the front
/// and back of the old memory allocation.
void _copyDownward(
ByteData oldData, ByteData newData, int inUseBack, int inUseFront) {
if (inUseBack != 0) {
newData.buffer.asUint8List().setAll(
newData.lengthInBytes - inUseBack,
oldData.buffer.asUint8List().getRange(
oldData.lengthInBytes - inUseBack, oldData.lengthInBytes));
}
if (inUseFront != 0) {
newData.buffer
.asUint8List()
.setAll(0, oldData.buffer.asUint8List().getRange(0, inUseFront));
}
}
}
class DefaultAllocator extends Allocator {
const DefaultAllocator();
@override
ByteData allocate(int size) => ByteData(size);
@override
void deallocate(ByteData _) {
// nothing to do, it's garbage-collected
}
}

View File

@@ -133,6 +133,22 @@ class CheckOtherLangaugesData {
} }
} }
/// Test a custom, fixed-memory allocator (no actual allocations performed)
class CustomAllocator extends Allocator {
final _memory = ByteData(10 * 1024);
@override
ByteData allocate(int size) {
if (size > _memory.lengthInBytes) {
throw UnsupportedError('Trying to allocate too much');
}
return ByteData.sublistView(_memory, 0, size);
}
@override
void deallocate(ByteData _) {}
}
@reflectiveTest @reflectiveTest
class BuilderTest { class BuilderTest {
void test_monsterBuilder([Builder? builder]) { void test_monsterBuilder([Builder? builder]) {
@@ -247,7 +263,7 @@ class BuilderTest {
} }
void test_low() { void test_low() {
Builder builder = new Builder(initialSize: 0); final builder = Builder(initialSize: 0, allocator: CustomAllocator());
expect((builder..putUint8(1)).lowFinish(), [1]); expect((builder..putUint8(1)).lowFinish(), [1]);
expect((builder..putUint32(2)).lowFinish(), [2, 0, 0, 0, 0, 0, 0, 1]); expect((builder..putUint32(2)).lowFinish(), [2, 0, 0, 0, 0, 0, 0, 1]);
expect((builder..putUint8(3)).lowFinish(), expect((builder..putUint8(3)).lowFinish(),
@@ -263,7 +279,7 @@ class BuilderTest {
void test_table_default() { void test_table_default() {
List<int> byteList; List<int> byteList;
{ {
Builder builder = new Builder(initialSize: 0); final builder = Builder(initialSize: 0, allocator: CustomAllocator());
builder.startTable(); builder.startTable();
builder.addInt32(0, 10, 10); builder.addInt32(0, 10, 10);
builder.addInt32(1, 20, 10); builder.addInt32(1, 20, 10);