为何JavaScript中数组预分配比动态push性能更差?
为什么Node.js中预分配数组比push动态添加更慢?
你的测试结果和预期相反,核心原因在于V8引擎对不同数组创建方式的内部优化逻辑差异,具体可以拆解为以下几点:
空洞数组的额外处理开销:
new Array(size)创建的是一个空洞数组(所有元素为empty状态,并非undefined),当你逐个为元素赋值时,V8需要频繁检查元素状态,处理从empty到具体值的类型转换,甚至可能触发数组结构调整(比如从慢数组转为快数组、调整元素存储模式),这会带来额外性能损耗。而push操作是连续向数组末尾添加元素,不会产生空洞,V8可以维持数组的**Fast Elements(快数组)**状态,全程无需额外状态检查。内存初始化策略不同:
new Array(size)仅分配内存空间,但并未初始化元素值;而push在添加元素时,V8会预判元素类型(此处为数字),提前优化内存布局,避免后续类型转换开销。预分配数组的赋值操作相当于在未初始化的内存区域写入数据,V8内存管理器需要做更多校验工作。V8扩容机制的高效性:你可能高估了扩容开销。V8的数组扩容采用预分配冗余空间策略(通常每次扩容到当前容量的1.5-2倍),对于1亿规模的数组,
push的扩容次数仅约27次(从初始容量4开始,每次翻倍至超过1亿),扩容总开销远小于处理空洞数组的额外成本。
测试验证补充
如果修改预分配方式,避免空洞数组,性能会显著提升:
- 先填充默认值再赋值:
let array = new Array(size).fill(0); - 使用TypedArray(固定类型数组,无空洞问题):
let array = new Uint32Array(size);
这两种方式的性能会和push接近甚至更优,因为它们规避了空洞数组的处理开销。
你的测试代码
function testPush(size) { let start = process.hrtime.bigint(); let array = []; for (let i = 0; i < size; i++) { array.push(i); } let end = process.hrtime.bigint(); return Number(end - start) / 1e6; // Convert to milliseconds } function testPreAllocated(size) { let start = process.hrtime.bigint(); let array = new Array(size); // let array = new Uint32Array(size); // let array = Array.from({ length: size }); for (let i = 0; i < size; i++) { array[i] = i; } let end = process.hrtime.bigint(); return Number(end - start) / 1e6; // Convert to milliseconds } function compareArrays() { const size = 1e8; // 100 million console.log(`Testing with array size: ${size}`); console.log("Starting push test..."); let pushTime = testPush(size); console.log(`Push test completed in ${pushTime.toFixed(2)} ms`); console.log("Starting pre-allocated test..."); let preAllocatedTime = testPreAllocated(size); console.log(`Pre-allocated test completed in ${preAllocatedTime.toFixed(2)} ms`); } compareArrays();
测试输出
Testing with array size: 100000000 Starting push test... Push test completed in 1624.93 ms Starting pre-allocated test... Pre-allocated test completed in 4824.86 ms
内容的提问来源于stack exchange,提问作者Mikhael Abdallah
相关产品推荐
相关产品推荐

