mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
perf(buffer): optimize Buffer.from(array) by using setFromArrayLike directly (#26135)
## Summary Optimizes `Buffer.from(array)` by bypassing `JSC::construct()` overhead (~30ns) and leveraging JSC's internal array optimizations. ## Changes - For JSArray inputs, directly use `setFromArrayLike()` which internally detects array indexing types (Int32Shape/DoubleShape) and uses bulk copy operations (`copyFromInt32ShapeArray`/`copyFromDoubleShapeArray`) - Array-like objects and iterables continue to use the existing slow path - Added mitata benchmark for measuring performance ## Benchmark Results | Test | Before | After | Improvement | |------|--------|-------|-------------| | Buffer.from(int32[8]) | ~85ns | ~43ns | ~50% faster | | Buffer.from(int32[64]) | ~207ns | ~120ns | ~42% faster | | Buffer.from(int32[1024]) | ~1.85μs | ~1.32μs | ~29% faster | | Buffer.from(double[8]) | ~86ns | ~50ns | ~42% faster | | Buffer.from(double[64]) | ~212ns | ~151ns | ~29% faster | Bun is now faster than Node.js for these operations. ## Test All 449 buffer tests pass.
This commit is contained in:
38
bench/snippets/buffer-from-array.mjs
Normal file
38
bench/snippets/buffer-from-array.mjs
Normal file
@@ -0,0 +1,38 @@
|
||||
// @runtime bun,node
|
||||
import { Buffer } from "node:buffer";
|
||||
import { bench, group, run } from "../runner.mjs";
|
||||
|
||||
// Small arrays (common case)
|
||||
const int32Array8 = [1, 2, 3, 4, 5, 6, 7, 8];
|
||||
const doubleArray8 = [1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5];
|
||||
|
||||
// Medium arrays
|
||||
const int32Array64 = Array.from({ length: 64 }, (_, i) => i % 256);
|
||||
const doubleArray64 = Array.from({ length: 64 }, (_, i) => i + 0.5);
|
||||
|
||||
// Large arrays
|
||||
const int32Array1024 = Array.from({ length: 1024 }, (_, i) => i % 256);
|
||||
|
||||
// Array-like objects (fallback path)
|
||||
const arrayLike8 = { 0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, length: 8 };
|
||||
|
||||
// Empty array
|
||||
const emptyArray = [];
|
||||
|
||||
group("Buffer.from(array) - Int32 arrays", () => {
|
||||
bench("Buffer.from(int32[8])", () => Buffer.from(int32Array8));
|
||||
bench("Buffer.from(int32[64])", () => Buffer.from(int32Array64));
|
||||
bench("Buffer.from(int32[1024])", () => Buffer.from(int32Array1024));
|
||||
});
|
||||
|
||||
group("Buffer.from(array) - Double arrays", () => {
|
||||
bench("Buffer.from(double[8])", () => Buffer.from(doubleArray8));
|
||||
bench("Buffer.from(double[64])", () => Buffer.from(doubleArray64));
|
||||
});
|
||||
|
||||
group("Buffer.from(array) - Edge cases", () => {
|
||||
bench("Buffer.from([])", () => Buffer.from(emptyArray));
|
||||
bench("Buffer.from(arrayLike[8])", () => Buffer.from(arrayLike8));
|
||||
});
|
||||
|
||||
await run();
|
||||
@@ -77,6 +77,8 @@
|
||||
|
||||
// #include <JavaScriptCore/JSTypedArrayViewPrototype.h>
|
||||
#include <JavaScriptCore/JSArrayBufferViewInlines.h>
|
||||
#include <JavaScriptCore/JSArray.h>
|
||||
#include <JavaScriptCore/JSGenericTypedArrayViewInlines.h>
|
||||
|
||||
extern "C" bool Bun__Node__ZeroFillBuffers;
|
||||
|
||||
@@ -2859,11 +2861,38 @@ EncodedJSValue constructBufferFromArray(JSC::ThrowScope& throwScope, JSGlobalObj
|
||||
{
|
||||
auto* globalObject = defaultGlobalObject(lexicalGlobalObject);
|
||||
|
||||
// FIXME: Further optimization possible by calling copyFromInt32ShapeArray/copyFromDoubleShapeArray.
|
||||
if (JSArray* array = jsDynamicCast<JSArray*>(arrayValue)) {
|
||||
if (isJSArray(array)) {
|
||||
size_t length = array->length();
|
||||
|
||||
// Empty array case
|
||||
if (length == 0)
|
||||
RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(createEmptyBuffer(lexicalGlobalObject)));
|
||||
|
||||
// Allocate uninitialized buffer
|
||||
auto* uint8Array = createUninitializedBuffer(lexicalGlobalObject, length);
|
||||
RETURN_IF_EXCEPTION(throwScope, {});
|
||||
if (!uint8Array) [[unlikely]] {
|
||||
throwOutOfMemoryError(lexicalGlobalObject, throwScope);
|
||||
return {};
|
||||
}
|
||||
|
||||
// setFromArrayLike internally detects Int32Shape/DoubleShape and uses
|
||||
// copyFromInt32ShapeArray/copyFromDoubleShapeArray for bulk copy
|
||||
bool success = uint8Array->setFromArrayLike(lexicalGlobalObject, 0, array, 0, length);
|
||||
RETURN_IF_EXCEPTION(throwScope, {});
|
||||
if (!success)
|
||||
return {};
|
||||
RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(uint8Array));
|
||||
}
|
||||
}
|
||||
|
||||
// Slow path: array-like objects, iterables
|
||||
auto* constructor = lexicalGlobalObject->m_typedArrayUint8.constructor(lexicalGlobalObject);
|
||||
MarkedArgumentBuffer argsBuffer;
|
||||
argsBuffer.append(arrayValue);
|
||||
JSValue target = globalObject->JSBufferConstructor();
|
||||
// TODO: I wish we could avoid this - it adds ~30ns of overhead just using JSC::construct.
|
||||
auto* object = JSC::construct(lexicalGlobalObject, constructor, target, argsBuffer, "Buffer failed to construct"_s);
|
||||
RETURN_IF_EXCEPTION(throwScope, {});
|
||||
RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(object));
|
||||
|
||||
Reference in New Issue
Block a user