提交 b0cc7d98 编写于 作者: J Jean Boussier

Refactor MemoryStore to use Hash ordering rather than key access times

This is mainly to simplify the code and use less memory, as large hash can use quite a lot:

```ruby
>> ObjectSpace.memsize_of(1000.times.map { |i| [i, i]}.to_h)
=> 28768
>> ObjectSpace.memsize_of(10_000.times.map { |i| [i, i]}.to_h)
=> 458848
```

The performance is mostly not impacted, if not slightly better:

```ruby
require 'benchmark/ips'
require 'active_support/all'
@store = ActiveSupport::Cache::MemoryStore.new
@store.write("small", "small")
Benchmark.ips do |x|
  x.report("read:miss") { @store.read("miss") }
  x.report("read:small") { @store.read("small") }
  x.report("write:small") { @store.write("small", "small") }
end
```

6.0.3.2:
```
Warming up --------------------------------------
           read:miss    42.466k i/100ms
          read:small    25.315k i/100ms
         write:small    17.826k i/100ms
Calculating -------------------------------------
           read:miss    426.923k (± 1.9%) i/s -      2.166M in   5.074890s
          read:small    248.518k (± 2.7%) i/s -      1.266M in   5.097049s
         write:small    180.388k (± 1.6%) i/s -    909.126k in   5.041238s
```

This branch:
```
Warming up --------------------------------------
           read:miss    42.040k i/100ms
          read:small    28.364k i/100ms
         write:small    19.361k i/100ms
Calculating -------------------------------------
           read:miss    417.814k (± 2.1%) i/s -      2.102M in   5.033186s
          read:small    278.950k (± 2.8%) i/s -      1.418M in   5.088135s
         write:small    193.384k (± 1.8%) i/s -    968.050k in   5.007446s
```
上级 0f4258f6
......@@ -22,7 +22,6 @@ def initialize(options = nil)
options ||= {}
super(options)
@data = {}
@key_access = {}
@max_size = options[:size] || 32.megabytes
@max_prune_time = options[:max_prune_time] || 2
@cache_size = 0
......@@ -39,7 +38,6 @@ def self.supports_cache_versioning?
def clear(options = nil)
synchronize do
@data.clear
@key_access.clear
@cache_size = 0
end
end
......@@ -65,7 +63,7 @@ def prune(target_size, max_time = nil)
start_time = Concurrent.monotonic_time
cleanup
instrument(:prune, target_size, from: @cache_size) do
keys = synchronize { @key_access.keys.sort { |a, b| @key_access[a].to_f <=> @key_access[b].to_f } }
keys = synchronize { @data.keys }
keys.each do |key|
delete_entry(key, **options)
return if @cache_size <= target_size || (max_time && Concurrent.monotonic_time - start_time > max_time)
......@@ -121,30 +119,29 @@ def cached_size(key, entry)
end
def read_entry(key, **options)
entry = @data[key]
entry = nil
synchronize do
entry = @data.delete(key)
if entry
@data[key] = entry
entry = entry.dup
entry.dup_value!
@key_access[key] = Time.now.to_f
else
@key_access.delete(key)
end
end
entry&.dup_value!
entry
end
def write_entry(key, entry, **options)
entry.dup_value!
synchronize do
old_entry = @data[key]
return false if @data.key?(key) && options[:unless_exist]
return false if options[:unless_exist] && @data.key?(key)
old_entry = @data.delete(key)
if old_entry
@cache_size -= (old_entry.size - entry.size)
else
@cache_size += cached_size(key, entry)
end
@key_access[key] = Time.now.to_f
@data[key] = entry
prune(@max_size * 0.75, @max_prune_time) if @cache_size > @max_size
true
......@@ -153,7 +150,6 @@ def write_entry(key, entry, **options)
def delete_entry(key, **options)
synchronize do
@key_access.delete(key)
entry = @data.delete(key)
@cache_size -= cached_size(key, entry) if entry
!!entry
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册