2

I'm learning Ruby right now. Coming from using Javascript the past couple of years, I'm familiar with the While loop. But the until loop? I've looked around but couldn't find a solid reason why one would be better than the other.

Ruby has "until" which is described as another way to phrase a problem. The way I see it, "while" iterates until false, and "until" iterates until true.

I'm sure that most of the programs I write won't really need refactoring for speed. However, I like to get into the little details sometimes.

Is there a speed difference between the two loops? Why is there an "until" syntax in Ruby? Why not just stick with "while?"

Nathan
  • 291
  • 2
  • 5
  • 17

3 Answers3

5

There would not be a speed difference between while and until as they mirror each other.

We'll compare a while loop with an until loop:

n = 0
puts n += 1 while n != 3

n = 0
puts n += 1 until n == 3

These will both print 1 through 3.

Here's a diff between the two disassembled human-readable instruction sequences from the Ruby VM:

@@ -13,7 +13,7 @@
 0021 pop              
 0022 getlocal_OP__WC__0 2
 0024 putobject        3
-0026 opt_neq          <callinfo!mid:!=, argc:1, ARGS_SIMPLE>, <callcache>, <callinfo!mid:==, argc:1, ARGS_SIMPLE>, <callcache>
-0031 branchif         8
-0033 putnil           
-0034 leave
+0026 opt_eq           <callinfo!mid:==, argc:1, ARGS_SIMPLE>, <callcache>
+0029 branchunless     8
+0031 putnil           
+0032 leave

A while loop uses a branchif for its jump, whereas the until loop used a branchunless. So, these loops simply differ in the comparison being made, which you can see by looking at how branchif and branchunless are defined:

DEFINE_INSN
branchif
(OFFSET dst)
(VALUE val)
()
{
    if (RTEST(val)) {
    RUBY_VM_CHECK_INTS(th);
    JUMP(dst);
    }
}
DEFINE_INSN
branchunless
(OFFSET dst)
(VALUE val)
()
{
    if (!RTEST(val)) {
    RUBY_VM_CHECK_INTS(th);
    JUMP(dst);
    }
}

Performance between while and until should be nearly identical. Usage should be determined by readability.

Sagar Pandya
  • 9,323
  • 2
  • 24
  • 35
Jon
  • 2,932
  • 2
  • 23
  • 30
  • Wow, this is actually really cool to see! Thank you. Where can I learn more about this? – Nathan Aug 02 '17 at 14:20
  • @Nathan There's a great book about Ruby's internals: [_Ruby Under a Microscope_](https://www.nostarch.com/rum) by Pat Shaughnessy. Pat has some excerpts in a few blog posts here: http://patshaughnessy.net/2012/6/29/how-ruby-executes-your-code There aren't too many resources about this topic. There are a few blog posts, but they tend to reference Pat's book. – Jon Aug 02 '17 at 16:15
  • 1
    Note: Ruby Under a Microscope is *not* a book about Ruby's internals. It is a book about YARV's internals. (Ruby is a programming language, a language is a specification, a piece of paper, it has no "internals".) As far as I know, it doesn't cover TruffleRuby *at all*, for example, which I personally think is by far the most interesting Ruby implementation to study. – Jörg W Mittag Aug 02 '17 at 19:52
  • @JörgWMittag Right! The book specifically looks at YARV/MRI, but it's a great source for those looking to learn more. If you know of other resources for those looking to learn, please share. TruffleRuby is a very interesting implementation which stands out among other implementations. I'm just waiting for MRI's [deoptimizations](https://github.com/ruby/ruby/pull/1419)... :) – Jon Aug 02 '17 at 20:18
  • FWIW, it looks like Ruby Under a Microscope spends a chapter each on JRuby and Rubinius. Both Ruby Under a Microscope and TruffleRuby look pretty interesting, I guess I have some reading to do... – Brad Werth Aug 03 '17 at 00:09
3

Speed differences aside, it's really all about readability, which is something that Ruby prides itself on.

Let's pretend we're making a drink - which do you think reads better?

A) pour_drink until glass.full?
B) pour_drink while !glass.full?
Zonet
  • 302
  • 2
  • 10
  • Ok, yeah, I get that. the [until] statement is more readable. But really, the question is about speed. In a large program, would it really make a difference? How much of "until" is being parsed down into machine code compared to "while"? – Nathan Aug 02 '17 at 02:51
  • 2
    @Nathan: "I'm learning Ruby right now." - In this case, worrying about what bytecode gets generated should be the __last__ thing on your mind. Ruby programs are never slow just because one loop keyword is "slower" than the other. – Sergio Tulentsev Aug 02 '17 at 07:05
3

Speed will be more influenced by your choice of comparison operator than your choice of while or until

Benchmark.bmbm do |bm|
  bm.report('while') do
    n = 0
    n += 1 while n != 10_000_000
  end

  bm.report('until') do
    n = 0
    n += 1 until n == 10_000_000
  end
end

            user     system      total        real
while   0.250000   0.000000   0.250000 (  0.247949)
until   0.220000   0.000000   0.220000 (  0.222049)

With while n != 10_000_000 vs. until n == 10_000_000, until appears to be faster.

Benchmark.bmbm do |bm|
  bm.report('while') do
    n = 0
    n += 1 while n < 10_000_000
  end

  bm.report('until') do
    n = 0
    n += 1 until n == 10_000_000
  end
end

            user     system      total        real
while   0.210000   0.000000   0.210000 (  0.207265)
until   0.220000   0.000000   0.220000 (  0.223195)

Change it to while n < 10_000_000 and now while seems to have the edge. To be fair we should give them the more equivalent while n < 10_000_000 vs. until n > 9_999_999

Benchmark.bmbm do |bm|
  bm.report('while') do
    n = 0
    n += 1 while n < 10_000_000
  end

  bm.report('until') do
    n = 0
    n += 1 until n > 9_999_999
  end
end

            user     system      total        real
while   0.200000   0.000000   0.200000 (  0.208428)
until   0.200000   0.000000   0.200000 (  0.206218)

Now they're almost identical. So follow Ruby's lead and gain your satisfaction from code that reads like an English sentence. But make sure you use < or > to gain that extra boost of .0000000001 seconds.

m. simon borg
  • 2,515
  • 12
  • 20
  • This is kinda cool, but yeah, I can see where speed is a very negligible difference here. Obviously this will also depend on each individual computer at this level. Thank you for the effort on writing this up for me. – Nathan Aug 02 '17 at 14:22
  • @Nathan No problem! Thanks for asking the question. Jon's answer is definitely better at explaining why they're the same, there's no way I could top that. But it's always fun to write up some benchmarks and test things out for real. – m. simon borg Aug 02 '17 at 14:34
  • @Nathan It was interesting to see that `!=` was about 20% slower than `<` in this context – m. simon borg Aug 02 '17 at 14:37