【问题标题】:JavaScript array iteration directionJavaScript 数组迭代方向
【发布时间】:2016-02-13 03:14:23
【问题描述】:

当处理一个非常大的统一(相同类型)JavaScript 对象(每个只有几列)的内存数组时...

在选择将其作为行->列与列->行进行迭代时,是否对性能和/或任何其他损失有任何影响?

示例

我们有来自 CSV 文件的 100,000 个数据行,每个数据行都是一个包含 10 个整数值的对象,我们需要触摸其中的每个值以进行特定计算。

垂直或水平遍历它会有什么不同吗?现代的 V8 对这些事情有丝毫关心吗?

var data = [...]; // array of many same-type objects;

// horizontal iteration:
for (var p in data[0]) {
    for (var i = 0; i < data.length; i++) {
        var value = data[i][p]; // current value;
        // calculate here from the value;
    }
}

// vertical iteration:
for (var i = 0; i < data.length; i++) {
    for (var p in data[0]) {
        var value = data[i][p]; // current value;
        // calculate here from the value;
    }
}

【问题讨论】:

  • 如何在不遍历内部循环的行的情况下逐列遍历数组?这对我来说没有意义。列的概念在 javascript 中没有得到很好的定义。所以,是的,我可以想象的“按列”迭代的任何方法都会比按行慢得多。你能展示一些代码来解释你的意思吗?
  • @ChristianFritz:如果内部结构的成员(属性/索引)一致,则可以在外部循环中定义。如果内部结构是一个对象而外部是一个数组,我认为将对象放在外部会产生性能差异,因为for-in 迭代通常比for 慢。
  • 在猜测中,我认为按行可能更快。鉴于数据可能是按行而不是按列构造的,那么行的数据存储在连续的内存块中的可能性(IE:更有可能已经将行的其余数据存储在缓存中),我会猜测它会更快。只是一个想法,可能是错误的,但这是我的想法。总的来说,不会有太大的不同。
  • @ChristianFritz,我在您的问题中添加了一个示例。
  • 我刚刚看到代码编辑,考虑到这一点,以及 Squint 的评论,我会选择水平迭代,只需要循环 for-in numColumns 次,如果使用垂直,则需要循环 numColumns * numRows。

标签: javascript node.js


【解决方案1】:

唯一可以确定的方法是运行一些基准测试,我做了一个 jsperf:http://jsperf.com/vertical-vs-horizontal-loop 结果取决于预期的引擎。我所做的测试的早期结果(Chrome 42 是 Window 10 上的 Edge):

|     UserAgent    | horizontal iteration | vertical iteration | vertical iteration with caching | # Tests |
|:----------------:|:--------------------:|:------------------:|:-------------------------------:|:-------:|
| Chrome 42.0.2311 |         1,067        |         287        |               226               |    2    |
|   Firefox 43.0   |         5,621        |         415        |               443               |    2    |
|      IE 11.0     |          976         |         441        |               313               |    2    |
|  Iron 46.0.2450  |         1,557        |         901        |              1,907              |    2    |
(numbers are ops/s, the higher the better)

有趣的是,水平迭代的速度从两倍到几十倍不等(在 Firefox 上)。但是带有缓存的垂直迭代仅在 Iron 46(Chromium fork so V8 引擎)上是最快的。

节点 v5.1.0 的基准测试:

水平迭代 x 1,140 ops/sec ±1.11%(采样 63 次运行)
垂直迭代 x 833 ops/sec ±0.92%(采样 68 次运行)
垂直迭代,缓存 x 1,678 ops/sec ±1.13%(采样 67 次运行)
最快的是带有缓存的垂直迭代

【讨论】:

  • 您的后续行动与结果相矛盾,即您错误地颠倒了它,我相信。你的意思是说垂直更快,而不是水平。
  • @vitaly-t 不,没有缓存,水平在所有浏览器/引擎中都更快,如果添加缓存,只有 V8 的垂直速度最快。这些数字是 ops/s 而不是时间,所以越高越好:)
  • 知道了。我错误地认为这些数字是延迟,越小越好,但事实恰恰相反。好测试!谢谢!
【解决方案2】:

反向垂直更适合更好的内存利用率和性能。我会说反向遍历数据数组。需要考虑的一些小事情是用i += 1 而不是i++++i 递增i。但是当您遍历一个大数组时,这些会显示出非常小的性能优势。如果你问我,我遵循以下任一方法

  1. 使用异步库的each 函数遍历数据数组并进行操作。优点是它们是真正异步的,因此不会阻塞执行线程。我已经使用这个库来比较两个大型数组,其中每个数组操作包括将 html 绘制到画布并进行比较。异步 js 对我来说做得很好。
  2. 根据用户计算机创建工作 (n) 个线程,然后将大数组分割成更小的块,为工作人员提供分块数组。无论哪个工作人员先完成,都会从队列中选择另一个分块数组。最后,您可以汇总结果。我自己测试过这种方法。我试图对一个包含 50K 数组项的数组进行排序。使用常规执行浏览器被阻止,但使用这种方法它能够完美地完成。我什至尝试了 300K 项目。我想说,如果您不支持低端浏览器,这是更好的方法。

【讨论】:

  • 为什么反向更好?有任何链接可以阅读它吗?我只是想了解为什么它更好。
  • “需要考虑的一些小事情是使用 i += 1 而不是 i++ 或 ++i 递增 i。但这些会显示出非常小的性能优势” -为什么+=1 会比++ 带来性能优势?
  • @nnnnnn 检查此链接jsperf.com/for-loop-i-vs-i/4。早期 Crockford 的方法曾经更快。现在浏览器似乎在该领域得到了改进。 Zakas 在他的设计模式书中还提到使用i += 1 而不是i++++i
  • 现代 JS 引擎在这类事情上非常聪明。出于性能原因,Crockford 不建议使用 +=1。
【解决方案3】:

垂直显然要好得多,因为您可以缓存data[i],并且在检查属性之前只查找一次。

// vertical iteration:
for (var i = 0; i < data.length; i++) {
    var obj = data[i] // <--- here
    for (var p in obj) {
        var value = obj[p]; // current value;
        // calculate here from the value;
    }
}

【讨论】:

  • “明显好多了”是主观的而不是客观的。为外部循环的每次迭代分配一个变量的成本很可能完全抵消了您实际获得的任何性能优势。多次调用data[i] 比将其分配给一个变量并多次调用该变量更有效。
  • @michael,当简单明了的“不做不必要的操作”与“重复做”时,它怎么可能是“主观的”。 “不做”总是客观上更快。完全消除工作是优化的基础。
猜你喜欢
  • 1970-01-01
  • 2015-03-14
  • 2015-12-12
  • 2021-06-05
  • 1970-01-01
  • 1970-01-01
  • 2017-03-29
  • 2018-04-06
  • 2019-02-27
相关资源
最近更新 更多