Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

micro optimizations in allocating hashes and arrays #1915

Merged
merged 1 commit into from
Oct 6, 2019
Merged

micro optimizations in allocating hashes and arrays #1915

merged 1 commit into from
Oct 6, 2019

Conversation

dnesteryuk
Copy link
Member

There were a few places where hashes could be mutated without side effects, they already work with copies of original hashes. So, merge! replaced some of merge calls.

Also, Grape::Util::StackableValues, Grape::Util::ReverseStackableValues and Grape::Util::InheritableValues got a base class which keeps common methods
to avoid duplication. Besides, that a [] and keys methods were optimized to allocate less arrays when it is possible.

There is an additional benchmark which measures more complex scenario. Unfortunately, there isn't noticeable difference in performance between the master and this change. However, I also profiled the same endpoint as the benchmark has.

Before this change:

 Measure Mode: memory
 Thread ID: 47216803207600
 Fiber ID: 47216805628400
 Total: 40536.000000
 Sort by: self_time

 %self      total      self      wait     child     calls  name
 13.85   6848.000  5616.000     0.000  1232.000       15  *Hash#each_pair
 9.73    3944.000  3944.000     0.000     0.000       17   Hash#merge
 8.80    5088.000  3568.000     0.000  1520.000        8  *Array#map
 8.57   11720.000  3472.000     0.000  8248.000       27  *Class#new
 4.54    1840.000  1840.000     0.000     0.000       46   Symbol#to_s
 4.44    1800.000  1800.000     0.000     0.000       20  *Grape::Util::StackableValues#[]
 3.36    1440.000  1360.000     0.000    80.000        5   Grape::Endpoint#run_filters
 2.88    3040.000  1168.000     0.000  1872.000        9  *Hash#each
 2.37    1200.000   960.000     0.000   240.000        6   <Class::Grape::Router>#normalize_path
 2.29    1088.000   928.000     0.000   160.000        4   ActiveSupport::HashWithIndifferentAccess#[]=
 2.21    1128.000   896.000     0.000   232.000        8  *Kernel#dup
 2.07     840.000   840.000     0.000     0.000        9   String#split
 1.64   12008.000   664.000     0.000 11344.000        1   Grape::Endpoint#run_validators

After this change:

 Measure Mode: memory
 Thread ID: 47390769740200
 Fiber ID: 47390772121400
 Total: 37296.000000
 Sort by: self_time

 %self      total      self      wait     child     calls  name
 15.06   6848.000  5616.000     0.000  1232.000       15  *Hash#each_pair
 9.57    5088.000  3568.000     0.000  1520.000        8  *Array#map
 9.31   11720.000  3472.000     0.000  8248.000       27  *Class#new
 4.93    1840.000  1840.000     0.000     0.000       46   Symbol#to_s
 4.35    1624.000  1624.000     0.000     0.000        7   Hash#merge
 3.65    1440.000  1360.000     0.000    80.000        5   Grape::Endpoint#run_filters
 3.13    3040.000  1168.000     0.000  1872.000        9  *Hash#each
 2.57    1200.000   960.000     0.000   240.000        6   <Class::Grape::Router>#normalize_path
 2.49    1088.000   928.000     0.000   160.000        4   ActiveSupport::HashWithIndifferentAccess#[]=
 2.40    1128.000   896.000     0.000   232.000        8  *Kernel#dup
 2.25     840.000   840.000     0.000     0.000        9   String#split
 2.15    1400.000   800.000     0.000   600.000       20  *Grape::Util::StackableValues#[]
 1.78   11768.000   664.000     0.000 11104.000        1   Grape::Endpoint#run_validators
  • The total memory usage went down from 40536 to 37296 bytes.
  • Hash#merge moved from second position to 5th.
  • Grape::Util::StackableValues#[] moved from 6th to 12th.

There were a few places where hashes could be mutated without side effects,
they already work with copies of original hashes. So, `merge!` replaced some
of `merge` calls.

Also, `Grape::Util::StackableValues`, `Grape::Util::ReverseStackableValues` and
`Grape::Util::InheritableValues` got a base class which keeps common methods
to avoid duplication. Besides, that a `[]` and `keys` methods were optimized
to allocate less arrays when it is possible.

There is an additional benchmark which measures more complex scenario. Unfortunately,
there isn't noticeable difference in performance between the master and this change.
However, I also profiled the same endpoint as the benchmark has.

Before this change:

     Measure Mode: memory
     Thread ID: 47216803207600
     Fiber ID: 47216805628400
     Total: 40536.000000
     Sort by: self_time

     %self      total      self      wait     child     calls  name
     13.85   6848.000  5616.000     0.000  1232.000       15  *Hash#each_pair
     9.73    3944.000  3944.000     0.000     0.000       17   Hash#merge
     8.80    5088.000  3568.000     0.000  1520.000        8  *Array#map
     8.57   11720.000  3472.000     0.000  8248.000       27  *Class#new
     4.54    1840.000  1840.000     0.000     0.000       46   Symbol#to_s
     4.44    1800.000  1800.000     0.000     0.000       20  *Grape::Util::StackableValues#[]
     3.36    1440.000  1360.000     0.000    80.000        5   Grape::Endpoint#run_filters
     2.88    3040.000  1168.000     0.000  1872.000        9  *Hash#each
     2.37    1200.000   960.000     0.000   240.000        6   <Class::Grape::Router>#normalize_path
     2.29    1088.000   928.000     0.000   160.000        4   ActiveSupport::HashWithIndifferentAccess#[]=
     2.21    1128.000   896.000     0.000   232.000        8  *Kernel#dup
     2.07     840.000   840.000     0.000     0.000        9   String#split
     1.64   12008.000   664.000     0.000 11344.000        1   Grape::Endpoint#run_validators

After this change:

     Measure Mode: memory
     Thread ID: 47390769740200
     Fiber ID: 47390772121400
     Total: 37296.000000
     Sort by: self_time

     %self      total      self      wait     child     calls  name
     15.06   6848.000  5616.000     0.000  1232.000       15  *Hash#each_pair
     9.57    5088.000  3568.000     0.000  1520.000        8  *Array#map
     9.31   11720.000  3472.000     0.000  8248.000       27  *Class#new
     4.93    1840.000  1840.000     0.000     0.000       46   Symbol#to_s
     4.35    1624.000  1624.000     0.000     0.000        7   Hash#merge
     3.65    1440.000  1360.000     0.000    80.000        5   Grape::Endpoint#run_filters
     3.13    3040.000  1168.000     0.000  1872.000        9  *Hash#each
     2.57    1200.000   960.000     0.000   240.000        6   <Class::Grape::Router>#normalize_path
     2.49    1088.000   928.000     0.000   160.000        4   ActiveSupport::HashWithIndifferentAccess#[]=
     2.40    1128.000   896.000     0.000   232.000        8  *Kernel#dup
     2.25     840.000   840.000     0.000     0.000        9   String#split
     2.15    1400.000   800.000     0.000   600.000       20  *Grape::Util::StackableValues#[]
     1.78   11768.000   664.000     0.000 11104.000        1   Grape::Endpoint#run_validators

 - The total memory usage went down from 40536 to 37296 bytes.
 - `Hash#merge` moved from second position to 5th.
 - `Grape::Util::StackableValues#[]` moved from 6th to 12th.
@grape-bot
Copy link

grape-bot commented Oct 6, 2019

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

Generated by 🚫 danger

@dblock dblock merged commit deeb826 into ruby-grape:master Oct 6, 2019
@dblock
Copy link
Member

dblock commented Oct 6, 2019

👍

@dnesteryuk dnesteryuk deleted the chore/micro-optimization-in-hashes-arrays branch October 6, 2019 20:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants