【问题标题】:Is there any real benefit for using javascript Array reduce() method?使用 javascript Array reduce() 方法有什么真正的好处吗?
【发布时间】:2012-03-26 15:08:26
【问题描述】:

reduce() 方法的大多数用例都可以使用 for 循环轻松重写。对 JSPerf 的测试表明,reduce() 通常会慢 60%-75%,具体取决于每次迭代中执行的操作。

除了能够以“函数式”编写代码之外,还有什么真正的理由使用 reduce() 吗?如果你可以通过编写更多的代码来获得 60% 的性能提升,你为什么还要使用 reduce()?

编辑:事实上,forEach() 和 map() 等其他函数式方法都表现出相似的性能,至少比简单的 for 循环慢 60%。

这里是 JSPerf 测试的链接(带有函数调用):forloop vs forEach

【问题讨论】:

    标签: javascript arrays reduce


    【解决方案1】:

    方法的性能可能因数据大小而异。 速度还受编译器优化和数据预热的影响。 因此,在小数据上for of 获胜,在大数据上reduce 微不足道。

    您可以通过运行测试自己查看:

    const LOOP = 3
    
    test(dataGenerator(5))
    test(dataGenerator(500))
    test(dataGenerator(50000))
    test(dataGenerator(500000))
    test(dataGenerator(5000000))
    
    function test(dataSet) {
        let sum
    
        console.log('Data length:', dataSet.length)
    
        for (let x = 0; x < LOOP; x++) {
            sum = 0
            console.time(`${x} reduce`)
            sum = dataSet.reduce((s, d) => s += d.data, 0)
            console.timeEnd(`${x} reduce`)
        }
    
        for (let x = 0; x < LOOP; x++) {
            sum = 0
            console.time(`${x} map`)
            dataSet.map((i) => sum += i.data)
            console.timeEnd(`${x} map`)
        }
    
        for (let x = 0; x < LOOP; x++) {
            sum = 0
            console.time(`${x} for loop`)
            for (let i = 0; i < dataSet.length; i++) {
                sum += dataSet[i].data
            }
            console.timeEnd(`${x} for loop`)
        }
    
        for (let x = 0; x < LOOP; x++) {
            sum = 0
            console.time(`${x} for reverse`)
            for (let i = dataSet.length; i--;) {
                sum += dataSet[i].data
            }
            console.timeEnd(`${x} for reverse`)
        }
    
        for (let x = 0; x < LOOP; x++) {
            sum = 0
            console.time(`${x} for of`)
            for (const item of dataSet) {
                sum += item.data
            }
            console.timeEnd(`${x} for of`)
        }
    
        for (let x = 0; x < LOOP; x++) {
            sum = 0
            console.time(`${x} for each`)
            dataSet.forEach(element => {
                sum += element.data
            })
            console.timeEnd(`${x} for each`)
        }
    
        console.log()
    }
    
    function dataGenerator(rows) {
        const dataSet = []
        for (let i = 0; i < rows; i++) {
            dataSet.push({id: i, data: Math.floor(100 * Math.random())})
        }
        return dataSet
    }
    

    这些是在我的笔记本电脑上进行的性能测试结果。 与for reversefor of 不同,for loop 无法稳定工作。

    ➜  node reduce_vs_for.js 
    Data length: 5
    0 reduce: 0.127ms
    1 reduce: 0.008ms
    2 reduce: 0.006ms
    0 map: 0.036ms
    1 map: 0.007ms
    2 map: 0.018ms
    0 for loop: 0.005ms
    1 for loop: 0.014ms
    2 for loop: 0.004ms
    0 for reverse: 0.009ms
    1 for reverse: 0.005ms
    2 for reverse: 0.004ms
    0 for of: 0.008ms
    1 for of: 0.004ms
    2 for of: 0.004ms
    0 for each: 0.046ms
    1 for each: 0.003ms
    2 for each: 0.003ms
    
    Data length: 500
    0 reduce: 0.031ms
    1 reduce: 0.027ms
    2 reduce: 0.026ms
    0 map: 0.039ms
    1 map: 0.036ms
    2 map: 0.033ms
    0 for loop: 0.029ms
    1 for loop: 0.028ms
    2 for loop: 0.028ms
    0 for reverse: 0.027ms
    1 for reverse: 0.026ms
    2 for reverse: 0.026ms
    0 for of: 0.051ms
    1 for of: 0.063ms
    2 for of: 0.051ms
    0 for each: 0.030ms
    1 for each: 0.030ms
    2 for each: 0.027ms
    
    Data length: 50000
    0 reduce: 1.986ms
    1 reduce: 1.017ms
    2 reduce: 1.017ms
    0 map: 2.142ms
    1 map: 1.352ms
    2 map: 1.310ms
    0 for loop: 2.407ms
    1 for loop: 12.170ms
    2 for loop: 0.246ms
    0 for reverse: 0.226ms
    1 for reverse: 0.225ms
    2 for reverse: 0.223ms
    0 for of: 0.217ms
    1 for of: 0.213ms
    2 for of: 0.215ms
    0 for each: 0.391ms
    1 for each: 0.409ms
    2 for each: 1.020ms
    
    Data length: 500000
    0 reduce: 1.920ms
    1 reduce: 1.837ms
    2 reduce: 1.860ms
    0 map: 13.140ms
    1 map: 12.762ms
    2 map: 14.584ms
    0 for loop: 15.325ms
    1 for loop: 2.295ms
    2 for loop: 2.014ms
    0 for reverse: 2.163ms
    1 for reverse: 2.138ms
    2 for reverse: 2.182ms
    0 for of: 1.990ms
    1 for of: 2.009ms
    2 for of: 2.108ms
    0 for each: 2.226ms
    1 for each: 2.583ms
    2 for each: 2.238ms
    
    Data length: 5000000
    0 reduce: 18.763ms
    1 reduce: 17.155ms
    2 reduce: 26.592ms
    0 map: 145.415ms
    1 map: 135.946ms
    2 map: 144.325ms
    0 for loop: 29.273ms
    1 for loop: 28.365ms
    2 for loop: 21.131ms
    0 for reverse: 21.301ms
    1 for reverse: 27.779ms
    2 for reverse: 29.077ms
    0 for of: 19.094ms
    1 for of: 19.338ms
    2 for of: 26.567ms
    0 for each: 22.456ms
    1 for each: 26.224ms
    2 for each: 20.769ms
    

    【讨论】:

      【解决方案2】:
      • 你可能想要范围界定。例如,您可能想要创建回调函数或引用 javascript 对象。有关更多信息,请参阅为什么 javascript 没有被阻止作用域。 [编辑:现代 javascript 现在支持 let 变量。回到 ESv6 之前,当你声明一个 var 变量时,它会像写在函数代码块的顶部一样被提升,所以你经常不得不将 for 循环体编写为函数。但以下内容仍然适用:] 如果您编写了一个函数,不妨使用函数式样式,除非它是一个重大瓶颈。
      • 您的代码并不总是需要以全速运行。您甚至可能没有优化瓶颈中的代码。
      • 此外,您没有提供“在 JSPerf 上的测试”,因此我们可以对其进行批评。例如,如果您已经有一个归约函数(或 map 或 forEach 函数),那么我敢打赌,性能将是相当的。即使没有,测试方法也可能存在缺陷,尤其是考虑到许多浏览器的优化方式可能不同或具有不同的函数调用开销。

      旁注:这是语法之间的有效性能比较,但是当语法不是手头的问题时,无效的性能比较

      myArray.map(function(x){return x+1})
      
      // ...versus...
      
      for(var i=0; i<myArray.length; i++) {
          myArray[i] = myArray[i]+1;
      }
      

      这将是一个有效的性能比较:

      myArray.forEach(function(x){return x+1})
      
      // ...versus...
      
      var plusOne = function(x){return x+1};
      for(var i=0; i<myArray.length; i++) {
          plusOne(myArray[i]);
      }
      
      // (may need a side-effect if the compiler is smart enough to optimize this)
      

      (同时回复您的编辑:.forEach().map() 提供了更多的清晰度,并避免了显式循环 int i=0; i&lt;array.length; i++ 参数的需要。)

      【讨论】:

      • 我不同意你关于什么构成有效的性能比较。 first 是有效的比较,因为(对于您的示例)问题肯定是“哪种方法在向数组的每个元素中添加一个时最快?”,而不是“哪种方法最快,但任何方法不使用函数调用将被取消参赛资格”。我同意,如果你假设你想要一个函数用于范围/关闭目的,那么你最好使用 .reduce().forEach() 或其他任何东西。
      • 问题完全是关于语法的,所以这是一个有效的性能比较。也许我应该在您想要压缩所有可能的性能的情况下重新表述这个问题。在那种情况下,我找不到不使用普通 for 循环的充分理由。您能否提供一个绝对需要范围界定的具体示例?
      • @EvanYou:[1,2,3].forEach(function(x){setTimeout(function(){alert(x)},1000)})(警报 1、2、3)与 for(var i=0;i&lt;4;i++){setTimeout(function(){alert(i)},1000)}(警报 4、4、4)
      • @ninjagecko 谢谢,这是有道理的。但是,我们也可以通过创建一个在每个 for 循环中调用的函数来提供范围,就像原始答案中的第二个测试用例一样。我在 JSPerf 上做了一个类似的操作,结果 forEach 仍然慢了 60%:test here
      • 在 for 循环中使用 let 而不是 var 会使您的第一个项目符号在 JS 的现代实现中无效(仅将这里留给 2017 年阅读此问题的人)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-03-15
      • 2020-10-20
      • 1970-01-01
      • 1970-01-01
      • 2019-12-14
      • 2018-08-17
      • 2010-12-11
      相关资源
      最近更新 更多