Skip to content

Conversation

@dnesteryuk
Copy link
Member

@dnesteryuk dnesteryuk commented Jul 15, 2020

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.

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.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.03%) to 95.792% when pulling c7c18c9 on dnesteryuk:attr_delegation into ab3238d on ruby-grape:master.

1 similar comment
@coveralls
Copy link

Coverage Status

Coverage increased (+0.03%) to 95.792% when pulling c7c18c9 on dnesteryuk:attr_delegation into ab3238d on ruby-grape:master.

@coveralls
Copy link

coveralls commented Jul 15, 2020

Coverage Status

Coverage increased (+0.1%) to 95.898% when pulling 2023ea8 on dnesteryuk:attr_delegation into ab3238d on ruby-grape:master.

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.
@grape-bot
Copy link

1 Warning
⚠️ There’re library changes, but not tests. That’s OK as long as you’re refactoring existing code.

Generated by 🚫 danger

1 similar comment
@grape-bot
Copy link

grape-bot commented Jul 15, 2020

1 Warning
⚠️ There’re library changes, but not tests. That’s OK as long as you’re refactoring existing code.

Generated by 🚫 danger

@LeFnord
Copy link
Member

LeFnord commented Jul 15, 2020

thanks @dnesteryuk … especially for the explanation

but … can it have implications on upgrade?

@dnesteryuk
Copy link
Member Author

@LeFnord I don't see how it might impact existing code in projects which use grape entity 🤔

@LeFnord LeFnord merged commit 5d6b712 into ruby-grape:master Jul 15, 2020
@dnesteryuk dnesteryuk deleted the attr_delegation branch July 16, 2020 11:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

4 participants