Home on Rails

This morning, return to your home on rails

Ruby Speed Quiz Explained

At the end of Ruby speed quiz I promised to publish an explanation for each case. I did not expect that more than 900 Rubyists will give it a shot. I was surprised. Thanks to all who participated!

But lets go back to the questions.

1. Range cover? VS include?

  • ('a'..'z').cover?('f')
  • ('a'..'z').include?('f')
  • both run with the similar speed

cover? is faster because it just finds out if the argument is > than the first and < the second. No looping necessary. include?, in opposite, loops through all elements of the range until it finds an argument or reaches the end.

1
2
3
4
5
6
7
8
9
10
('a'..'z').cover?('f')
==> 'a' <= 'f' && 'f' <= 'z'

('a'..'z').include?('f')
==> 'a' == 'f'
==> 'b' == 'f'
==> 'c' == 'f'
==> 'd' == 'f'
==> 'e' == 'f'
==> 'f' == 'f'

Caveats

Be careful when using include? and cover?:

1
2
3
4
5
6
7
8
('a'..'z').include?('blah')
# => false
('a'..'z').cover?('blah')
# => true
'a' < 'blah'
# => true
'blah' < 'z'
# => true

See this post for detailed benchmarks.

2. blk.call VS yield

1
2
3
4
def foo
  yield if block_given?
end
foo { puts "Hi from foo" }
1
2
3
4
def bar(&blk)
  blk.call
end
bar { puts "Hi from bar" }
  • both run with the similar speed

yield is faster because the process of procifying a block takes time.

See this post for detailed benchmarks.

3. Hash [] VS fetch

1
2
h = {}
h[:a] || 1
1
2
h = {}
h.fetch(:a, 1)
  • both run with the similar speed

Using brackets to get goodies out of a hash is the same as fetch because they both use the same code to do exactly the same thing.

1
if (!RHASH(hash)->ntbl || !st_lookup(RHASH(hash)->ntbl, key, &val)) {

It checks that the hash is not empty and tries to find the value using a given key.

4. define_method VS class_eval (definition, NOT call speed)

1
2
3
4
5
class A
  100.times do |i|
    define_method("foo_#{i}") { 10.times.map { "foo".length } }
  end
end
1
2
3
4
5
class B
  100.times do |i|
    class_eval 'def bar_#{i}; 10.times.map { "foo".length }; end'
  end
end
  • both run with the similar speed

Define method is faster because you don’t have to eval the class and then define a method on it. You are already in the class scope. Also on each call to class_eval, MRI creates a new parser and parses the string. In the define_method case, the parser is only run once.

However, it’s not that simple. Yes, define_method creates the method faster. But after creation, a short method created with class_eval is usually faster than one created with define_method. That is why you still can find many class_eval instructions in Rails. These are places where run (not startup) speed matters. So, it’s worthwhile to chose based on your use case.

See this post for detailed benchmarks.

5. super with OR without arguments

1
2
3
4
5
6
7
8
9
10
11
12
13
class Parent
 def bar(a, b)
   puts "#{a} - #{b}"
 end
end

class Child < Parent
 def bar(a, b)
   super
 end
end
c = Child.new
c.bar(1, 2)
1
2
3
4
5
6
7
8
9
10
11
12
13
class Parent
  def bar(a, b)
    puts "#{a} - #{b}"
  end
end

class Child < Parent
  def bar(a, b)
    super(a, b)
  end
end
c = Child.new
c.bar(1, 2)
  • both run with the similar speed

Calling super without arguments passes any arguments along that were passed through to the calling method. So it deals with arguments anyways.

Comments