When performance isn’t obvious

I was writing some Ruby code recently, and although it didn't need to be optimised, I found myself using both x * 0.5 and x / 2 in the same method, which got me thinking, which way is quickest? It's not like either is any easier/harder to read, so it kind of made sense to see which operation is actually fastest.

My test code is a very simple benchmark of applying each operation 100,000,000 times in a vanilla IRB session on my MacBook M1 Pro, and displaying the time taken to finish.

n = 100_000_000
a = 10

time = Benchmark.measure do
  n.times do
    a * 0.5
  end
end
puts "Multiplication by 0.5: #{time.real} seconds"

time = Benchmark.measure do
  n.times do
    a / 2
  end
end
puts "Division by 2: #{time.real} seconds"

time = Benchmark.measure do
  n.times do
    a >> 1
  end
end
puts "Bitshift by 1 to the right: #{time.real} seconds"
Multiplication by 0.5: 3.275779000017792 seconds
Division by 2: 2.3231279999017715 seconds
Bitshift by 1 to the right: 3.062190000084229 seconds

My assumption would be that multiplication would be faster than division because, as we all know, multiplication is the faster operation right? Turns out, no, division is faster. Presumably the Ruby runtime is having to do a conversion for the integer to a float for the multiplication benchmark, maybe the runtime is doing some clever optimisation to make the division quicker. If so, can we beat it by simply bitshifting the integer? We know in this benchmark that we always have an even integer, so a simple bitshift to the right will half the input number and surly that will be quicker? No, division still beats bitshifting :shrug:. No idea why, and it's not that important to understand exactly why one is better, as I always want to write code that the runtime/compiler can use to produce the best outcome.

It's very possible that Ruby, being an interrupted language, is performing lots of it's own optimisation, which is great as we can take advantage of this, but sometimes it might not be an obvious.

What happens if our number is a float? Does ruby still perform some kind of magic to make division quicker?

n = 100_000_000
a = 10.0

time = Benchmark.measure do
  n.times do
    a * 0.5
  end
end
puts "Multiplication by 0.5: #{time.real} seconds"

time = Benchmark.measure do
  n.times do
    a / 2
  end
end
puts "Division by 2: #{time.real} seconds"
Multiplication by 0.5: 2.7136590000009164 seconds
Division by 2: 3.2784969999920577 seconds

No, now we see that multiplication wins, which to me, makes more sense as we don't pay the cost of the type conversion.

Is this a useful optimisation? Over 100 million iterations, and we saved ~1 second, which is certainly not going to make a great impact for most algorithms, but if you've got a calculation that does multiple operations, and gets called a lot, then it can't hurt to make sure you're using the fastest operators.

About the Author

Phil Balchin is a full-time software developer at Zendesk, previously at Heroku/Salesforce, and Kyan, as well as a part-time photographer living in Guildford, Surrey, UK.

facebook.com/phil.balchin | instagram.com/maniacalrobot | last.fm/users/maniacalrobot | picfair.com/maniacalrobot | maniacalrobot.tumblr.com | twitter.com/maniacalrobot