dart-sdk/runtime/lib/string_buffer_patch.dart
Stephen Adams 2bb2a12720 Avoid multiple reallocations of StringBuffer._parts
The original code

    _parts = [ str ];

allocated a _GrowableList backed by a single element _List.
Subsequent calls to _parts.add() grows the backing list to 2, then 4, etc.

On a dart2js workload, allocating with a reasonable initial capacity
reduces the number of _List objects allocated by 8%, with 4% reduction in
_List bytes allocated.

R=lrn@google.com

Review URL: https://codereview.chromium.org/1888453011 .
2016-04-18 18:08:16 -07:00

185 lines
5.6 KiB
Dart

// Copyright (c) 2012, 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.
patch class StringBuffer {
static const int _BUFFER_SIZE = 64;
static const int _PARTS_TO_COMPACT = 128;
static const int _PARTS_TO_COMPACT_SIZE_LIMIT = _PARTS_TO_COMPACT * 8;
/**
* When strings are written to the string buffer, we add them to a
* list of string parts.
*/
List<String> _parts;
/**
* Total number of code units in the string parts. Does not include
* the code units added to the buffer.
*/
int _partsCodeUnits = 0;
/**
* To preserve memory, we sometimes compact the parts. This combines
* several smaller parts into a single larger part to cut down on the
* cost that comes from the per-object memory overhead. We keep track
* of the last index where we ended our compaction and the number of
* code units added since the last compaction.
*/
int _partsCompactionIndex = 0;
int _partsCodeUnitsSinceCompaction = 0;
/**
* The buffer is used to build up a string from code units. It is
* used when writing short strings or individual char codes to the
* buffer. The buffer is allocated on demand.
*/
Uint16List _buffer;
int _bufferPosition = 0;
/**
* Collects the approximate maximal magnitude of the code units added
* to the buffer.
*
* The value of each added code unit is or'ed with this variable, so the
* most significant bit set in any code unit is also set in this value.
* If below 256, the string in the buffer is a Latin-1 string.
*/
int _bufferCodeUnitMagnitude = 0;
/// Creates the string buffer with an initial content.
/* patch */ StringBuffer([Object content = ""]) {
write(content);
}
/* patch */ int get length => _partsCodeUnits + _bufferPosition;
/* patch */ void write(Object obj) {
String str = '$obj';
if (str.isEmpty) return;
_consumeBuffer();
_addPart(str);
}
/* patch */ void writeCharCode(int charCode) {
if (charCode <= 0xFFFF) {
if (charCode < 0) {
throw new RangeError.range(charCode, 0, 0x10FFFF);
}
_ensureCapacity(1);
_buffer[_bufferPosition++] = charCode;
_bufferCodeUnitMagnitude |= charCode;
} else {
if (charCode > 0x10FFFF) {
throw new RangeError.range(charCode, 0, 0x10FFFF);
}
_ensureCapacity(2);
int bits = charCode - 0x10000;
_buffer[_bufferPosition++] = 0xD800 | (bits >> 10);
_buffer[_bufferPosition++] = 0xDC00 | (bits & 0x3FF);
_bufferCodeUnitMagnitude |= 0xFFFF;
}
}
/* patch */ void writeAll(Iterable objects, [String separator = ""]) {
Iterator iterator = objects.iterator;
if (!iterator.moveNext()) return;
if (separator.isEmpty) {
do {
write(iterator.current);
} while (iterator.moveNext());
} else {
write(iterator.current);
while (iterator.moveNext()) {
write(separator);
write(iterator.current);
}
}
}
/* patch */ void writeln([Object obj = ""]) {
write(obj);
write("\n");
}
/** Makes the buffer empty. */
/* patch */ void clear() {
_parts = null;
_partsCodeUnits = _bufferPosition = _bufferCodeUnitMagnitude = 0;
}
/** Returns the contents of buffer as a string. */
/* patch */ String toString() {
_consumeBuffer();
return (_partsCodeUnits == 0) ?
"" :
_StringBase._concatRange(_parts, 0, _parts.length);
}
/** Ensures that the buffer has enough capacity to add n code units. */
void _ensureCapacity(int n) {
if (_buffer == null) {
_buffer = new Uint16List(_BUFFER_SIZE);
} else if (_bufferPosition + n > _buffer.length) {
_consumeBuffer();
}
}
/**
* Consumes the content of the buffer by turning it into a string
* and adding it as a part. After calling this the buffer position
* will be reset to zero.
*/
void _consumeBuffer() {
if (_bufferPosition == 0) return;
bool isLatin1 = _bufferCodeUnitMagnitude <= 0xFF;
String str = _create(_buffer, _bufferPosition, isLatin1);
_bufferPosition = _bufferCodeUnitMagnitude = 0;
_addPart(str);
}
/**
* Adds a new part to this string buffer and keeps track of how
* many code units are contained in the parts.
*/
void _addPart(String str) {
int length = str.length;
_partsCodeUnits += length;
_partsCodeUnitsSinceCompaction += length;
if (_parts == null) {
// Empirically this is a good capacity to minimize total bytes allocated.
_parts = new _GrowableList.withCapacity(10)..add(str);
} else {
_parts.add(str);
int partsSinceCompaction = _parts.length - _partsCompactionIndex;
if (partsSinceCompaction == _PARTS_TO_COMPACT) {
_compact();
}
}
}
/**
* Compacts the last N parts if their average size allows us to save a
* lot of memory by turning them all into a single part.
*/
void _compact() {
if (_partsCodeUnitsSinceCompaction < _PARTS_TO_COMPACT_SIZE_LIMIT) {
String compacted = _StringBase._concatRange(
_parts,
_partsCompactionIndex, // Start
_partsCompactionIndex + _PARTS_TO_COMPACT // End
);
_parts.length = _parts.length - _PARTS_TO_COMPACT;
_parts.add(compacted);
}
_partsCodeUnitsSinceCompaction = 0;
_partsCompactionIndex = _parts.length;
}
/**
* Create a [String] from the UFT-16 code units in buffer.
*/
static String _create(Uint16List buffer, int length, bool isLatin1)
native "StringBuffer_createStringFromUint16Array";
}