JavaScript: V8问题:小整数是否被重用?

JavaScript: V8 question: are small integers reused?

根据this article from V8's blog and ,我们知道小整数直接用指针标记编码成指针。

The "trick" of Smis is that they're not stored as separate objects: when you have an object that refers to a Smi, such as let foo = {smi: 42}, then the value 42 can be smi-encoded and stored directly inside the "foo" object (whereas if the value was 42.5, then the object would store a pointer to a separate "HeapNumber"). But since the object is on the heap, so is the Smi.

这意味着如果我有两个具有相同对象值的不同对象{smi: 42}。 smi 42 应该位于堆上的两个不同的内存位置,因为这两个对象在堆上并且值 42 直接编码到指针中,而不是有额外的存储。

但这与Chrome devtool 的内存分析结果相矛盾。给出这个片段

<body>
    <button id='btn'>btn</button>
    <script>  
    const btn = document.querySelector('#btn')
    function MyObject() {
        this.number = 3.14
        this.smi = 123
        this.undefined = undefined
        this.true = true
        this.false = false
        this.null = null
        this.string = 'foo'
    }
    let obj1
    let obj2
    btn.onclick = () => { 
        obj1 = new MyObject()
        obj2 = new MyObject()
    }
    </script>
</body>

我认为 smi:123 应该位于两个不同的位置,而双 number = 3.14 应该指向同一个数字对象。

但事实并非如此 - smi 位于相同的内存位置,而 double 不在。

The smi 42 should be located in two different memory location on heap since the two objects are on heap and the value 42 is encoded directly into the pointer, instead of having an additional storage.

是的。 SMI 存储两次,一次在每个对象值中。所以不,它们没有被重用,实际上不能重用,因为它们是原始值而不是可引用的东西。

开发工具不显示指针在内存中的位置,而是显示指针指向的位置,在 SMI 的情况下是一个无意义的位置,因为它实际上对值进行了编码。因此,由于两者都编码相同的值,因此它们的“指向空的指针”是相同的。

对@JonasWilms 的精彩回答的补充评论:

如果您真的关心引擎盖下发生的事情,我建议您学习如何使用(本机,而非 DevTools)调试器并检查实际内存。 DevTools 旨在帮助您了解您的应用程序在做什么,虽然这与揭示引擎盖下发生的事情有很多重叠,但它们并不完全相同,并且可能会有某些细节(例如 Smis 的呈现方式)这种差异显示在哪里。我认为堆快照使用raw_address → object ID map,你看到的是将Smis放入这样的map的自然结果。

对于测试用例的简化版本(只是 function MyObject() { this.number = 12.5; this.smi = 23; }),这就是您在内存中看到的内容:

(gdb) x/5xw 0x1f010810aedc
0x1f010810aedc: 0x082c7db1  // pointer to map
                0x08002249  // pointer to properties (empty array)
                0x08002249  // pointer to elements (empty array)
                0x0810af71  // pointer to a HeapNumber
                0x0000002e  // Smi: 23 << 1 == 46 == 0x2e
(gdb) x/5xw 0x1f010810afa4
0x1f010810afa4: 0x082c7db1  // map (same as other object)
                0x08002249  // properties
                0x08002249  // elements
                0x0810afd9  // pointer to a HeapNumber
                0x0000002e  // Smi: 23 << 1

另一方面,HeapNumbers 可以而且确实会被重用。由于另一种机制,我们在此示例中看不到这一点:对象属性使用 mutable HeapNumbers。可变使它们无法共享,在这个玩具示例中,整个方法只会浪费时间和内存;但事实证明,对于典型的使用模式来说,这是一个有益的权衡,因为它允许更新值(特别是从优化代码),而无需每次都分配新的 HeapNumber。
如果你只是有一个 function f() { return 12.5; },它实际上会 return 每次调用时重复使用相同的 HeapNumber。