9

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)

Mangara
  • 1,116
  • 1
  • 10
  • 23
  • 1
    `begin i+=1 end while i<=NUMBER` is faster still. Go figure... – dawg Jul 13 '17 at 01:28
  • 2
    [Kernel#loop](http://ruby-doc.org/core-2.4.0/Kernel.html#method-i-loop) rescues `StopIteration` exceptions. It's response is to break out of the loop. If `enum = [1,2,3].to_enum; loop do; enum.next; end`, `StopIteration` is raised when `next` is executed after the enumerator has generated its last value. Conceivably, the associated overhead may account for the benchmark results, but I would expect any time savings from using `while(true)` or `while(1)` (FORTRAN relics) would normally be dwarfed by the time taken to execute the statements within the loop. For consistency I always use `loop`. – Cary Swoveland Jul 13 '17 at 06:13

2 Answers2

10

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.

davidhu
  • 9,523
  • 6
  • 32
  • 53
Stephane Paquet
  • 2,315
  • 27
  • 31
  • Quick remarks on your loops 1. Placing the condition at the end is usually better 2. Expanding the condition seems to improve performance (of both loops) so write the following code f index > NUMBER break end You can get more information here https://launchschool.com/books/ruby/read/loops_iterators – Stephane Paquet Jul 13 '17 at 04:11
-5

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.

  • 7
    This doesn't seem to answer the question. The `while` loop is being presented as the faster option, despite the conditional... – Brad Werth Jul 13 '17 at 01:10
  • The best place to look for the answer then would be the source code. So what is being run can be compared beyond just what a benchmark can. – broken_historian Jul 13 '17 at 02:21
  • 5
    Maybe you should do that, next time, before posting an incorrect guess as an answer... – Brad Werth Jul 13 '17 at 02:34