steveklabnik / request_store

Per-request global storage for Rack.
https://github.com/steveklabnik/request_store
MIT License
1.47k stars 87 forks source link

RequestStore#fetch without &block #58

Closed chopraanmol1 closed 6 years ago

chopraanmol1 commented 6 years ago

&block inside method definitions creates additional proc which consumes more time and memory.

One of the usecase of RequestStore is to memoize value on per request basis. Currently, in this scenario while using RequestStore#fetch unused proc will be created multiple times

Benchmark:

2.4.3 :001 > require 'memory_profiler'
 => true
2.4.3 :002 > require 'request_store'
 => true
2.4.3 :003 > require 'benchmark/ips'
 => true
2.4.3 :004 >
2.4.3 :005 >   module RequestStore
2.4.3 :006?>     def self.fetch_without_proc_creation(key)
2.4.3 :007?>         # Suggested
2.4.3 :008 >           store[key] = yield unless exist?(key)
2.4.3 :009?>         store[key]
2.4.3 :010?>       end
2.4.3 :011?>     def self.fetch_with_proc_creation(key, &block)
2.4.3 :012?>         # current
2.4.3 :013 >           store[key] = yield unless exist?(key)
2.4.3 :014?>         store[key]
2.4.3 :015?>       end
2.4.3 :016?>   end ; nil
 => nil
2.4.3 :017 >
2.4.3 :018 >   Benchmark.ips do |x|
2.4.3 :019 >       x.config(:time => 5, :warmup => 2)
2.4.3 :020?>     x.report("with &block") { RequestStore.fetch_with_proc_creation(:a){0} }
2.4.3 :021?>
2.4.3 :022 >       x.report("without &block") {RequestStore.fetch_without_proc_creation(:b){1}}
2.4.3 :023?>
2.4.3 :024 >       x.compare!
2.4.3 :025?>   end ; nil
Warming up --------------------------------------
         with &block   101.397k i/100ms
      without &block   157.885k i/100ms
Calculating -------------------------------------
         with &block      1.474M (± 2.6%) i/s -      7.402M in   5.024766s
      without &block      2.554M (± 1.9%) i/s -     12.789M in   5.009695s

Comparison:
      without &block:  2553852.9 i/s
         with &block:  1474212.3 i/s - 1.73x  slower

 => nil
2.4.3 :026 >
2.4.3 :027 >   MemoryProfiler.report{ 100.times{ RequestStore.fetch_with_proc_creation(:aa){0} } }.pretty_print(detailed_report: false, allocated_strings: false, retained_strings: 0)
Total allocated: 8000 bytes (100 objects)
Total retained:  0 bytes (0 objects)

Allocated String Report
-----------------------------------

 => nil
2.4.3 :028 >
2.4.3 :029 >   MemoryProfiler.report{ 100.times{ RequestStore.fetch_without_proc_creation(:bb){1} } }.pretty_print(detailed_report: false, allocated_strings: false, retained_strings: 0)
Total allocated: 0 bytes (0 objects)
Total retained:  0 bytes (0 objects)

Allocated String Report
-----------------------------------

 => nil
2.4.3 :030 >
chopraanmol1 commented 6 years ago

Benchmarking Script

require 'memory_profiler'
require 'request_store'
require 'benchmark/ips'

module RequestStore
  def self.fetch_without_proc_creation(key)
    # Suggested
    store[key] = yield unless exist?(key)
    store[key]
  end
  def self.fetch_with_proc_creation(key, &block)
    # current
    store[key] = yield unless exist?(key)
    store[key]
  end
end ; nil

Benchmark.ips do |x|
  x.config(:time => 5, :warmup => 2)
  x.report("with &block") { RequestStore.fetch_with_proc_creation(:a){0} }

  x.report("without &block") {RequestStore.fetch_without_proc_creation(:b){1}}

  x.compare!
end ; nil

MemoryProfiler.report{ 100.times{ RequestStore.fetch_with_proc_creation(:aa){0} } }.pretty_print(detailed_report: false, allocated_strings: false, retained_strings: 0)

MemoryProfiler.report{ 100.times{ RequestStore.fetch_without_proc_creation(:bb){1} } }.pretty_print(detailed_report: false, allocated_strings: false, retained_strings: 0)
steveklabnik commented 6 years ago

Neat! I am happy to merge, but can't release a new version right this second; expect me to do so within a week. Thank you so much!

chopraanmol1 commented 6 years ago

Ya sure no problem

chopraanmol1 commented 6 years ago

Hey, @steveklabnik just wanted to remind you about merging this PR as it has already been 2 weeks.

steveklabnik commented 6 years ago

Yes, sorry. cutting a release. sorry for the delay.

steveklabnik commented 6 years ago

published as 1.4.1

lvangool commented 6 years ago

Nice work @chopraanmol1!