当前位置:  开发笔记 > 后端 > 正文

什么在Ruby中更快,`arr + = [x]`或`arr << x`

如何解决《什么在Ruby中更快,`arr+=[x]`或`arr<<x`》经验,为你挑选了1个好方法。

直观地说,后者应该比前者更快.但是,当我看到基准测试结果时,我感到非常惊讶:

  require 'benchmark/ips'

  b = (0..20).to_a;
  y = 21;
  Benchmark.ips do |x|
    x.report('<<')   { a = b.dup; a << y }
    x.report('+=')   { a = b.dup; a += [y] }
    x.report('push') { a = b.dup; a.push(y) }
    x.report('[]=')  { a = b.dup; a[a.size]=y }
    x.compare!
  end

结果是:

Calculating -------------------------------------
                  <<    24.978k i/100ms
                  +=    30.389k i/100ms
                push    24.858k i/100ms
                 []=    22.306k i/100ms
-------------------------------------------------
                  <<    493.125k (± 3.2%) i/s -      2.473M
                  +=    599.830k (± 2.3%) i/s -      3.009M
                push    476.374k (± 3.3%) i/s -      2.386M
                 []=    470.263k (± 3.8%) i/s -      2.364M

Comparison:
                  +=:   599830.3 i/s
                  <<:   493125.2 i/s - 1.22x slower
                push:   476374.0 i/s - 1.26x slower
                 []=:   470262.8 i/s - 1.28x slower

然而,当我的一位同事独立创建自己的基准时,结果恰恰相反:

 Benchmark.ips do |x|
   x.report('push') {@a = (0..20).to_a; @a.push(21)}
   x.report('<<')   {@b = (0..20).to_a; @b << 21}
   x.report('+=')   {@c = (0..20).to_a; @c += [21]}
   x.compare!
 end

结果:

Calculating -------------------------------------
                push    17.623k i/100ms
                  <<    18.926k i/100ms
                  +=    16.079k i/100ms
-------------------------------------------------
                push    281.476k (± 4.2%) i/s -      1.410M
                  <<    288.341k (± 3.6%) i/s -      1.457M
                  +=    219.774k (± 8.3%) i/s -      1.093M

Comparison:
                  <<:   288341.4 i/s
                push:   281476.3 i/s - 1.02x slower
                  +=:   219774.1 i/s - 1.31x slower

我们还跨越了我们的基准测试,在我们的两台机器上,他的基准测试显示+=速度明显慢于<<我的基准测试,而我的基准测试显示相反.

这是为什么?

UPD:我的Ruby版本是Ruby 2.2.3p173(2015-08-18修订版51636)[x86_64-darwin14] ; 我的同事是2.2.2(不知道详情,明天会更新帖子).

UPD2:ruby 2.2.2p95(2015-04-13修订版50295)[x86_64-darwin12.0]我的队友的Ruby版本.



1> Wand Maker..:

在我看来,为了简化各种运算符的比较,我们应该删除不必要的代码并保持测试简单.

require 'benchmark/ips'

y = 10
Benchmark.ips do |x|
    x.report('<<')   { a = [0,1,2,3,4,5,6,7,8,9]; a << y }
    x.report('+=')   { a = [0,1,2,3,4,5,6,7,8,9]; a += [y] }
    x.report('push') { a = [0,1,2,3,4,5,6,7,8,9]; a.push(y) }
    x.report('[]=')  { a = [0,1,2,3,4,5,6,7,8,9]; a[a.size]=y }
    x.compare!
end

上述代码的结果与问题中共享的第二个代码段一致.

Calculating -------------------------------------
                  <<   101.735k i/100ms
                  +=   104.804k i/100ms
                push    92.863k i/100ms
                 []=    99.604k i/100ms
-------------------------------------------------
                  <<      2.134M (± 3.3%) i/s -     10.682M
                  +=      1.786M (±13.2%) i/s -      8.804M
                push      1.930M (±16.1%) i/s -      9.472M
                 []=      1.948M (± 7.9%) i/s -      9.761M

Comparison:
                  <<:  2134005.4 i/s
                 []=:  1948256.8 i/s - 1.10x slower
                push:  1930165.3 i/s - 1.11x slower
                  +=:  1785808.5 i/s - 1.19x slower

[Finished in 28.3s]

为什么<<比快+=

Array#<<将元素附加到数组的四种方法中最快的是因为它只是这样 - 将一个元素附加到数组.相反,Array#+附加一个元素但返回一个新的数组副本 - 创建新的数组副本使其最慢.(可以使用toogle code文档中的选项来了解某些方法所做的其他工作)

基准标记 dup

如果我们使用下面的代码进行基准测试,

require 'benchmark/ips'

y = 10
Benchmark.ips do |x|
    x.report('<<')   { a = [0,1,2,3,4,5,6,7,8,9].dup; a << y }
    x.report('+=')   { a = [0,1,2,3,4,5,6,7,8,9].dup; a += [y] }
    x.report('push') { a = [0,1,2,3,4,5,6,7,8,9].dup; a.push(y) }
    x.report('[]=')  { a = [0,1,2,3,4,5,6,7,8,9].dup; a[a.size]=y }
    x.compare!
end

我们看到以下结果:

Calculating -------------------------------------
                  <<    65.225k i/100ms
                  +=    76.106k i/100ms
                push    64.864k i/100ms
                 []=    63.582k i/100ms
-------------------------------------------------
                  <<      1.221M (±14.3%) i/s -      6.001M
                  +=      1.291M (±13.1%) i/s -      6.393M
                push      1.164M (±14.1%) i/s -      5.773M
                 []=      1.168M (±14.5%) i/s -      5.722M

Comparison:
                  +=:  1290970.6 i/s
                  <<:  1221029.0 i/s - 1.06x slower
                 []=:  1168219.3 i/s - 1.11x slower
                push:  1163965.9 i/s - 1.11x slower

[Finished in 28.3s]

如果我们仔细研究两个结果,我们只看到一个区别.该+=条目已成为第一个,而其余方法的顺序与原始结果保持一致.

使用时为什么会翻转结果dup

这是我疯狂的猜测,我猜测Ruby解释器优化了代码并且没有创建新数组作为其中的一部分,+=因为它知道它正在处理新创建的数组副本dup


令我惊讶的是,"<<"和"推"的结果是如此不同.我认为一个是另一个的别名.
顺便说一句.这样的基准没有多大意义,因为一般规则没有推论.它们是OS/HW平台和语言实现依赖.在我的Linux x86_64系统上,`#<<`始终最快,##``最慢(~15%),CRuby 2.2.3.使用JRuby 9.0.1.0是最快的##] =`和`#+`slowest(~23%).使用`#dup`来创建数组而不是文字定义将`#+`移到顶部,尽管它产生更长的字节码(instr.序列)而没有明显的优化技巧.它可能是由底层的os或hw缓存管道引起的.
问题的本质和它最有趣的部分是为什么重复翻转结果如此戏剧性,而不是哪个运算符是最快的.
推荐阅读
落单鸟人
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有