tompave
tompave

Reputation: 12412

ruby memoization, efficiency

This question is mostly about Ruby internals, the speed can be gauged with a simple benchmark.

What's the most efficient way to memoize a return value in ruby?

I've always memoized values with:

def method
  @value ||= calculate_value
end

But since it technically expands to:

@value = @value || calculate_value

I wonder about the efficiency of re-executing the same assignment each time.

Would this be better?

def method
  @value ? @value : (@value = calculate_value)
end

Also, does it change in different interpreters? MRI, Rubinius, etc.

Upvotes: 7

Views: 495

Answers (3)

sawa
sawa

Reputation: 168121

I think any differences between ||= and ... = ... || ... should be subtle.

Perhaps, a much more efficient way is to switch the method when the value is set. This switching happens only once, and from there on, the method call becomes just a reference to the variable, so it should be fast, and the advantage increases as the number of times the method is called increases.

def method
  alias method memoized_value
  @value ||= calculate_value
end

def memoized_value; @value end

This assumes that calculate_value always returns a truthy value, and there is no other part in the code that modifies the value so that once method is called, the variable's value remains truthy.

Upvotes: 0

Pavel S
Pavel S

Reputation: 389

@spickermann, @tompave, sorry for posting it as an answer but I need code formatting to post benchmark results for different usecases. It looks like explicit way is the fastest @a || @a = calculated_value

> n = 10000000;
> a = 1;
> puts Benchmark.measure { n.times { a ||= 1 } }
  0.570000   0.000000   0.570000 (  0.569977)
> puts Benchmark.measure { n.times { a ? a : a = 1 } }
  0.560000   0.000000   0.560000 (  0.562855)
> puts Benchmark.measure { n.times { a || a = 1 } }
  0.530000   0.000000   0.530000 (  0.532715)
> @a = 1;
> puts Benchmark.measure { n.times { @a ||= 1 } }
  0.870000   0.000000   0.870000 (  0.869489)
> puts Benchmark.measure { n.times { @a ? @a : @a = 1 } }
  0.670000   0.000000   0.670000 (  0.668910)
> puts Benchmark.measure { n.times { @a || @a = 1 } }
  0.610000   0.000000   0.610000 (  0.613978)

ruby 2.1.2p95 (2014-05-08 revision 45877) [x86_64-darwin13.0]

All 3 notations behave the same from result point of view, however last one is the fasted one from performance point of view. Unfortunately it is all empirical and I can't link any ruby source code here.

Upvotes: 0

spickermann
spickermann

Reputation: 106932

Your example

@value ||= calculate_value

is equivalent to

@value || @value = calculate_value

and is not equivalent to

@value = @value || calculate_value

Therefore the answer is: It is very efficient. It is not re-assigned each time.

Upvotes: 9

Related Questions