k6
k6 copied to clipboard
Implement "Bring Your Own Buffer" constructs for `ReadableStream`
Description
As part of our effort to make handling large amounts of data more efficient, k6 has introduced support for the WebAPI ReadableStream construct as part of its k6/experimental/streamsmodule. While it’s already possible to handle binary data using this construct, it is neither the most convenient nor the most efficient way to do so.
To better accommodate binary data, we propose to implement the ReadableStreamBYOBReader and its ReadableByteStreamController and ReadableStreamBYOBRequest counterparts. These components allow binary data to be read from a stream into a user-provided buffer in a zero-copy fashion, improving performance and control.
Practical Benefits:
- Improved Memory Control: This feature allows precise control over how much memory is allocated for reading binary data by reusing user-defined buffers.
- Enhanced Efficiency: We eliminate redundant copies by reading data directly into user-provided buffers, reducing CPU overhead and garbage collection.
- Better Buffering Management: Users can manage the size of the buffers, allowing for optimized reads when processing large files or data streams.
- Explicit Binary Handling API: The BYOB reader offers a clear and explicit API for managing binary data, making it easier for developers to handle low-level data processing tasks.
Usage in practice
To read data from a stream, a user needs to instantiate (define) a stream, get a "reader", and call read on that reader repeatedly until the stream is closed or cancel.
Without BYOB reader
Currently, this is how reading from a stream can be achieved. Whenever .read is called, the value received is a new copy of the chunk produced by the stream.
// ... Some Stream definition ...
// Obtain and lock a reader to the stream
const reader = myPredefinedStream.getReader();
try {
// Read and process each item from the stream
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
console.log(value);
}
} catch (error) {
console.error('Stream reading failed:', error);
} finally {
reader.releaseLock();
}
With BYOB reader
With the BYOB reader, the read method takes as an argument a view of a user-supplied buffer into which data is read directly, bypassing the need for additional memory allocations. The value returned is a view over the same buffer, populated with the new data.
// ... Some Stream definition ...
// Obtain and lock a reader to the stream in BYOB mode
const reader = myPredefinedStream.getReader({ mode: "byob" });
// Predefine a buffer to read data into
let intoBuffer = new Uint8Array(200); // A typed array view over an ArrayBuffer
try {
// Read and process each item from the stream
while (true) {
// Use the buffer to store the read content directly, without internal copies
const { done, value } = await reader.read(intoBuffer);
if (done) {
break;
}
// The returned `value` is a view pointing to the `intoBuffer` containing the new data
console.log(value);
}
} catch (error) {
console.error('Stream reading failed:', error);
} finally {
reader.releaseLock();
}
Suggested Solution (optional)
No response
Already existing or connected issues / PRs (optional)
#2974 #2978 #3666