From a5c9f24603da478366a7fa1a1279f13501e30bbe Mon Sep 17 00:00:00 2001
From: Dillon Welch <daw0328@gmail.com>
Date: Mon, 23 Oct 2017 11:55:12 -0700
Subject: Performance improvements for acts_like? method.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

activesupport/lib/active_support/core_ext/object/acts_like.rb
acts_like?

Add a case statement to use direct symbols instead of string
interpolation for the three scenarios I found in the Rails codebase:
time, date, and string.

For time/date/string, this change prevents two string allocations for
each time the method is called and speeds up the method by ~2.7x. For
other arguments, there is no memory difference and performance
difference is within margin of error.

begin
  require "bundler/inline"
rescue LoadError => e
  $stderr.puts "Bundler version 1.10 or later is required. Please update
                your Bundler"
  raise e
end

gemfile(true) do
  source "https://rubygems.org"

  gem "rails", github: "rails/rails"
  gem "arel", github: "rails/arel"
  gem "benchmark-ips"
end

def allocate_count
  GC.disable
  before = ObjectSpace.count_objects
  yield
  after = ObjectSpace.count_objects
  after.each { |k,v| after[k] = v - before[k] }
  after[:T_HASH] -= 1 # probe effect - we created the before hash.
  GC.enable
  result = after.reject { |k,v| v == 0 }
  GC.start
  result
end

class Object
  def fast_acts_like?(duck)
    case duck
    when :time
      respond_to? :acts_like_time?
    when :date
      respond_to? :acts_like_date?
    when :string
      respond_to? :acts_like_string?
    else
      respond_to? :"acts_like_#{duck}?"
    end
  end
end

puts
puts " acts_like? ".center(80, '=')
puts

obj = ''.freeze

%i(time date string super_hacka).each do |type|
  puts " #{type} ".center(80, '=')

  puts " Memory Usage ".center(80, "=")
  puts

  puts "value.acts_like?"
  puts allocate_count { 1000.times { obj.acts_like?(type) } }

  puts "value.fast_acts_like?"
  puts allocate_count { 1000.times { obj.fast_acts_like?(type) } }

  puts
  puts " Benchmark.ips ".center(80, "=")
  puts

  Benchmark.ips do |x|
    x.report("acts_like?")      { obj.acts_like?(type) }
    x.report("fast_acts_like?") { obj.fast_acts_like?(type) }
    x.compare!
  end
end

================================== acts_like? ==================================

===================================== time =====================================
================================= Memory Usage =================================

value.acts_like?
{:FREE=>-1983, :T_STRING=>2052, :T_IMEMO=>1}
value.fast_acts_like?
{:FREE=>-1}

================================ Benchmark.ips =================================

Warming up --------------------------------------
          acts_like?   104.281k i/100ms
     fast_acts_like?   155.523k i/100ms
Calculating -------------------------------------
          acts_like?      1.688M (±10.7%) i/s -      8.342M in   5.003804s
     fast_acts_like?      4.596M (±12.1%) i/s -     22.551M in   5.000124s

Comparison:
     fast_acts_like?:  4596162.4 i/s
          acts_like?:  1688163.8 i/s - 2.72x  slower

===================================== date =====================================
================================= Memory Usage =================================

value.acts_like?
{:FREE=>-2001, :T_STRING=>2000}
value.fast_acts_like?
{:FREE=>-1}

================================ Benchmark.ips =================================

Warming up --------------------------------------
          acts_like?    85.372k i/100ms
     fast_acts_like?   166.097k i/100ms
Calculating -------------------------------------
          acts_like?      1.720M (± 8.3%) i/s -      8.537M in   5.001003s
     fast_acts_like?      4.695M (±10.1%) i/s -     23.254M in   5.010734s

Comparison:
     fast_acts_like?:  4695493.1 i/s
          acts_like?:  1719637.9 i/s - 2.73x  slower

==================================== string ====================================
================================= Memory Usage =================================

value.acts_like?
{:FREE=>-2001, :T_STRING=>2000}
value.fast_acts_like?
{:FREE=>-1}

================================ Benchmark.ips =================================

Warming up --------------------------------------
          acts_like?   100.221k i/100ms
     fast_acts_like?   182.841k i/100ms
Calculating -------------------------------------
          acts_like?      1.706M (± 7.3%) i/s -      8.519M in   5.022331s
     fast_acts_like?      3.968M (±22.8%) i/s -     18.650M in   5.006762s

Comparison:
     fast_acts_like?:  3967972.9 i/s
          acts_like?:  1705773.7 i/s - 2.33x  slower

================================= super_hacka ==================================
================================= Memory Usage =================================

value.acts_like?
{:FREE=>-2004, :T_STRING=>2002, :T_SYMBOL=>1}
value.fast_acts_like?
{:FREE=>-2003, :T_STRING=>2001, :T_SYMBOL=>1}

================================ Benchmark.ips =================================

Warming up --------------------------------------
          acts_like?   100.344k i/100ms
     fast_acts_like?   101.690k i/100ms
Calculating -------------------------------------
          acts_like?      1.617M (± 7.5%) i/s -      8.128M in   5.055285s
     fast_acts_like?      1.534M (±10.1%) i/s -      7.627M in   5.031052s

Comparison:
          acts_like?:  1617390.7 i/s
     fast_acts_like?:  1533897.3 i/s - same-ish: difference falls within error
---
 activesupport/lib/active_support/core_ext/object/acts_like.rb | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/activesupport/lib/active_support/core_ext/object/acts_like.rb b/activesupport/lib/active_support/core_ext/object/acts_like.rb
index 2eb72f6b3a..403ee20e39 100644
--- a/activesupport/lib/active_support/core_ext/object/acts_like.rb
+++ b/activesupport/lib/active_support/core_ext/object/acts_like.rb
@@ -7,6 +7,15 @@ class Object
   # <tt>x.acts_like?(:date)</tt> to do duck-type-safe comparisons, since classes that
   # we want to act like Time simply need to define an <tt>acts_like_time?</tt> method.
   def acts_like?(duck)
-    respond_to? :"acts_like_#{duck}?"
+    case duck
+    when :time
+      respond_to? :acts_like_time?
+    when :date
+      respond_to? :acts_like_date?
+    when :string
+      respond_to? :acts_like_string?
+    else
+      respond_to? :"acts_like_#{duck}?"
+    end
   end
 end
-- 
cgit v1.2.3