Skip to content

Commit

Permalink
Improve performance of method call via delegate_all
Browse files Browse the repository at this point in the history
Currently, `delegatable?` call `private_methods` to check method is
private or not. The `private_methods` generates an Array of methods
per call. So a method call via `delegate_all` generates an extra Array
every time.

This patch use `private_method_defined?` instead of `private_methods`.
This reduce the extra Array.

The inherit argument for `private_method_defined?` is supported since Ruby 2.6.
So this patch only works for >= Ruby 2.6. Rubies old than Ruby 2.6 keep using
`private_methods`.
Ref: https://bugs.ruby-lang.org/issues/14944

Benchmark is here.

```ruby

require "bundler/inline"

gemfile(true) do
  source "https://rubygems.org"
  gem "activerecord"
  gem "sqlite3"
  if ENV["USE_FORKED_GEM"]
    gem "draper", github: "y-yagi/draper", branch: "improve-delegate_all-performance"
  else
    gem "draper"
  end
  gem "benchmark-ips"
end

require "active_record"
require "logger"

ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = nil

ActiveRecord::Schema.define do
  create_table :users, force: true do |t|
    t.string :name, null: false
    t.timestamps
  end
end

ActiveRecord::Base.logger = Logger.new(STDOUT)

class User < ActiveRecord::Base
end

class UserDecorator < Draper::Decorator
  delegate_all

  def decorated_name
    "'#{name}'"
  end
end

User.create!(name: "Dummy User")
u = UserDecorator.decorate(User.first)

Benchmark.ips do |x|
  x.report(ENV["USE_FORKED_GEM"] == "true" ? "forked draper" : "released draper") do
    1000.times { u.decorated_name }
  end

  x.save! ENV["SAVE_FILE"] if ENV["SAVE_FILE"]
  x.compare!
end
```

```bash
$ SAVE_FILE=result.out ruby draper.rb
$ USE_FORKED_GEM=true SAVE_FILE=result.out ruby draper.rb
...
Comparison:
       forked draper:      454.5 i/s
     released draper:       63.8 i/s - 7.12x  (± 0.00) slower
```
  • Loading branch information
y-yagi committed Dec 16, 2021
1 parent 26a18c8 commit 5a03248
Showing 1 changed file with 14 additions and 4 deletions.
18 changes: 14 additions & 4 deletions lib/draper/automatic_delegation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,21 @@ def respond_to_missing?(method, include_private = false)
super || delegatable?(method)
end

# @private
def delegatable?(method)
return if private_methods(false).include?(method)
# The inherit argument for `private_method_defined?` is supported since Ruby 2.6.
if RUBY_VERSION >= "2.6"
# @private
def delegatable?(method)
return if self.class.private_method_defined?(method, false)

object.respond_to?(method)
object.respond_to?(method)
end
else
# @private
def delegatable?(method)
return if private_methods(false).include?(method)

object.respond_to?(method)
end
end

module ClassMethods
Expand Down

0 comments on commit 5a03248

Please sign in to comment.