javascript - 浏览器是否总是将 Javascript 中的字符串和数字视为不可变的?

标签 javascript performance memory-management garbage-collection v8

在 Javscript 中,浏览器运行时解释器总是将字符串和数字视为不可变的吗?

当然,在可证明无害的情况下,他们会优化并将它们视为可变的。如果不是,为什么不呢?

例如,考虑简单的 for 循环。

for (let i = 0; i < 1000000000000; i++) {
 console.log(i)
}

由于 i 变量的作用域为循环,并且循环中的任何代码都不需要 i 变量的“旧值”,因此浏览器简单地增加数字是有意义的符号 i 指向每次迭代。否则,新的内存字节流将被 i 的新值占用,这是无法想象的(“有人可能需要 i 的那些旧值!”) ).我们将在 for 循环(在内存中创建 i 的新值)和垃圾收集器(杀死 i 的所有旧值)之间进行不必要的竞争,这循环通常会获胜,并且我们会出现堆栈溢出。

哦,事情就是这样,不是吗。如果是这样,为什么浏览器在以其他方式优化代码时如此聪明,却如此愚蠢?

字符串也有类似的情况。请考虑以下事项。

{
   let completeWorks = "This string dictates the complete works of William Shakespeare. To be or not to be that is the question whether it is nobler in the mind..."
   completeWorks += "The End."  // <-- what happens here?
}

字符串 completeWorks 是 block 范围的,并且可以证明只存在于这个 block 中。因此,当浏览器遇到指令 completeWorks += "The End" 时,它肯定会改变 completeWorks。如果不是,为什么不呢?可能他们不这样做的一个很好的理由,我想学习它。

最佳答案

(此处为 V8 开发人员——因此我对其他浏览器/引擎知之甚少。)

对此没有简单的答案;实现很复杂。

在 V8 中,字符串始终是不可变的(在创建之后)。一个原因是在堆上分配对象时,对象后面通常没有可用空间,所以我们不能只是将字符附加到现有字符串。另一个原因是,跟踪哪些字符串可以安全地发生变异会增加大量的复杂性(除了一些更容易检测的小众情况,但如果只支持这些情况,那么该机制将提供更少的值(value))。

V8 确实有一些巧妙的字符串操作技巧:当您使用较大字符串的子字符串时,不会复制任何字符;新字符串只是一个引用,上面写着“我是那边另一个字符串的长度 X 的一部分,从索引 Y 开始”。同样,当连接两个字符串时,如您的 completeWorks 示例,新字符串是一个引用,表示“我是其他两个字符串的连接”。 (为了完整起见,我会提到有最低字符数,低于该值这些技巧将不适用,因为简单地复制字符至少同样有效。)

数字比字符串对性能更敏感,也更容易处理。一般来说,堆分配的数字总是不可变的;但这还没有结束。 V8 大量使用“Smis”(“小整数”)的特殊表示形式,因为 JavaScript 程序中的许多数字都属于这个桶。 Smis 不是堆对象;创建一个新的和修改一个一样便宜,而且实际上是无法区分的(就像 C++ 中的 int)。对于超出 Smi 范围的数字,优化编译器还会执行“转义分析”并可以“拆箱”非转义数字,这意味着将它们保存在 CPU 寄存器中(作为普通的 64 位 float )而不是在堆上分配它们首先,这又比改变其他不可变的堆对象更好。对于存储在对象属性中的数字的特殊情况,V8 也(在某些情况下)使用可变存储。

因此,您的问题的答案都是"is"(例如,当生成未优化的代码时,V8 不会花时间执行分析,因此代码必须保守地假设某处需要任何旧值),以及“不”(对于优化编译器,您的直觉是正确的,这应该是可以避免的;但这仍然并不意味着分配在堆上的任何数字都会在那里发生突变)。

Since the i variable is scoped to the loop

JavaScript 中的作用域很复杂。首先,没有 int i。现在考虑一下:

for (var i = 0; i < 100; i++) {
  // Use i here, or don't.
}
console.log(i);  // Prints "100".

如果你的意思是 let i,那么当然,你会有一个 block 范围的变量。在此示例中,性能将相同。

We will have an unnecessary race between the for loop (creating new values of i in memory) and the garbage collector (killing off all the old values of i), which the loop will generally win

没有。垃圾收集器具有高度自适应性,特别是当发生更多分配时它会做更多工作。没有办法“超越”它。如果需要,当垃圾收集器试图找到可以释放的内存时,程序会停止执行。

and we will have a stack overflow.

不,堆栈溢出与对象分配、垃圾回收或一般的堆内存无关。

关于javascript - 浏览器是否总是将 Javascript 中的字符串和数字视为不可变的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61627573/

相关文章:

c++ - 当一个类的所有实例都被销毁时释放内存

javascript - jQuery Slider - 如何根据另一个 slider 制作一组具有最大值的 slider ?

javascript - Quickblox Javascript SDK + Angular + webRTC - 无法读取未定义的属性 'send'

c# - 我应该压缩内存中的 C# 对象以获得更好的性能吗?

c - 如果 `malloc(0)` 返回一个非空指针,我可以将它传递给 `free` 吗?

memory-management - 在某些情况下,Ada 会自动释放内存吗?

javascript - HTML5 视频上方无法选择的文本

javascript - 正确构建knockoutjs View 模型和服务器调用

.net - WCF的内部限制是什么

c# - XPath 与反序列化 : which one is better in performance for read operations