数组 – Ruby中更快的是`arr = [x]`或`arr << x`

前端之家收集整理的这篇文章主要介绍了数组 – Ruby中更快的是`arr = [x]`或`arr << x`前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
直觉上,后者应该比前者快.不过,当我看到基准测试结果时,我感到非常惊讶:
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版本.

解决方法

我相信这是由于MRI分配阵列(所有这些答案都是非常具体的MRI). Ruby尝试使用数组来实现高效:例如,将小数组(< = 3元素)打包到RARRAY结构中. 另一件事是,如果你采取一个数组,并开始一次添加一个值,ruby不会一次增加缓冲区一个元素,它以块为单位:这是更有效率的,牺牲了少量的记忆. 查看所有这些的一个工具是使用memsize_of:
ObjectSpace.memspace_of([]) #=> 40 (on 64 bit platforms
ObjectSpace.memspace_of([1,2]) #=> 40 (on 64 bit platforms
ObjectSpace.memsize_of([1,2,3,4]) #=> 72
ObjectSpace.memsize_of([]<<1<<2<<3<<4) #=> 200

在前两种情况下,阵列包装在RARRAY结构中,因此内存大小只是任何对象的基本大小(40字节).在第三种情况下,ruby必须为4个值分配一个数组(每个8个字节),所以大小为40 32 = 72.在最后一种情况下,ruby将存储增加到20个元素

这与第二种情况有关.基准测试块内有一个新创建的阵列,仍然有一些备用容量:

ObjectSpace.memsize_of((0..20).to_a) #=> 336,enough for nearly 40 elements.

<<可以将其对象写入适当的插槽,而=必须分配一个新的数组(对象及其缓冲区)并复制所有的数据.

如果我做

a = [1,4,5]
b  = a.dup
ObjectSpace.memsize_of(b) #=> 40

这里b分享一个缓冲区,所以报告为使用没有超出基本对象大小的内存.在b被写入的地方,ruby将不得不复制数据(写在副本上):在第一个基准BOTH =和<<实际上正在分配一个足够大的新的缓冲区并复制所有的数据. 这里是我得到handwavy:这将完全解释的东西,如果<和=执行相同,但这不是发生了什么.我读的东西是简单的.所有它必须做的,无论什么是分配缓冲区,并从两个位置memcpy一些数据 - 这是快速. <<另一方面是使数组变异,因此它在写入时支付拷贝的开销:它正在做额外的工作与=比较.例如,ruby需要跟踪谁共享缓冲区,以便当没有人共享它时,原始数组的垃圾收集是可能的. 一个基准,可以说服我说这个解释是正确的如下:

require 'benchmark/ips'
b = (0..20).to_a.dup
y = 21
Benchmark.ips do |x|
  x.report('<<')   { a = b.dup; a << y }
  x.report('+=')   { a = b.dup; a += [y] }

  x.report('<<2')   { a = b.dup; a << y; a<< y}
  x.report('+=2')   { a = b.dup; a += [y]; a += [y] }
end

这与原来基本相同的基准,但现在附加了2个元素.对于<写入开销的副本只会在第一次发生.我得到的结果是

<<      1.325M (± 7.6%) i/s -      6.639M
              +=      1.742M (± 9.5%) i/s -      8.677M
             <<2      1.230M (±10.3%) i/s -      6.079M
             +=2      1.140M (±10.8%) i/s -      5.656M

如果你这样做两次,那么附加到数组就返回顶部了.

猜你在找的Ruby相关文章