JavaScript中作为参数传递对象的性能表现

3
理论问题,例如我有一个名为Order的大对象,它有许多属性:字符串、数字、数组、嵌套对象等。
我有一个函数:
function removeShipment(order) {
    order.shipment.forEach(
        // remove shipment action
    );
}

这意味着我只访问了一个属性(shipment),但是发送了一个大对象。

从垃圾回收和性能的角度来看,传递Order和传递Order.shipment之间是否有区别?

因为对象是通过引用传递的,并没有将Order实际复制到变量中。


3
对象是通过引用传递而不是值传递!将对象传递给函数和传递一个数字一样快! - ibrahim mahrir
2
你唯一可以进行的优化就是提高代码的清晰度! - ibrahim mahrir
3个回答

1

我自己也在想这个问题。于是我决定进行测试。以下是我的测试代码:

var a = "Here's a string value";
var b = 5; // and a number
var c = false;

var object = {
  a, b, c
}

var array = [
  a, b, c
];

var passObject = (obj) => {
  return obj.a.length + obj.b * obj.c ? 2 : 1;
}

var passRawValues = (val_a, val_b, val_c) => {
  return val_a.length + val_b * val_c ? 2 : 1;
}

var passArray = (arr) => {
  return arr[0].length + arr[1] * arr[2] ? 2 : 1;
}

var x = 0;

然后我像这样调用了三个函数:

x << 1;
x ^= passObject(object);

x << 1;
x ^= passRawValues(a, b, c);

x << 1;
x ^= passArray(array);

它进行位移和异或的原因是,如果没有这些操作,一些JS运行时会完全优化掉函数调用。通过存储函数结果,我强制运行时实际执行函数调用。

结果

在Webkit和Chromium中,传递对象和传递数组的速度大致相同,传递原始值稍微慢一些。Firefox显示了大致相同的性能比例,但我不确定是否信任结果,因为它比Chromium快了十倍。

这里是我的MeasureThat上的测试用例链接。如果链接无法使用:它与上面的代码相同。

这是运行结果的屏幕截图(在M1 Macbook Air上的Chromium中): staple diagram showing the iterations per second 在Chromium中,传递对象的操作速度约为500万次/秒,而传递三个基本值的速度约为370万次/秒。

解释

那么为什么会这样呢?嗯,JavaScript 严格使用传值语义。但是当你将一个对象传递给函数时,你传递的值实际上并不是对象本身,而是指向对象的指针。因此,存储指针的变量被复制了,但它所指向的内容并没有被复制。这也是为什么你可以有一个函数,它接受一个对象并改变它的属性,这个改变也会发生在函数外部,但如果你重新分配对象,外部作用域仍然引用旧对象。

因此,传递的对象的大小对性能来说基本无关紧要。如果上面的 var object = {...} 被更改以包含大量其他数据,则将其传递给函数时每秒执行的操作数量仍然完全相同,因为唯一变化的是存储对象的内存块中的数据量。传递给函数的值并不会因为对象变得更大而变得更大。


0

正如ibrahim mahrir在评论中所述 - 虽然我不知道为什么他们没有发布答案,因为OP被激励选择“最佳答案”,而唯一的、令人困惑的回复因此被选择了 - 在将order传递给您的removeShipment方法或将order.shipment传递给它之间不存在实际性能差异。

这是因为JavaScript函数对于原始类型(如numberboolean)是“按值传递”的,并且对于传递对象的副本使用称为“共享调用”的东西(例如您的order和假定的shipments数组)。当作为参数传递时,整个对象并未被复制,只是内存中对其的引用的副本。无论采用哪种方法,传递order还是order.shipments,效果都是相同的。

我确实为此编写了一些计时测试,但实际差异非常小,以至于很难编写一个甚至可以正确测量它的测试。出于完整性考虑,我将在结尾处包含我的代码,但从我在Firefox和Chrome中进行的有限测试来看,它们几乎是相同的,这是预期的。

针对与您类似的另一个问题/答案(以及一段关于为什么“微基准测试”通常不能产生正确结果的精彩视频),证实了我所写的内容,请参见:JavaScript函数中参数大小是否影响其性能?

有关“共享调用”的含义,请参见此答案JavaScript是按引用传递还是按值传递语言?


您并没有具体说明实际上“删除运输操作”是什么意思。如果您只想从订单对象中“删除所有装运”,则可以使用testOrder.shipments = []。如果没有其他变量引用这些装运记录,它们将在此后被垃圾回收。因为我担心否则所有东西都会被优化掉,所以我将迭代每个记录并执行添加操作。

// "num" between 0 inclusive & 26 exclusive
function letter(num)
{
    return String.fromCharCode(num + 65)
}

// Ships have a 3-letter name & a random value between 0 & 1
function getShipment() {
    return { "name": "Ship", "val": Math.random() }
}

// "order" has 100 "Shipments" 
// As well as 676 other named object properties with random "result" values
// e.g. order.AE => Object { result: 14.9815045239037 }
function getOrder() {
    var order = {}
    for (var i = 0; i < 26; i++) 
    for (var j = 0; j < 26; j++) {
        order[letter(i) + letter(j)] = { "result": (i+j) * Math.random() } 
    }
    order.shipments = Array.from({length: 100}).map(getShipment)
    return order
}

function removeShipmentOrder(order) {
    order.shipments.forEach(s => s.val++);
}

function removeShipmentList(shipmentList) {
    shipmentList.forEach(s => s.val++);
}

// Timing tests

var testOrder = getOrder();
console.time()
for(var i = 0; i < 1000000; i++)
    removeShipmentOrder(testOrder)
console.timeEnd()

// Break in-between tests; 
// Running them back-to-back, the second test always took longer.
// I assume it's actually due to some kind of compiler optimisation

var testOrder = getOrder();
console.time()
for(var i = 0; i < 1000000; i++)
    removeShipmentList(testOrder.shipments)
console.timeEnd()

-3

在这里创建了一个简单的测试 https://jsperf.com/passing-object-vs-passing-raw-value

测试结果:

  • 在 Chrome 中,传递对象比传递原始值慢约7%
  • 在 Firefox 中,传递对象比传递原始值慢约15%
  • 在 IE11 中,传递对象比传递原始值慢约10%

这是针对仅传递一个变量的合成测试,因此在其他情况下结果可能会有所不同


这个测试已经不存在了。我创建了一个测试,试图比较: https://jsperf.com/passing-object-vs-passing-raw-value ,结果得到了一个可以忽略的差异,大约只有1%。 - Diego
你们两个怎么会写代码作为测试,却没有将其包含在答案中呢?难道常见的 SO 实践不是将相关内容“直接包含在答案中”,而不是仅依赖于链接到外部资源吗? - Lovethenakedgun

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接