Ruby methods perfomance


Few days ago, guys from Netflix released new gem - https://github.com/Netflix/fast_jsonapi.

I’ve started implementation of one feature for this gem, and found interesting spec tests. For example some serialization methods should work at least 25 times faster than AMS. Interesting case!

After some investigation, I found for example - what simple call :symbol.to_s.pluralize takes a lot of time, and broke some test cases.

So, I decided to sort it out, and added some benchmarks.

My current ruby version:

$ ruby -v
ruby 2.4.2p198 (2017-09-14 revision 59899) [x86_64-darwin17]

Let’s start from string and this benchmark. Concatenation works faster, because we just modify existing string object. Without creation temp string object as for example for “+” operation.

require 'benchmark'

n = 100000
Benchmark.bm do |x|
  var1 = "One"
  var2 = "Two"
  x.report ('interpolation') { n.times do ; "Interpolation of #{var1} and #{var2} through interpolation" ; end }
  x.report ('plus') { n.times do ; "Adding of " + var1 + " and " + var2 ; end }
  x.report ('concatenation') { n.times do ; "Concatenation of " << var1 << " and " << var2 ; end }
end

#       user     system      total        real
# interpolation  0.060000   0.010000   0.070000 (  0.071697)
# plus           0.100000   0.000000   0.100000 (  0.105198)
# concatenation  0.050000   0.000000   0.050000 (  0.045616)

Interesting and obvious thing here - when we use directly same data structure, code runs faster. You can read about bang! methods here.

require 'benchmark'

def merge!(array)
  array.inject({}) { |h, e| h.merge!(e => e) }
end

def merge(array)
  array.inject({}) { |h, e| h.merge(e => e) }
end

N = 10_000
array = (0..N).to_a

Benchmark.bm(10) do |x|
  x.report("merge!") { merge!(array) }
  x.report("merge")  { merge(array)  }
end

#                  user     system      total        real
# merge!       0.010000   0.000000   0.010000 (  0.009320)
# merge        1.760000   0.570000   2.330000 (  2.365817)

When we trying to access instance variable, it works about two times faster! Accessor methods are too slow.

require 'benchmark'

class Tester
  attr_accessor :test_var

  def initialize(count)
    @count   = count
    @test_var = 22
  end

  def benchmark
    Benchmark.bm(10) do |x|
      x.report("@test_var") { @count.times { @test_var } }
      x.report("test_var" ) { @count.times { test_var  } }
      x.report("@test_var =")     { @count.times {|i| @test_var = i     } }
      x.report("self.test_var =") { @count.times {|i| self.test_var = i } }
    end
  end
end

metric = Tester.new(100_000_000)
metric.benchmark

#                  user     system      total        real
# @test_var    4.080000   0.050000   4.130000 (  4.481426)
# test_var     4.560000   0.030000   4.590000 (  4.858760)
# @test_var =  4.500000   0.030000   4.530000 (  4.703120)
# self.test_var =  5.150000   0.040000   5.190000 (  5.512396)

Last interesting thing in this post - parallel assigment. Yep - it’s slow :)

require 'benchmark'

COUNT = 10_000_000

Benchmark.bm(15) do |run|
  run.report('parallel') do
    COUNT.times do
      x, y = 100, 200
    end
  end

  run.report('standart') do |run|
    COUNT.times do
      x = 100
      y = 200
    end
  end
end

#                      user     system      total        real
# parallel          0.770000   0.000000   0.770000 (  0.784675)
# standart          0.450000   0.010000   0.460000 (  0.460964)