/ RAILS

Rails 6 faster & memory efficient Date#advance

Rails 6 includes a change in Date#advance method that optimised memory usage and time required to perform advance operation.

Previously, advance operation on Date class used to duplicate options (arguments) passed to the method. These options were used up and deleted from the options hash in advance method.

Before Rails 6

def advance(options)
  options = options.dup
  d = self
  d = d >> options.delete(:years) * 12 if options[:years]
  d = d >> options.delete(:months)     if options[:months]
  d = d +  options.delete(:weeks) * 7  if options[:weeks]
  d = d +  options.delete(:days)       if options[:days]
  d
end

As we can see, there is really no need to dup the options has received in the argument.

After Rails 6

With the pull request the behavior was changed to not duplicate the options has as given below.

def advance(options)
  d = self
  d = d >> options[:years] * 12 if options[:years]
  d = d >> options[:months] if options[:months]
  d = d + options[:weeks] * 7 if options[:weeks]
  d = d + options[:days] if options[:days]
  d
end

This improved the speed of Date#advance operation also it allocates less objects (thus memory) in order to perform the operation.

Benchmarking is available on this pull request

Mentioning Benchmark performance below for the reference.

Memory comparison:

Options: {:years=>1, :months=>1, :weeks=>1, :days=>1}
Calculating -------------------------------------
              master   576.000  memsize (     0.000  retained)
                         5.000  objects (     0.000  retained)
                         0.000  strings (     0.000  retained)
      advance_no_dup   288.000  memsize (     0.000  retained)
                         4.000  objects (     0.000  retained)
                         0.000  strings (     0.000  retained)

Comparison:
      advance_no_dup:        288 allocated
              master:        576 allocated - 2.00x more

Options: {:years=>1}
Calculating -------------------------------------
              master   264.000  memsize (     0.000  retained)
                         2.000  objects (     0.000  retained)
                         0.000  strings (     0.000  retained)
      advance_no_dup    72.000  memsize (     0.000  retained)
                         1.000  objects (     0.000  retained)
                         0.000  strings (     0.000  retained)

Comparison:
      advance_no_dup:         72 allocated
              master:        264 allocated - 3.67x more

Options: {:weeks=>1}
Calculating -------------------------------------
              master   264.000  memsize (     0.000  retained)
                         2.000  objects (     0.000  retained)
                         0.000  strings (     0.000  retained)
      advance_no_dup    72.000  memsize (     0.000  retained)
                         1.000  objects (     0.000  retained)
                         0.000  strings (     0.000  retained)

Comparison:
      advance_no_dup:         72 allocated
              master:        264 allocated - 3.67x more

Peformance comparison:

Options: {:years=>1, :months=>1, :weeks=>1, :days=>1}
Warming up --------------------------------------
              master    27.740k i/100ms
      advance_no_dup    37.705k i/100ms
Calculating -------------------------------------
              master    338.511k (± 5.9%) i/s -      1.692M in   5.020333s
      advance_no_dup    572.980k (± 3.7%) i/s -      2.866M in   5.008680s

Comparison:
      advance_no_dup:   572979.7 i/s
              master:   338510.9 i/s - 1.69x  slower

Options: {:years=>1}
Warming up --------------------------------------
              master    53.313k i/100ms
      advance_no_dup   115.016k i/100ms
Calculating -------------------------------------
              master    639.715k (± 1.7%) i/s -      3.199M in   5.001851s
      advance_no_dup      1.579M (± 6.4%) i/s -      7.936M in   5.053876s

Comparison:
      advance_no_dup:  1579251.7 i/s
              master:   639714.8 i/s - 2.47x  slower

Options: {:weeks=>1}
Warming up --------------------------------------
              master    57.353k i/100ms
      advance_no_dup   129.141k i/100ms
Calculating -------------------------------------
              master    674.113k (± 3.4%) i/s -      3.384M in   5.025973s
      advance_no_dup      1.911M (± 2.5%) i/s -      9.556M in   5.004496s

Comparison:
      advance_no_dup:  1910739.3 i/s
              master:   674112.6 i/s - 2.83x  slower

Reference