• P
    Reduce allocations when running AR callbacks. · 796cab45
    Pete Higgins 提交于
    Inspired by @tenderlove's work in
    c363fff2, this reduces the number of
    strings allocated when running callbacks for ActiveRecord instances. I
    measured that using this script:
    
    ```
    require 'objspace'
    require 'active_record'
    require 'allocation_tracer'
    
    ActiveRecord::Base.establish_connection adapter: "sqlite3",
                                            database: ":memory:"
    
    ActiveRecord::Base.connection.instance_eval do
      create_table(:articles) { |t| t.string :name }
    end
    
    class Article < ActiveRecord::Base; end
    a = Article.create name: "foo"
    a = Article.find a.id
    
    N = 10
    result = ObjectSpace::AllocationTracer.trace do
      N.times { Article.find a.id }
    end
    
    result.sort.each do |k,v|
      p k => v
    end
    puts "total: #{result.values.map(&:first).inject(:+)}"
    ```
    
    When I run this against master and this branch I get this output:
    
    ```
    pete@balloon:~/projects/rails/activerecord$ git checkout master
    M Gemfile
    Switched to branch 'master'
    pete@balloon:~/projects/rails/activerecord$ bundle exec ruby benchmark_allocation_with_callback_send.rb > allocations_before
    pete@balloon:~/projects/rails/activerecord$ git checkout remove-dynamic-send-on-built-in-callbacks
    M Gemfile
    Switched to branch 'remove-dynamic-send-on-built-in-callbacks'
    pete@balloon:~/projects/rails/activerecord$ bundle exec ruby benchmark_allocation_with_callback_send.rb > allocations_after
    pete@balloon:~/projects/rails/activerecord$ diff allocations_before allocations_after
    39d38
    <
    {["/home/pete/projects/rails/activesupport/lib/active_support/callbacks.rb",
    81]=>[40, 0, 0, 0, 0, 0]}
    42c41
    < total: 630
    ---
    > total: 590
    
    ```
    
    In addition to this, there are two micro-optimizations present:
    
    * Using `block.call if block` vs `yield if block_given?` when the block was being captured already.
    
    ```
    pete@balloon:~/projects$ cat benchmark_block_call_vs_yield.rb
    require 'benchmark/ips'
    
    def block_capture_with_yield &block
      yield if block_given?
    end
    
    def block_capture_with_call &block
      block.call if block
    end
    
    def no_block_capture
      yield if block_given?
    end
    
    Benchmark.ips do |b|
      b.report("block_capture_with_yield") { block_capture_with_yield }
      b.report("block_capture_with_call") { block_capture_with_call }
      b.report("no_block_capture") { no_block_capture }
    end
    pete@balloon:~/projects$ ruby benchmark_block_call_vs_yield.rb
    Calculating -------------------------------------
    block_capture_with_yield
                            124979 i/100ms
    block_capture_with_call
                            138340 i/100ms
        no_block_capture    136827 i/100ms
    -------------------------------------------------
    block_capture_with_yield
                          5703108.9 (±2.4%) i/s -   28495212 in   4.999368s
    block_capture_with_call
                          6840730.5 (±3.6%) i/s -   34169980 in   5.002649s
        no_block_capture  5821141.4 (±2.8%) i/s -   29144151 in   5.010580s
    ```
    
    * Defining and calling methods instead of using send.
    
    ```
    pete@balloon:~/projects$ cat benchmark_method_call_vs_send.rb
    require 'benchmark/ips'
    
    class Foo
      def tacos
        nil
      end
    end
    
    my_foo = Foo.new
    
    Benchmark.ips do |b|
      b.report('send') { my_foo.send('tacos') }
      b.report('call') { my_foo.tacos }
    end
    pete@balloon:~/projects$ ruby benchmark_method_call_vs_send.rb
    Calculating -------------------------------------
                    send     97736 i/100ms
                    call    151142 i/100ms
    -------------------------------------------------
                    send  2683730.3 (±2.8%) i/s -   13487568 in   5.029763s
                    call  8005963.9 (±2.7%) i/s -   40052630 in   5.006604s
    ```
    
    The result of this is making typical ActiveRecord operations slightly faster:
    
    https://gist.github.com/phiggins/e46e51dcc7edb45b5f98
    796cab45
core.rb 18.9 KB