oven-sh / bun

Incredibly fast JavaScript runtime, bundler, test runner, and package manager – all in one
https://bun.sh
Other
73.33k stars 2.7k forks source link

Object.seal is extremely slow when applying it in big array or object #6360

Open enzoferraribf opened 11 months ago

enzoferraribf commented 11 months ago

I was testing a little program to exemplify different allocation strategies in JS (Node).

Functions have the following code:

function getArrayOfSize(size, defaultValue) {
    const array = [];

    for (let i = 0; i < size; i++) {
        array.push(defaultValue);
    }

    return array;
}
function getOptimizedArrayOfSize(size, defaultValue) {
    const array = new Array(size);

    array.fill(defaultValue);

    Object.seal(array);

    return array;
}

Finally, I'm (naively) testing both strategies with:

console.log("Running with non-optimized version")
console.time('test-nonoptimized')
for (let i = 0; i < 2_000; i++) {
    const array = getArrayOfSize(i, i);
}
console.timeEnd('test-nonoptimized')

console.log("Running with optimized version")
console.time('test-optimized')
for (let i = 0; i < 2_000; i++) {
    const array = getOptimizedArrayOfSize(i, i);
}
console.timeEnd('test-optimized')

Running with node, I get consistent results in my machine: test-nonoptimized: 14.158ms test-optimized: 3.928ms

However, when running with bun, I get very weird results: [11.67ms] test-nonoptimized [423.54ms] test-optimized

When I remove Object.seal(array) from the optimized function, I get better results with bun: [11.11ms] test-nonoptimized [10.27ms] test-optimized

Node stays the same indepedent of Object.seal being present

Bun version: 1.0.4

enzoferraribf commented 11 months ago

Testing a little more, I was able to verify that that behavior only happens if the array is filled somehow. I was able to verify that applying Object.seal in an object with a LOT of properties causes the same weird performance issue.

Changing the function to be something like:

function getOptimizedArrayOfSize(size, defaultValue) {
    const array = new Array(size);

    // array.fill(defaultValue);

    Object.seal(array);

    return array;
}

Yields same results as Node

enzoferraribf commented 11 months ago

More updates, looks like the behavior of Object.seal is different from Bun to Node.

function getOptimizedArrayOfSize(size, defaultValue) {
    const array = new Array(size);

    array.fill(defaultValue);

    Object.seal(array);

    // works in Node, outputs 'offending write'
   // throws TypeError exception in Bun
    array[2] = 'offending write'
    console.log(array[2]) 

    return array;
}

While I think Bun's approach is better, should that deviate from Node's?

MoritzLoewenstein commented 11 months ago

_Unlike Object.freeze(), objects sealed with Object.seal() may have their existing properties changed, as long as they are writable._ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/seal

Would be interesting to test if the behavior is the same in safari.

enzoferraribf commented 11 months ago

Unfortunately I can't test that in Safari, as I only have a windows machine

oLucasRez commented 10 months ago

I was able to test in Safari. Here is the result:

[Debug] test-nonoptimized: 78.632ms [Debug] test-optimized: 1901.186ms

It seems to be something related to Webkit then.

Tested in Safari version: Version 17.1 (19616.2.9.11.7)