質問

It is to my knowledge that with Javascript when you delete an entry on a object, at least with chrome it puts the object into "dictionary mode" or "slow mode"

Example:

var user = { name: 'connor', sex: 'male' }; 
// user is in "fast mode"

delete user.sex;
// user is in ("slow" or "dictionary") mode 

When can this be beneficial and when can it be detrimental?

A specific case to go by would be, I have an object and when the application starts the object is empty, but as the code is run and the application memory builds up it can potentially become very large and the object will never decrease in size until the application closes by which it will not exist.

Also is there any semantics around this mode?

役に立ちましたか?

解決

It is to my knowledge that with Javascript when you delete an entry on a object, at least with chrome it puts the object into "dictionary mode" or "slow mode"

That isn't a JavaScript thing; that's an implementation characteristic of the V8 engine inside Chrome. This thread on the V8 users's list discusses this:

[ZenWolf] ...Does using the JavaScript "delete" keyword to delete a property from an object effect (sic) how v8 will optimize the object? ...

[Sven Panne] ...Deleting a property results in going to "slow mode", i.e. using a dictionary for the object's properties. So as a general rule of thumb, using 'delete' makes thing slower...

See also this post on the V8 blog, which goes into the three (yes, three) kinds of property modes V8 objects have.

But briefly, dictionary mode is there to deal with the case where the programmer has used an object as a grab bag of name/value pairs (like a Map) rather than as a structure whose shape (what properties it has and such) doesn't change over time. The programmer may be doing that due to personal preference or, back when the question was asked, because JavaScript didn't have a standard Map type until 2015. The requirements for structured objects vs. grab bags of name/value pairs are different, but V8 has to deal with the fact that an object may be used as one or the other. Using delete on an object is one signal that the object may be being used as a grab bag and should be optimized differently from an object that doesn't change shape over time.

Here are a couple of benchmarks designed specifically for V8 to show the difference, but see the notes after them.

This first one shows how reading properties repeatedly from the object is faster in "structure mode" (what the devs above are calling "fast") than in "dictionary mode (what the devs above are calling "slow"):

const btn = document.querySelector("input[type=button]");

function log(...msgs) {
    console.log(...msgs);
    for (const msg of msgs) {
        const p = document.createElement("pre");
        p.textContent = msg;
        document.body.appendChild(p);
    }
}

function checkProps(obj) {
    for (let n = 0; n < 100; ++n) {
        if (obj.name !== "connor" || obj.anotherProp !== 0) {
            throw new Error(`Error in test, check test setup`);
        }
    }
}

function runTest() {
    const suite = new Benchmark.Suite();

    const initialProps = {
        name: "connor",
        sex: "male",
        anotherProp: 0,
        andAnother: 0,
        somethingElse: 0,
    };

    suite
        .add("struct mode", () => {
            const structMode = { ...initialProps };
            structMode.sex = undefined; // Not the same as `delete` of course
            checkProps(structMode);
        })
        .add("dictionary mode", () => {
            const dictionaryMode = { ...initialProps };
            delete dictionaryMode.sex;
            checkProps(dictionaryMode);
        })
        .on("error", (e) => {
            log("Error", e.stack ?? String(e));
        })
        .on("cycle", () => {
            log("...");
        })
        .on("complete", function () {
            const results = Array.from(this);
            let longest = 0;
            for (const { name } of results) {
                longest = Math.max(longest, name.length);
            }
            for (const { name, hz } of results) {
                log(`${name.padEnd(longest)}: ${hz} cycles/second`);
            }
            log(
                "Done, fastest is " +
                    this.filter("fastest").map("name").join(", then ")
            );
            btn.disabled = false;
        })
        .run({ async: true });
}

btn.addEventListener("click", () => {
    log("Starting test, please be patient...");
    btn.disabled = true;
    setTimeout(runTest, 100);
});
pre {
    margin: 2px 0;
    white-space: pre-wrap;
}
<script src="https://unpkg.com/lodash@4.17.21/lodash.js"></script>
<script src="https://unpkg.com/benchmark@2.1.4/benchmark.js"></script>

<input type="button" value="Run Test">

In this one, you can see that if you're often creating objects and then adding properties to them, it's faster if the object is in dictionary mode than if it's in struct mode:

const btn = document.querySelector("input[type=button]");

function log(...msgs) {
    console.log(...msgs);
    for (const msg of msgs) {
        const p = document.createElement("pre");
        p.textContent = msg;
        document.body.appendChild(p);
    }
}

function testOperation(obj) {
    const n = obj.nextProp++;
    const nextName = `prop${n}`;
    obj[nextName] = n;
}

function runTest() {
    const suite = new Benchmark.Suite();

    const initialProps = {
        unneeded: "x",
        nextProp: 2,
        prop0: 0,
        prop1: 1,
    };

    suite
        .add("struct mode", () => {
            const structMode = { ...initialProps };
            structMode.unneeded = undefined; // Not the same as `delete`
            testOperation(structMode);
        })
        .add("dictionary mode", () => {
            const dictionaryMode = { ...initialProps };
            delete dictionaryMode.unneeded;
            testOperation(dictionaryMode);
        })
        .on("error", (e) => {
            log("Error", e.stack ?? String(e));
        })
        .on("cycle", () => {
            log("...");
        })
        .on("complete", function () {
            const results = Array.from(this);
            let longest = 0;
            for (const { name } of results) {
                longest = Math.max(longest, name.length);
            }
            for (const { name, hz } of results) {
                log(`${name.padEnd(longest)}: ${hz} cycles/second`);
            }
            log(
                "Done, fastest is " +
                    this.filter("fastest").map("name").join(", then ")
            );
            btn.disabled = false;
        })
        .run({ async: true });
}

btn.addEventListener("click", () => {
    log("Starting test, please be patient...");
    btn.disabled = true;
    setTimeout(runTest, 100);
});
pre {
    margin: 2px 0;
    white-space: pre-wrap;
}
<script src="https://unpkg.com/lodash@4.17.21/lodash.js"></script>
<script src="https://unpkg.com/benchmark@2.1.4/benchmark.js"></script>

<input type="button" value="Run Test">

So in the first, putting the object in dictionary mode slowed down the kind of property access that that micro-benchmark does. But in the second, putting the object in dictionary mode sped up the kind of operations that benchmark did. So "slow" and "fast" are misnomers. More more examples of the kinds of operations each of them does more or less well, see this answer by jmrk.

As always, beware of micro-benchmarks, you can easily mislead yourself by testing something different from what your actual code will do / what you mean to test. (For example, my first version of the delete benchmark above showed essentially no speed difference, because it happened to run into a specific optimization V8 has in place — something that was kindly pointed out by jmrk. [The optimization is that if you delete the last property you added to the object, it doesn't go into dictionary mode.]) Prefer instead to test your actual code and worry about performance problems if and when you have a specific performance problem to worry about — avoid premature optimization.

Also note that these benchmarks were written specifically for V8 based on information from the linked thread, blog post, and comments by jmrk. SpiderMonkey (Firefox) or JavaScriptCore (Safari, or any browser on iOS) may behave differently.

If you're writing code to be used in web browsers, optimizing for a specific engine tends to be a waste of time unless you're facing a specific, real-world performance problem on that engine. (And if you are, once you've dealt with that, make sure those changes don't mess up the other engines!)

If you were writing for NodeJS, Deno, or other runtimes that specifically use V8, then of course optimizing for V8 is fine (although the usual rules about premature optimization still apply).

When can this be beneficial and when can it be detrimental?

Simplifying it a bit, dictionary mode can be beneficial when the object's properties are fluid (changing), not fixed. (But in that case, consider using Map instead.) Struct mode is beneficial when an object's properties are fixed (or largely fixed).

A specific case to go by would be, I have an object and when the application starts the object is empty, but as the code is run and the application memory builds up it can potentially become very large and the object will never decrease in size until the application closes by which it will not exist.

It will depend on the proportion of property reads to property additions. See you can see in that answer by jmrk, adding a property to an object is faster in dictionary mode than struct mode (as is the first access to the property), but repeatedly accessing an existing property is faster in struct mode.

That said, again, worry about a performance problem when you have a performance problem to worry about. :-)

Also is there any semantics around this mode?

No. The semantics of JavaScript are defined by the specification, which doesn't dictate how objects are implemented provided their behavior matches the semantics of the spec; the spec doesn't address performance basically at all. V8 implements objects by generating dynamic classes on-the-fly and compiling them to machine code, but falling back to "slow" (dictionary) mode when you remove properties from them. (If you add properties, the much more common operation as Sven Panne said in the quote above, it dynamically creates a derived class, which doesn't slow things down.) But other engines are free to implement them as hash maps, or linked lists of properties, or anything else.

他のヒント

Delete makes things slower, yes, but it would be even slower if V8 would try to keep the object in fast mode even when you are changing its shape all the time. Objects must have a fixed set of properties so that they can be statically loaded at fixed offsets (just like fields of C++ or Java objects). If you delete and add properties randomly (I.E. changing the shape of the object), this static loading at fixed offsets cannot be done.

So it's basically a very sensitive heuristic: if you call delete even once, V8 assumes you are going to be changing the object's shape even in the future and gives up on it - even if in reality you are only going to call delete just once and have the shape be unchanged after that.

The benefit is that doing a hash table lookup is much faster than re-calculating the hidden class (and re-compiling all functions based on the old hidden class) every time the shape is changed.

this article has some answers about how google's v8 handles object properties

the article confirms that there are such things as slow and fast properties

my guess is that if your property names change frequently the "Map" is a better way to go.

https://v8.dev/blog/fast-properties

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top