Why is Ruby's loop command slower than while t

2019-06-24 19:00发布

问题:

Ruby has a built-in loop command that executes the block following it forever (or until stopped by break). However, when comparing it against the functionally similar while true, it is significantly slower:

require "benchmark/ips"

NUMBER = 100_000_000

def fast
  index = 0
  while true
    break if index > NUMBER
    index += 1
  end
end

def slow
  index = 0
  loop do
    break if index > NUMBER
    index += 1
  end
end

Benchmark.ips do |x|
  x.report("While Loop")  { fast }
  x.report("Kernel loop") { slow }
  x.compare!
end

Under Ruby 2.4.1 (p111 (2017-03-22 revision 58053) [x64-mingw32]), the difference is striking:

Warming up --------------------------------------
          While Loop     1.000  i/100ms
         Kernel loop     1.000  i/100ms
Calculating -------------------------------------
          While Loop      0.630  (± 0.0%) i/s -      4.000  in   6.350897s
         Kernel loop      0.190  (± 0.0%) i/s -      1.000  in   5.274249s

Comparison:
          While Loop:        0.6 i/s
         Kernel loop:        0.2 i/s - 3.32x  slower

Why is there such a performance difference? And why is the single-purpose loop command worse at its job than the general-purpose while?

(Benchmark copied from here, licensed under CC-BY-SA)

回答1:

loop is a kernel method which takes a block. As a reminder, a block introduces new local variable scope.

For example:

loop do
 a = 2
 break
end
puts a

Will return an error such as: "NameError: undefined local variable or method `a' for main:Object" On the other hand:

while true
 a = 2
 break
end
p a #=> return a = 2

So I wouldn't be surprised that loop creates some sort of local variable(s) such as one for the break statement that is (are) going to be in its scope. Creating/deleting those variables at every iteration slow down the process.



回答2:

Generally to get more accurate results from a benchmark you can increase the number of times you do your test and average the results across the number of benchmarks.

The while loop has a conditional to check at the top of every loop, and in contrast loop do...end has no conditional does not. So it is computing less logic even if that conditional is true, it is still doing at least one check more.