【问题标题】:javascript loop (for statement) slows down after 2.1 billion iteration?javascript循环(for语句)在21亿次迭代后变慢?
【发布时间】:2020-08-07 23:33:04
【问题描述】:

我试图对 javascript 和 .net 核心进行基准测试,以便选择一个服务器端框架来提供一些需要迭代大型数组的特定 RESTful 服务(大约 21 亿)。在编写一个简单的代码时,我意识到节点在特定数字迭代后有奇怪的行为。我在多个平台上重复并达到了相同的结果。测试的平台是:

  • macOS catalina (nodeJS v.12.18) intel core i9 4ghz 6 core
  • linux centos 7 (nodeJS v.12.18) vm intel core i9 4ghz 2 core
  • google chrome 版本 84.0.4147.105(官方版本)(64 位)
  • Mozilla firefox 78.2 版

running video shows surprisingly increase process time about two times from 300ms to 600ms

示例代码:

1。节点:

var cnt = 0;
var logPeriod=100000000;
var max=10000000000;
for (let i = 0; i < max; i++) {
  if (i % logPeriod === 0) {
    // var end = Date.now();
    if (i !== 0) {
      console.timeEnd(cnt*logPeriod, i);
      cnt++;
    }
    console.time(cnt*logPeriod);
  }
}

2.浏览器

<!DOCTYPE html>
<html>
  <head>
    <script>
      function doloop() {
        var cnt = 0;
        var logPeriod = 100000000;
        var max = 10000000000;
        for (let i = 0; i < max; i++) {
          if (i % logPeriod === 0) {
            // var end = Date.now();
            if (i !== 0) {
              console.timeEnd(cnt * logPeriod, i);
              cnt++;
            }
            console.time(cnt * logPeriod);
          }
        }
      }
    </script>
  </head>
  <body>
    <button onclick="doloop()">doloop</button>
  </body>
</html>

【问题讨论】:

  • 可能cnt*logPeriod 最终会超出真正的整数范围(31 位)并转而使用更大整数的浮点表示,因此速度会变慢。
  • 等等,您的应用程序真的有一个用例,您的服务器需要迭代一个包含 21 亿个条目的数组?
  • 好的。是的。然而,在幕后执行“引擎”时,为了提高效率,在适当的位范围内使用规范类型的整数。这取决于引擎的实现。因此,这很可能是 OP 环境变慢的原因。
  • @GetSet:你在现场;除了 cnt*logPeriodi 本身超出 int32 范围之外。
  • @AhadRafatTalebi:这是意料之中的。在浮点运算总是比整数运算慢的意义上,操作系统和 CPU 无关紧要。只有差异的大小可能取决于硬件(我在我的机器上看到大约 4 倍:120 对 540 毫秒)。它是否是 64 位操作系统也无关紧要:“在内部使用 int32”技巧不能扩展到 int64,因为可能的 int32 值是 double 值的子集,但 int64 值不是:它们将是 太精确了,所以如果引擎使用它们,那将是一个错误。这很棘手:-)

标签: javascript for-loop v8 slowdown


【解决方案1】:

V8 开发者在这里。

V8 的优化编译器生成的代码尽可能使用纯 32 位整数作为数字。一旦一个数字超过 int32 范围(或精度要求,即当它需要保存小数值时),那么这种优化的代码将被丢弃(或从一开始就不会生成)并使用 64 位双精度值,如 JavaScript 规范需要。算术运算(甚至像 i++ 这样简单的运算)在 64 位双精度数上比在 32 位整数上慢,这正是硬件所做的。

在行为方面,这种内部差异是无法观察到的:数字的行为总是就好像它们是 64 位双精度数。但这并不意味着引擎实际上总是在底层使用 64 位双精度数:正如您在此处看到的,当引擎可以在内部不使用 32 位整数时,会有显着的性能优势。

为需要迭代大型数组(约 21 亿)的 restful 服务选择 [JavaScript 或 .net]

这是一个简单的决定:使用 .net。 V8(以及 Node)不允许您创建包含 21 亿个元素的数组,因为每个对象的大小限制远低于此。当然,var a = new Array(2_100_000_000) 会评估得很好,但那是因为它实际上并没有分配所有的内存。开始填充元素并在一段时间后观察它崩溃:-)

如果您的实际阵列毕竟不会那么大,那么请定义一个更接近您实际工作负载的基准,因为它的结果将更具代表性,因此对您的决策更有用。

【讨论】:

  • 很好地描述了 v8 结构。你是对的,但作为最后的尝试,你认为是否可以在这种情况下使用新功能BigInt
  • 如何增加每个对象的大小限制? node --stack-size=100000000000000000 --max-old-space-size=65536 -p "Array(2_100_000_000).fill(null).length" 没有帮助。我需要自定义构建吗?我应该更改哪个常量?
  • 有趣的是,QuickJS默认是没有限制的:qjs -e "Array(2_100_000_000).fill(null)"吃掉31.3G内存后成功。
  • @AhadRafatTalebi:我不明白你到底想在这里使用什么 BigInts;对于循环变量i?你当然可以使用它们;作为一个相当新的功能,它们还没有像数字那样得到尽可能多的优化工作,所以现在 BigInt-heavy 的代码往往比基于 Number 的代码慢。
  • @AlanLiang:你不能增加 V8 的每个对象的大小限制。预计不同的引擎有不同的限制。它们可能是自我强加的任意限制,也可能来自内部实现设计选择。 V8 仅使用 30 位来表示对象的大小,因此每个对象必须适合 1G。我们更喜欢这种方式,因为 (1) 使用更多位会增加元数据开销,以及 (2) 在大多数情况下,作为用户,您甚至不会想要单个对象使用比这更多的内存. FWIW,TypedArrays 可以变得更大。
猜你喜欢
  • 2020-01-05
  • 1970-01-01
  • 2011-08-16
  • 1970-01-01
  • 2021-07-28
  • 1970-01-01
  • 2016-08-25
  • 2013-05-17
  • 1970-01-01
相关资源
最近更新 更多