3

Theoretical question, if for e.g. I have a big object called Order and it has a tons on props: strings, numbers, arrays, nested objects.

I have a function:

function removeShipment(order) {
    order.shipment.forEach(
        // remove shipment action
    );
}

Which mean I access only one prop (shipment), but send a big object.

From perspective of garbage collection and performance is there a difference, between pass Order and pass Order.shipment?

Because object passed by reference, and don't actually copy Order into variable.

ibrahim mahrir
  • 31,174
  • 5
  • 48
  • 73

3 Answers3

1

I was wondering this myself. I decided to test it. Here is my test code:

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;

Then I called the three functions like this:

x << 1;
x ^= passObject(object);
x << 1;
x ^= passRawValues(a, b, c);
x << 1;
x ^= passArray(array);

The reason it does the bit shifting and XORing is that without it, the function call was optimized away entirely by some JS runtimes. By storing the result of the function, I forced the runtime to actually do the function call.

Results

In Webkit and Chromium, passing an object and passing an array were about the same speed, and passing raw values was a little bit slower. Firefox showed about the same performance ratio but I'm not sure that I trust the results since it was literally ten times faster than Chromium.

Here is a link to my my test case on MeasureThat. In case the link doesn't work: it's the same code as above.

Here's a screenshot of the run results (in Chromium on an M1 Macbook Air): staple diagram showing the iterations per second About 5 million ops/s in Chromium for passing an object, versus about 3.7 million for passing a trio of primitive values.

Explanation

So why is that? Well, JavaScript strictly uses pass-by-value semantics. But when you pass an object to a function, the value that you're passing isn't actually the object itself, but rather a pointer to the object. So the variable storing the pointer gets duplicated, but the contents of what it points to does not. This is also why you can have a function that takes an object and alters its properties and that change will happen outside the function as well, but if you reassign the object, the outside scope will still reference the old object.

For this reason, the size of the passed object is largely irrelevant for performance. If the var object = {...} above is changed to contain a bunch of other data, the operations per second achieved when passing it to the function remains exactly the same, because the only thing changing is the amount of data in the block of memory storing the object. The value being passed to the function isn't bigger just because the object is bigger.

Simon Lundberg
  • 1,413
  • 2
  • 11
  • 23
0

As ibrahim mahrir stated in a comment-- though I don't know why they didn't post an answer, because OPs are incentivised to pick a "best answer" & the sole, bewildering response was therefore chosen-- there is no practical performance difference between passing order to your removeShipment method, or passing order.shipment

This is because JavaScript functions are "pass-by-value" for primitive types, like number and boolean, and it uses something known as "call-by-sharing" for passing copies of references for Objects (like your order and assumedly your Array of shipments). The entire object is not copied when passed as a parameter, just a copy of a reference to it in memory. Either approach, passing order or order.shipments, is effectively identical.

I did write a couple timing tests for this, but the actual difference is so small that it's exceptionally difficult to write a test that even properly measures it. I'll include my code at the end for completeness' sake, but from my limited testing in Firefox & Chrome, they were practically identical, as expected.

For another question / answer in the same vein as yours (as well as a great video on why "Micro-benchmarking" often doesn't produce correct results) that corroborates what I wrote, see: does size of argument in a javascript function affects its performance?

See this answer regarding the implications of "call-by-sharing" Is JavaScript a pass-by-reference or pass-by-value language?


You didn's specify what, "remove shipment action" actually "means" in practice. You could just do testOrder.shipments = [] if you just wanted to "remove all shipments" from the order object. They'd be garbage collected at some point after this if nothing else can reach them. I'm just going to iterate through each & perform an addition operation as a stub, as I'm afraid otherwise everything would just be optimised out.

// "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()
Lovethenakedgun
  • 731
  • 6
  • 22
-3

Created a simple test here https://jsperf.com/passing-object-vs-passing-raw-value

Test results:

  • in Chrome passing object is ~7% slower that passing raw value
  • in Firefox passing object is ~15% slower that passing raw value
  • in IE11 passing object is ~10% slower that passing raw value

This is syntetic test for passing only one variable, so in other cases results may differ

Andrii Muzalevskyi
  • 3,261
  • 16
  • 20
  • This test does not exist anymore. I created one, to try : https://jsperf.com/passing-object-vs-passing-raw-value , and I got a negligible ~1% difference. – Diego Dec 15 '19 at 03:09
  • How is it that both of you wrote code as a test & didn't include it in an answer? Isn't that common SO practice to actually include something relevant like that "within the answer" rather than relying on purely linking to an external resource? – Lovethenakedgun Mar 29 '22 at 06:52