TieDad
TieDad

Reputation: 9939

Which Ruby statement is more efficient?

I have a hash table:

hash = Hash.new(0)

hash[:key] = hash[:key] + 1   # Line 1
hash[:key] += 1               # Line 2

Line 1 and Line 2 do the same thing. Looks like line 1 needs to query hash by key two times while line 2 only once. Is that true? Or they are actually same?

Upvotes: 1

Views: 96

Answers (4)

Jörg W Mittag
Jörg W Mittag

Reputation: 369624

The Ruby Language Specification spells out the algorithm for evaluating abbreviated indexing assignment expressions quite clearly. It is something like this:

primary_expression[indexing_argument_list] ω= expression
# ω can be any operator, in this example, it is +

is (roughly) evaluated like

o = primary_expression
*l = indexing_argument_list
v = o.[](*l)
w = expression
l << (v ω w)
o.[]=(*l)

In particular, you can see that both the getter and the setter are called exactly once.

You can also see that by looking at the informal desugaring:

hash[:key] += 1

# is syntactic sugar for 

hash[:key] = hash[:key] + 1

# which is syntactic sugar for 

hash.[]=(:key, hash.[](:key).+(1))

Again, you see that both the setter and the getter are called exactly once.

Upvotes: 1

edwardsmatt
edwardsmatt

Reputation: 2044

@sza beat me to it :)

Here is my example irb session:

> require 'benchmark'
=> true 
> n = 10000000
=> 10000000 
> Benchmark.bm do |x|
>   hash = Hash.new(0)
>   x.report("Case 1:") { n.times do; hash[:key] = hash[:key] + 1; end }
>   hash = Hash.new(0)
>   x.report("Case 2:") { n.times do; hash[:key] += 1; end }
> end
       user     system      total        real
Case 1:  1.070000   0.000000   1.070000 (  1.071366)
Case 2:  1.040000   0.000000   1.040000 (  1.043644)

Upvotes: 1

zs2020
zs2020

Reputation: 54541

I created a ruby script to benchmark it

require 'benchmark'

def my_case1()
  @hash[:key] = @hash[:key] + 1 
end

def my_case2()
  @hash[:key] += 1   
end

n = 10000000
Benchmark.bm do |test|
  test.report("case 1") { 
    @hash = Hash.new(1)
    @hash[:key] = 0
    n.times do; my_case1(); end 
  }

  test.report("case 2") {
    @hash = Hash.new(1)
    @hash[:key] = 0
    n.times do; my_case2(); end 
 }
end

Here is the result

       user     system      total        real
case 1  3.620000   0.080000   3.700000 (  4.253319)
case 2  3.560000   0.080000   3.640000 (  4.178699)

It looks hash[:key] += 1 is slightly better.

Upvotes: 4

Hrishi
Hrishi

Reputation: 7138

second one is the customary way of doing it. It is more efficient.

Upvotes: 0

Related Questions