Skip to content

Commit 5d6b712

Browse files
authored
pass options to delegators when they accept it (#336)
This change reduces hash allocations. Here is a simplified example what was happening. def do_not_accept_keywords(**) end 100.times { do_not_accept_keywords(hash: true) } After measuring via memory profiler, we got this: Total allocated: 69600 bytes (300 objects) allocated memory by location ----------------------------------- 69600 profile/sample.rb:15 It points to a line where the method gets called. So, every call created a separate hash. It was happening for `Grape::Entity::Delegator::PlainObject`. If options get extracted, the picture is better. def do_not_accept_keywords(**) end opts = { hash: true } 100.times { do_not_accept_keywords(opts) } Allocation: Total allocated: 46632 bytes (201 objects) allocated memory by location ----------------------------------- 46400 profile/sample.rb:15 232 profile/sample.rb:13 However, there is no object allocation if nothing is passed to the method. options. Total allocated: 0 bytes (0 objects) Btw, if a method "swallows" arguments, but nothing is passed, there is still allocation. def do_not_accept_keywords(**) end 100.times { do_not_accept_keywords } Result: Total allocated: 23200 bytes (100 objects) allocated memory by location ----------------------------------- 23200 profile/sample.rb:15 The splat operator brings its cost. So, now Grape Entity checks whether the delegetor accepts options before passing them. I measured this change against our production: **Before:** Total allocated: 1512362 bytes (12328 objects) allocated memory by location ----------------------------------- 605520 /usr/local/bundle/gems/grape-entity-0.8.0/lib/grape_entity/entity.rb:537 **After:** Total allocated: 908402 bytes (9733 objects) allocated memory by location ----------------------------------- 18000 /app/grape-entity/lib/grape_entity/entity.rb:553 0.57 MB of RAM were saved while serving the identical request.
1 parent ab3238d commit 5d6b712

File tree

5 files changed

+14
-4
lines changed

5 files changed

+14
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
* Your contribution here.
1010
* [#333](https://github.com/ruby-grape/grape-entity/pull/333): Fix typo in CHANGELOG.md - [@eitoball](https://github.com/eitoball).
11+
* [#336](https://github.com/ruby-grape/grape-entity/pull/336): Pass options to delegators when they accept it - [@dnesteryuk](https://github.com/dnesteryuk).
1112

1213
### 0.8.0 (2020-02-18)
1314

lib/grape_entity/delegator/fetchable_object.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ module Grape
44
class Entity
55
module Delegator
66
class FetchableObject < Base
7-
def delegate(attribute, **)
7+
def delegate(attribute)
88
object.fetch attribute
99
end
1010
end

lib/grape_entity/delegator/openstruct_object.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ module Grape
44
class Entity
55
module Delegator
66
class OpenStructObject < Base
7-
def delegate(attribute, **)
7+
def delegate(attribute)
88
object.send attribute
99
end
1010
end

lib/grape_entity/delegator/plain_object.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ module Grape
44
class Entity
55
module Delegator
66
class PlainObject < Base
7-
def delegate(attribute, **)
7+
def delegate(attribute)
88
object.send attribute
99
end
1010

lib/grape_entity/entity.rb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ def hash_access=(value)
130130
:to_sym
131131
end
132132
end
133+
134+
def delegation_opts
135+
@delegation_opts ||= { hash_access: hash_access }
136+
end
133137
end
134138

135139
@formatters = {}
@@ -479,6 +483,9 @@ def initialize(object, options = {})
479483
@object = object
480484
@options = options.is_a?(Options) ? options : Options.new(options)
481485
@delegator = Delegator.new(object)
486+
487+
# Why not `arity > 1`? It might be negative https://ruby-doc.org/core-2.6.6/Method.html#method-i-arity
488+
@delegator_accepts_opts = @delegator.method(:delegate).arity != 1
482489
end
483490

484491
def root_exposures
@@ -531,8 +538,10 @@ def value_for(key, options = Options.new)
531538
def delegate_attribute(attribute)
532539
if is_defined_in_entity?(attribute)
533540
send(attribute)
541+
elsif @delegator_accepts_opts
542+
delegator.delegate(attribute, self.class.delegation_opts)
534543
else
535-
delegator.delegate(attribute, hash_access: self.class.hash_access)
544+
delegator.delegate(attribute)
536545
end
537546
end
538547

0 commit comments

Comments
 (0)