Re-write _BufferList to keep an internal Uint8List buffer, that will grow on demand.

This eliminates a lot of the performance issues seen with _BufferList.
We now do either setRange if it's Uint8List, or a fast iteration for any
other list (in case of e.g. CodeUnits).

BUG=

Review URL: https://codereview.chromium.org//14057010

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@21679 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
ajohnsen@google.com 2013-04-18 16:22:16 +00:00
parent dfd51cba6f
commit 6f81bd5427
3 changed files with 54 additions and 121 deletions

View file

@ -5,153 +5,91 @@
part of dart.io;
/**
* Utility class that holds a number of byte buffers and can deliver
* the bytes either one by one or in chunks.
* Utility class that can fast concatenate [List<int>]s of bytes. Use
* [readBytes] to get the final buffer.
*/
class _BufferList {
const int _INIT_SIZE = 1 * 1024;
_BufferList() {
clear();
}
/**
* Adds a new buffer to the list possibly with an offset of the
* first byte of interest. The offset can only be specified if the
* buffer list is empty.
*/
void add(List<int> buffer, [int offset = 0]) {
assert(offset == 0 || _buffers.isEmpty);
_buffers.addLast(buffer);
_length += buffer.length;
if (offset != 0) _index = offset;
}
/** Alias for [add]. */
void write(List<int> buffer, [int offset = 0]) {
add(buffer, offset);
int pow2roundup(int x) {
--x;
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
return x + 1;
}
/**
* Returns the first buffer from the list. This returns the whole
* buffer and does not remove the buffer from the list. Use
* [index] to determine the index of the first byte in the buffer.
* Adds a new buffer to the list.
*/
List<int> get first => _buffers.first;
/**
* Returns the current index of the next byte. This will always be
* an index into the first buffer as when the index is advanced past
* the end of a buffer it is removed from the list.
*/
int get index => _index;
/**
* Peek at the next available byte.
*/
int peek() => _buffers.first[_index];
/**
* Returns the next available byte removing it from the buffers.
*/
int next() {
int value = _buffers.first[_index++];
_length--;
if (_index == _buffers.first.length) {
_buffers.removeFirst();
_index = 0;
void add(List<int> buffer) {
int bufferLength = buffer.length;
int required = _length + bufferLength;
if (_buffer == null) {
int size = pow2roundup(required);
if (size < _INIT_SIZE) size = _INIT_SIZE;
_buffer = new Uint8List(size);
} else if (_buffer.length < required) {
// This will give is a list in the range of 2-4 times larger than
// required.
int size = pow2roundup(required) * 2;
Uint8List newBuffer = new Uint8List(size);
newBuffer.setRange(0, _buffer.length, _buffer);
_buffer = newBuffer;
}
return value;
}
/**
* Read [count] bytes from the buffer list. If the number of bytes
* requested is not available null will be returned.
*/
List<int> readBytes([int count]) {
if (count == null) count = length;
List<int> result;
if (_length == 0) return new Uint8List(0);
if (_length < count) return null;
if (_index == 0 && _buffers.first.length == count) {
result = _buffers.first;
_buffers.removeFirst();
_index = 0;
_length -= count;
return result;
assert(_buffer.length >= required);
if (buffer is Uint8List) {
_buffer.setRange(_length, required, buffer);
} else {
int firstRemaining = _buffers.first.length - _index;
if (firstRemaining >= count) {
result = _buffers.first.sublist(_index, _index + count);
_index += count;
_length -= count;
if (_index == _buffers.first.length) {
_buffers.removeFirst();
_index = 0;
}
return result;
} else {
result = new Uint8List(count);
int remaining = count;
while (remaining > 0) {
int bytesInFirst = _buffers.first.length - _index;
if (bytesInFirst <= remaining) {
int startIndex = count - remaining;
int endIndex = startIndex + bytesInFirst;
result.setRange(startIndex, endIndex, _buffers.first, _index);
_buffers.removeFirst();
_index = 0;
_length -= bytesInFirst;
remaining -= bytesInFirst;
} else {
result.setRange(count - remaining, count, _buffers.first, _index);
_index = remaining;
_length -= remaining;
remaining = 0;
assert(_index < _buffers.first.length);
}
}
return result;
for (int i = 0; i < bufferLength; i++) {
_buffer[_length + i] = buffer[i];
}
}
_length = required;
}
/**
* Remove a number of bytes from the buffer list. Currently the
* number of bytes to remove must be confined to the first buffer.
* Same as [add].
*/
void removeBytes(int count) {
int firstRemaining = first.length - _index;
assert(count <= firstRemaining);
if (count == firstRemaining) {
_buffers.removeFirst();
_index = 0;
} else {
_index += count;
}
_length -= count;
void write(List<int> buffer) {
add(buffer);
}
/**
* Read all the bytes from the buffer list. If it's empty, an empty list
* is returned. A call to [readBytes] will clear the buffer.
*/
List<int> readBytes() {
if (_buffer == null) return new Uint8List(0);
var buffer = new Uint8List.view(_buffer.buffer, 0, _length);
clear();
return buffer;
}
/**
* Returns the total number of bytes remaining in the buffers.
* Returns the total number of bytes in the buffer.
*/
int get length => _length;
/**
* Returns whether the buffer list is empty that is has no bytes
* available.
* Returns whether the buffer list is empty.
*/
bool get isEmpty => _buffers.isEmpty;
bool get isEmpty => _length == 0;
/**
* Clears the content of the buffer list.
*/
void clear() {
_index = 0;
_length = 0;
_buffers = new Queue();
_buffer = null;
}
int _length; // Total number of bytes remaining in the buffers.
Queue<List<int>> _buffers; // List of data buffers.
int _index; // Index of the next byte in the first buffer.
int _length; // Total number of bytes in the buffer.
Uint8List _buffer; // Internal buffer.
}

View file

@ -478,8 +478,7 @@ class _File extends _FileBase implements File {
openRead().listen(
(d) => chunks.add(d),
onDone: () {
var result = chunks.readBytes(chunks.length);
if (result == null) result = <int>[];
var result = chunks.readBytes();
completer.complete(result);
},
onError: (e) {

View file

@ -277,11 +277,7 @@ class _WebSocketProtocolTransformer extends StreamEventTransformer {
sink.add(_buffer.toString());
break;
case _WebSocketMessageType.BINARY:
if (_buffer.length == 0) {
sink.add(const []);
} else {
sink.add(_buffer.readBytes(_buffer.length));
}
sink.add(_buffer.readBytes());
break;
}
_buffer = null;