lite-memoize 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +7 -1
- data/.travis.yml +14 -10
- data/CHANGELOG.md +9 -0
- data/Gemfile.lock +1 -1
- data/README.md +99 -13
- data/benchmarks/base.rb +52 -0
- data/benchmarks/memery.rb +44 -0
- data/benchmarks/memist.rb +44 -0
- data/benchmarks/memo-it.rb +44 -0
- data/benchmarks/memoist.rb +46 -0
- data/benchmarks/memoist2.rb +46 -0
- data/benchmarks/memoit.rb +42 -0
- data/benchmarks/memoizable.rb +46 -0
- data/benchmarks/memoizer.rb +46 -0
- data/lib/lite/memoize.rb +1 -1
- data/lib/lite/memoize/alias.rb +190 -0
- data/lib/lite/memoize/instance.rb +24 -35
- data/lib/lite/memoize/klass.rb +4 -3
- data/lib/lite/memoize/mixin.rb +33 -0
- data/lib/lite/memoize/version.rb +1 -1
- metadata +13 -3
- data/lib/lite/memoize/shared.rb +0 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2a2e51485362684ce1520b7eca9484106b5b83c9bc6336b2215dcffa6d56cfed
|
4
|
+
data.tar.gz: 43eb2cd5cb4330f12a503f3b66594b6a0c076bb1a0a991a0735bef1c11fd3cb6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b082b4804dbcedb965b006b0077375195316720d7397b5ed0140b5ae0de8847706d3503e8ca9f33f7c6ce064a0d79c2ff6f25a57a06e3a458ee2506e0f623436
|
7
|
+
data.tar.gz: 0dc03f302a930483a8ca51f114e80b6ed36506153bf1840f0ddcfb221404acd1f307dc77ae1dcf8f4a0a60970513d382954180cb70e49928d7727a41dcdcf48c
|
data/.rubocop.yml
CHANGED
@@ -11,13 +11,19 @@ Layout/EmptyLinesAroundBlockBody:
|
|
11
11
|
Exclude:
|
12
12
|
- 'spec/**/**/*'
|
13
13
|
Layout/EmptyLinesAroundClassBody:
|
14
|
-
EnforcedStyle:
|
14
|
+
EnforcedStyle: empty_lines_except_namespace
|
15
15
|
Layout/EmptyLinesAroundModuleBody:
|
16
16
|
EnforcedStyle: empty_lines_except_namespace
|
17
17
|
Metrics/BlockLength:
|
18
18
|
Exclude:
|
19
19
|
- 'spec/**/**/*'
|
20
20
|
- '*.gemspec'
|
21
|
+
Metrics/ModuleLength:
|
22
|
+
Enabled: false
|
23
|
+
Naming/FileName:
|
24
|
+
Enabled: false
|
25
|
+
Naming/MemoizedInstanceVariableName:
|
26
|
+
Enabled: false
|
21
27
|
Naming/UncommunicativeMethodParamName:
|
22
28
|
Enabled: false
|
23
29
|
Style/Documentation:
|
data/.travis.yml
CHANGED
@@ -1,20 +1,24 @@
|
|
1
|
-
---
|
2
1
|
sudo: false
|
3
2
|
language: ruby
|
4
3
|
cache: bundler
|
5
4
|
rvm:
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
- 2.5
|
6
|
+
- 2.6
|
7
|
+
- ruby-head
|
9
8
|
matrix:
|
10
9
|
fast_finish: true
|
11
10
|
allow_failures:
|
12
|
-
|
11
|
+
- rvm: ruby-head
|
13
12
|
before_install:
|
14
|
-
|
15
|
-
|
13
|
+
- gem update --system
|
14
|
+
- gem install bundler
|
16
15
|
install:
|
17
|
-
|
16
|
+
- bundle install --jobs=3 --retry=3
|
18
17
|
script:
|
19
|
-
|
20
|
-
|
18
|
+
- bundle exec rspec
|
19
|
+
- bundle exec rubocop
|
20
|
+
# - bundle exec fasterer
|
21
|
+
notifications:
|
22
|
+
email: false
|
23
|
+
slack:
|
24
|
+
secure: AqhUTKwgANtYDGb/t49g7Ibvm9B7zgIOQXJSTRC4NXjWSE8xCJ7uYEH6EwmuBPK4/QfK81TgzSC74cQKmzQojpZnTjtHtdNzUbjJ07EKvlaywdRiFNlqs7WpKVs4MdqUd9UpX8f9KAKqZqEsVPmCNuFqApJ66wJiz0ab4tXKqcC8XDhpbzXSPlwhPXXwBUx5gBQZy/sOSWVhSZCJvLO6RwuShzUbA4wHBtMYuAfu5nhHdkK7Jq48aMy973Nh3zvPRTR2dt+25/DPWoDlXtZ6p6JvuTkuzkqsFm13pMH3maxTwZOESFbGD/eFAmeOtJkZuDAfWhajfMh1ESjr4ONRC7UFCXEMZFmAbd24xS57zF5C2de5eM8UFN3O9SEHa4VU3fYFsDOJPFeJoadeyrmkmpnlO4u/EvFH1qrB2wbLurNB7daI30BvQ0UflKE6hBW7TyeswP0o3g5y4fwJORCmG7symRiy9BDkj3LFaANQ7zz1LRabZliiD5ZiNLMiXQ8GLtQ6um1F91ZtX60qj0Qo7z16UJRdWJTkmJmneneJYoP/F5fgKmVRactX+24dEojKUFV9EsF6qOf2W9q5PiOjJ7Ja8O6ubpx3XrDWbZpq1zgJ3DWw7J40TxWzPHAHJRdg1fQ90AmyUMpwJ/4HoRuGD1w3VLeoK9+d9Nrx23FJX4Q=
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
## [1.0.1] - 2019-07-18
|
10
|
+
### Added
|
11
|
+
- Added alias memoization
|
12
|
+
- Added benchmarks
|
13
|
+
### Changed
|
14
|
+
- Improved speed of key generation and lookup for instance
|
15
|
+
- Changed refresh arg to reload
|
16
|
+
- Renamed shared to mixin
|
17
|
+
|
9
18
|
## [1.0.0] - 2019-06-24
|
10
19
|
### Added
|
11
20
|
- Initial project version
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -3,8 +3,7 @@
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/lite-memoize.svg)](http://badge.fury.io/rb/lite-memoize)
|
4
4
|
[![Build Status](https://travis-ci.org/drexed/lite-memoize.svg?branch=master)](https://travis-ci.org/drexed/lite-memoize)
|
5
5
|
|
6
|
-
Lite::Memoize provides an API for caching and memoizing locally expensive calculations including those with parameters.
|
7
|
-
The flexible API allows you to memoize results using class or instance level cache.
|
6
|
+
Lite::Memoize provides an API for caching and memoizing locally expensive calculations including those with parameters. The flexible API allows you to memoize results using alias, class, instance, or mixin level cache.
|
8
7
|
|
9
8
|
**NOTE:** If you are coming from `ActiveMemoize`, please read the [port](#port) section.
|
10
9
|
|
@@ -26,21 +25,63 @@ Or install it yourself as:
|
|
26
25
|
|
27
26
|
## Table of Contents
|
28
27
|
|
29
|
-
* [
|
28
|
+
* [Alias](#alias)
|
30
29
|
* [Klass](#klass)
|
31
30
|
* [Instance](#instance)
|
31
|
+
* [Mixin](#mixin)
|
32
|
+
* [Benchmarks](#benchmarks)
|
33
|
+
* [Port](#port)
|
32
34
|
|
33
|
-
##
|
35
|
+
## Alias
|
34
36
|
|
35
|
-
|
37
|
+
Alias level memoization is the fastest of the available methods, and provides a decent level
|
38
|
+
of control. It's the only one that can also be used to memoize class level methods. Method
|
39
|
+
arguments are automatically watched to cache dynamic values.
|
36
40
|
|
37
|
-
|
38
|
-
|
41
|
+
You can only cache results without access to any information about the `store`.
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
class Movies
|
45
|
+
extend Lite::Memoize::Alias
|
46
|
+
|
47
|
+
class << self
|
48
|
+
extend Lite::Memoize::Alias
|
49
|
+
|
50
|
+
def random
|
51
|
+
HTTP.get('http://movies.com/any')
|
52
|
+
end
|
53
|
+
|
54
|
+
memoize :random
|
55
|
+
end
|
56
|
+
|
57
|
+
def random
|
58
|
+
HTTP.get('http://movies.com/any')
|
59
|
+
end
|
60
|
+
|
61
|
+
memoize :random
|
62
|
+
|
63
|
+
def search(title)
|
64
|
+
HTTP.get("http://movies.com?title=#{title}")
|
65
|
+
end
|
66
|
+
|
67
|
+
memoize :search, as: :find
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
# NOTE: To reload a method just append the reload argument key
|
72
|
+
Movies.random #=> Cached
|
73
|
+
Movies.random(reload: true) #=> New value
|
74
|
+
|
75
|
+
# NOTE: To flush the entire cache
|
76
|
+
Movies.clear_cache #=> New value
|
77
|
+
```
|
39
78
|
|
40
79
|
## Klass
|
41
80
|
|
42
|
-
Class level memoization is the quickest way to get up
|
43
|
-
|
81
|
+
Class level memoization is the quickest way to get up without polluting your class with new methods.
|
82
|
+
It's perfect for short lived or non-altering items like `activerecord` objects.
|
83
|
+
|
84
|
+
You can only cache results without access to any information about the `store`.
|
44
85
|
|
45
86
|
```ruby
|
46
87
|
class Movies
|
@@ -63,7 +104,12 @@ end
|
|
63
104
|
|
64
105
|
## Instance
|
65
106
|
|
66
|
-
Instance level memoization is
|
107
|
+
Instance level memoization is the slowest of the available methods, but it provides
|
108
|
+
the most amount of flexibility and control. It's very useful for creating services or things
|
109
|
+
where control is paramount like clearing the cache or dumping it to JSON. Please read the spec
|
110
|
+
suite to see all available actions. Method arguments are automatically watched to cache dynamic
|
111
|
+
values.
|
112
|
+
|
67
113
|
You can access almost all methods in the `instance.rb` file.
|
68
114
|
|
69
115
|
```ruby
|
@@ -73,16 +119,18 @@ class Movies
|
|
73
119
|
@cache ||= Lite::Memoize::Instance.new
|
74
120
|
end
|
75
121
|
|
122
|
+
# NOTE: This method gets all relevent info like name and args automatically
|
76
123
|
def all
|
77
124
|
cache.memoize { HTTP.get("http://movies.com/all") }
|
78
125
|
end
|
79
126
|
|
80
|
-
def random
|
81
|
-
cache['random'] ||= HTTP.get(
|
127
|
+
def random(type)
|
128
|
+
cache['random'] ||= HTTP.get("http://movies.com/any?type=#{type}")
|
82
129
|
end
|
83
130
|
|
131
|
+
# NOTE: Arguments in the memoize method are optional
|
84
132
|
def search(title)
|
85
|
-
cache.memoize(as: :find,
|
133
|
+
cache.memoize(as: :find, args: [title], reload: !cache.empty?) do
|
86
134
|
HTTP.get("http://movies.com?title=#{title}")
|
87
135
|
end
|
88
136
|
end
|
@@ -90,6 +138,44 @@ class Movies
|
|
90
138
|
end
|
91
139
|
```
|
92
140
|
|
141
|
+
## Mixin
|
142
|
+
|
143
|
+
Mixin level memoization is the leanest of the available methods, and provides a decent level
|
144
|
+
of control. Useful when you want to keep your class light weight.
|
145
|
+
|
146
|
+
You can access all methods to the `Hash` class.
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
class Movies
|
150
|
+
include Lite::Memoize::Mixin
|
151
|
+
|
152
|
+
def all
|
153
|
+
memoize(:all) { HTTP.get("http://movies.com/all") }
|
154
|
+
end
|
155
|
+
|
156
|
+
# NOTE: Arguments in the memoize method are optional with the exception of method name
|
157
|
+
def search(title)
|
158
|
+
memoize(:find, args: [title], reload: false) do
|
159
|
+
HTTP.get("http://movies.com?title=#{title}")
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
```
|
165
|
+
|
166
|
+
## Benchmarks
|
167
|
+
|
168
|
+
The classes ranked from fastest to slowest are `Alias`, `Mixin`, `Klass`, and `Instance`.
|
169
|
+
|
170
|
+
View all how it compares to other libs by running the [benchmarks](https://github.com/drexed/lite-statistics/tree/master/benchmarks).
|
171
|
+
|
172
|
+
## Port
|
173
|
+
|
174
|
+
`Lite::Memoize` is a compatible port of [ActiveMemoize](https://github.com/drexed/active_memoize).
|
175
|
+
|
176
|
+
Switching is as easy as renaming `ActiveMemoize::Klass` to `Lite::Memoize::Klass`
|
177
|
+
and `ActiveMemoize::Instance` to `Lite::Memoize::Instance`.
|
178
|
+
|
93
179
|
## Development
|
94
180
|
|
95
181
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/benchmarks/base.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
%w[lib benchmarks].each { |name| $LOAD_PATH.unshift(name) }
|
4
|
+
|
5
|
+
require 'benchmark/ips'
|
6
|
+
require 'lite/memoize'
|
7
|
+
|
8
|
+
class LiteMemoizeAliasCache
|
9
|
+
|
10
|
+
extend Lite::Memoize::Alias
|
11
|
+
|
12
|
+
def randomize
|
13
|
+
rand(1..99)
|
14
|
+
end
|
15
|
+
|
16
|
+
memoize :randomize
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
class LiteMemoizeInstanceCache
|
21
|
+
|
22
|
+
def cache
|
23
|
+
@cache ||= Lite::Memoize::Instance.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def randomize
|
27
|
+
cache.memoize { rand(1..99) }
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
class LiteMemoizeKlassCache
|
33
|
+
|
34
|
+
extend Lite::Memoize::Klass
|
35
|
+
|
36
|
+
def randomize
|
37
|
+
rand(1..99)
|
38
|
+
end
|
39
|
+
|
40
|
+
memoize :randomize
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
class LiteMemoizeMixinCache
|
45
|
+
|
46
|
+
include Lite::Memoize::Mixin
|
47
|
+
|
48
|
+
def randomize
|
49
|
+
memoize(:randomize) { rand(1..99) }
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'memery'
|
4
|
+
require_relative 'base'
|
5
|
+
|
6
|
+
class MemeryCache
|
7
|
+
|
8
|
+
include Memery
|
9
|
+
|
10
|
+
memoize def randomize
|
11
|
+
rand(1..99)
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
klass_a = LiteMemoizeInstanceCache.new
|
17
|
+
klass_b = LiteMemoizeKlassCache.new
|
18
|
+
klass_c = LiteMemoizeMixinCache.new
|
19
|
+
klass_d = LiteMemoizeAliasCache.new
|
20
|
+
klass_z = MemeryCache.new
|
21
|
+
|
22
|
+
Benchmark.ips do |x|
|
23
|
+
x.report('LM.instance') do
|
24
|
+
klass_a.randomize
|
25
|
+
end
|
26
|
+
|
27
|
+
x.report('LM.klass') do
|
28
|
+
klass_b.randomize
|
29
|
+
end
|
30
|
+
|
31
|
+
x.report('LM.mixin') do
|
32
|
+
klass_c.randomize
|
33
|
+
end
|
34
|
+
|
35
|
+
x.report('LM.alias') do
|
36
|
+
klass_d.randomize
|
37
|
+
end
|
38
|
+
|
39
|
+
x.report('Memery') do
|
40
|
+
klass_z.randomize
|
41
|
+
end
|
42
|
+
|
43
|
+
x.compare!
|
44
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'memist'
|
4
|
+
require_relative 'base'
|
5
|
+
|
6
|
+
class MemistCache
|
7
|
+
|
8
|
+
def randomize
|
9
|
+
rand(1..99)
|
10
|
+
end
|
11
|
+
|
12
|
+
memoize :randomize
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
klass_a = LiteMemoizeInstanceCache.new
|
17
|
+
klass_b = LiteMemoizeKlassCache.new
|
18
|
+
klass_c = LiteMemoizeMixinCache.new
|
19
|
+
klass_d = LiteMemoizeAliasCache.new
|
20
|
+
klass_z = MemistCache.new
|
21
|
+
|
22
|
+
Benchmark.ips do |x|
|
23
|
+
x.report('LM.instance') do
|
24
|
+
klass_a.randomize
|
25
|
+
end
|
26
|
+
|
27
|
+
x.report('LM.klass') do
|
28
|
+
klass_b.randomize
|
29
|
+
end
|
30
|
+
|
31
|
+
x.report('LM.mixin') do
|
32
|
+
klass_c.randomize
|
33
|
+
end
|
34
|
+
|
35
|
+
x.report('LM.alias') do
|
36
|
+
klass_d.randomize
|
37
|
+
end
|
38
|
+
|
39
|
+
x.report('Memist') do
|
40
|
+
klass_z.randomize
|
41
|
+
end
|
42
|
+
|
43
|
+
x.compare!
|
44
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'memo/it'
|
4
|
+
require_relative 'base'
|
5
|
+
|
6
|
+
class MemoitCache
|
7
|
+
|
8
|
+
def randomize
|
9
|
+
memo do
|
10
|
+
rand(1..99)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
klass_a = LiteMemoizeInstanceCache.new
|
17
|
+
klass_b = LiteMemoizeKlassCache.new
|
18
|
+
klass_c = LiteMemoizeMixinCache.new
|
19
|
+
klass_d = LiteMemoizeAliasCache.new
|
20
|
+
klass_z = MemoitCache.new
|
21
|
+
|
22
|
+
Benchmark.ips do |x|
|
23
|
+
x.report('LM.instance') do
|
24
|
+
klass_a.randomize
|
25
|
+
end
|
26
|
+
|
27
|
+
x.report('LM.klass') do
|
28
|
+
klass_b.randomize
|
29
|
+
end
|
30
|
+
|
31
|
+
x.report('LM.mixin') do
|
32
|
+
klass_c.randomize
|
33
|
+
end
|
34
|
+
|
35
|
+
x.report('LM.alias') do
|
36
|
+
klass_d.randomize
|
37
|
+
end
|
38
|
+
|
39
|
+
x.report('Memoit') do
|
40
|
+
klass_z.randomize
|
41
|
+
end
|
42
|
+
|
43
|
+
x.compare!
|
44
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'memoist'
|
4
|
+
require_relative 'base'
|
5
|
+
|
6
|
+
class MemoistCache
|
7
|
+
|
8
|
+
extend Memoist
|
9
|
+
|
10
|
+
def randomize
|
11
|
+
rand(1..99)
|
12
|
+
end
|
13
|
+
|
14
|
+
memoize :randomize
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
klass_a = LiteMemoizeInstanceCache.new
|
19
|
+
klass_b = LiteMemoizeKlassCache.new
|
20
|
+
klass_c = LiteMemoizeMixinCache.new
|
21
|
+
klass_d = LiteMemoizeAliasCache.new
|
22
|
+
klass_z = MemoistCache.new
|
23
|
+
|
24
|
+
Benchmark.ips do |x|
|
25
|
+
x.report('LM.instance') do
|
26
|
+
klass_a.randomize
|
27
|
+
end
|
28
|
+
|
29
|
+
x.report('LM.klass') do
|
30
|
+
klass_b.randomize
|
31
|
+
end
|
32
|
+
|
33
|
+
x.report('LM.mixin') do
|
34
|
+
klass_c.randomize
|
35
|
+
end
|
36
|
+
|
37
|
+
x.report('LM.alias') do
|
38
|
+
klass_d.randomize
|
39
|
+
end
|
40
|
+
|
41
|
+
x.report('Memoist') do
|
42
|
+
klass_z.randomize
|
43
|
+
end
|
44
|
+
|
45
|
+
x.compare!
|
46
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'memoist2'
|
4
|
+
require_relative 'base'
|
5
|
+
|
6
|
+
class Memoist2Cache
|
7
|
+
|
8
|
+
include Memoist2
|
9
|
+
|
10
|
+
def randomize
|
11
|
+
rand(1..99)
|
12
|
+
end
|
13
|
+
|
14
|
+
memoize :randomize
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
klass_a = LiteMemoizeInstanceCache.new
|
19
|
+
klass_b = LiteMemoizeKlassCache.new
|
20
|
+
klass_c = LiteMemoizeMixinCache.new
|
21
|
+
klass_d = LiteMemoizeAliasCache.new
|
22
|
+
klass_z = Memoist2Cache.new
|
23
|
+
|
24
|
+
Benchmark.ips do |x|
|
25
|
+
x.report('LM.instance') do
|
26
|
+
klass_a.randomize
|
27
|
+
end
|
28
|
+
|
29
|
+
x.report('LM.klass') do
|
30
|
+
klass_b.randomize
|
31
|
+
end
|
32
|
+
|
33
|
+
x.report('LM.mixin') do
|
34
|
+
klass_c.randomize
|
35
|
+
end
|
36
|
+
|
37
|
+
x.report('LM.alias') do
|
38
|
+
klass_d.randomize
|
39
|
+
end
|
40
|
+
|
41
|
+
x.report('Memoist2') do
|
42
|
+
klass_z.randomize
|
43
|
+
end
|
44
|
+
|
45
|
+
x.compare!
|
46
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'memoit'
|
4
|
+
require_relative 'base'
|
5
|
+
|
6
|
+
class MemoitCache
|
7
|
+
|
8
|
+
memoize def randomize
|
9
|
+
rand(1..99)
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
klass_a = LiteMemoizeInstanceCache.new
|
15
|
+
klass_b = LiteMemoizeKlassCache.new
|
16
|
+
klass_c = LiteMemoizeMixinCache.new
|
17
|
+
klass_d = LiteMemoizeAliasCache.new
|
18
|
+
klass_z = MemoitCache.new
|
19
|
+
|
20
|
+
Benchmark.ips do |x|
|
21
|
+
x.report('LM.instance') do
|
22
|
+
klass_a.randomize
|
23
|
+
end
|
24
|
+
|
25
|
+
x.report('LM.klass') do
|
26
|
+
klass_b.randomize
|
27
|
+
end
|
28
|
+
|
29
|
+
x.report('LM.mixin') do
|
30
|
+
klass_c.randomize
|
31
|
+
end
|
32
|
+
|
33
|
+
x.report('LM.alias') do
|
34
|
+
klass_d.randomize
|
35
|
+
end
|
36
|
+
|
37
|
+
x.report('Memoit') do
|
38
|
+
klass_z.randomize
|
39
|
+
end
|
40
|
+
|
41
|
+
x.compare!
|
42
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'memoizable'
|
4
|
+
require_relative 'base'
|
5
|
+
|
6
|
+
class MemoizableCache
|
7
|
+
|
8
|
+
include Memoizable
|
9
|
+
|
10
|
+
def randomize
|
11
|
+
rand(1..99)
|
12
|
+
end
|
13
|
+
|
14
|
+
memoize :randomize
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
klass_a = LiteMemoizeInstanceCache.new
|
19
|
+
klass_b = LiteMemoizeKlassCache.new
|
20
|
+
klass_c = LiteMemoizeMixinCache.new
|
21
|
+
klass_d = LiteMemoizeAliasCache.new
|
22
|
+
klass_z = MemoizableCache.new
|
23
|
+
|
24
|
+
Benchmark.ips do |x|
|
25
|
+
x.report('LM.instance') do
|
26
|
+
klass_a.randomize
|
27
|
+
end
|
28
|
+
|
29
|
+
x.report('LM.klass') do
|
30
|
+
klass_b.randomize
|
31
|
+
end
|
32
|
+
|
33
|
+
x.report('LM.mixin') do
|
34
|
+
klass_c.randomize
|
35
|
+
end
|
36
|
+
|
37
|
+
x.report('LM.alias') do
|
38
|
+
klass_d.randomize
|
39
|
+
end
|
40
|
+
|
41
|
+
x.report('Memoizable') do
|
42
|
+
klass_z.randomize
|
43
|
+
end
|
44
|
+
|
45
|
+
x.compare!
|
46
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'memoizer'
|
4
|
+
require_relative 'base'
|
5
|
+
|
6
|
+
class MemoizerCache
|
7
|
+
|
8
|
+
include Memoizer
|
9
|
+
|
10
|
+
def randomize
|
11
|
+
rand(1..99)
|
12
|
+
end
|
13
|
+
|
14
|
+
memoize :randomize
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
klass_a = LiteMemoizeInstanceCache.new
|
19
|
+
klass_b = LiteMemoizeKlassCache.new
|
20
|
+
klass_c = LiteMemoizeMixinCache.new
|
21
|
+
klass_d = LiteMemoizeAliasCache.new
|
22
|
+
klass_z = MemoizerCache.new
|
23
|
+
|
24
|
+
Benchmark.ips do |x|
|
25
|
+
x.report('LM.instance') do
|
26
|
+
klass_a.randomize
|
27
|
+
end
|
28
|
+
|
29
|
+
x.report('LM.klass') do
|
30
|
+
klass_b.randomize
|
31
|
+
end
|
32
|
+
|
33
|
+
x.report('LM.mixin') do
|
34
|
+
klass_c.randomize
|
35
|
+
end
|
36
|
+
|
37
|
+
x.report('LM.alias') do
|
38
|
+
klass_d.randomize
|
39
|
+
end
|
40
|
+
|
41
|
+
x.report('Memoizer') do
|
42
|
+
klass_z.randomize
|
43
|
+
end
|
44
|
+
|
45
|
+
x.compare!
|
46
|
+
end
|
data/lib/lite/memoize.rb
CHANGED
@@ -0,0 +1,190 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lite
|
4
|
+
module Memoize
|
5
|
+
module Alias
|
6
|
+
|
7
|
+
MemoizedMethod = Struct.new(:memoized_method, :ivar, :arity)
|
8
|
+
|
9
|
+
module InstanceMethods
|
10
|
+
|
11
|
+
def memoized_structs(names)
|
12
|
+
ref_obj = self.class.respond_to?(:class_eval) ? singleton_class : self
|
13
|
+
structs = ref_obj.all_memoized_structs
|
14
|
+
return structs if names.empty?
|
15
|
+
|
16
|
+
structs.select { |s| names.include?(s.memoized_method) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def memoize_all(*method_names)
|
20
|
+
memoized_structs(method_names).each do |struct|
|
21
|
+
if struct.arity.zero?
|
22
|
+
__send__(struct.memoized_method)
|
23
|
+
else
|
24
|
+
instance_variable_set(struct.ivar, {})
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def clear_cache(*method_names)
|
30
|
+
memoized_structs(method_names).each do |struct|
|
31
|
+
next unless instance_variable_defined?(struct.ivar)
|
32
|
+
|
33
|
+
remove_instance_variable(struct.ivar)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
alias flush_cache clear_cache
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
class << self
|
42
|
+
|
43
|
+
# rubocop:disable Lint/NestedMethodDefinition
|
44
|
+
def extended(extender)
|
45
|
+
Lite::Memoize::Alias.memoist_eval(extender) do
|
46
|
+
return if singleton_class.method_defined?(:memoized_methods)
|
47
|
+
|
48
|
+
def self.memoized_methods
|
49
|
+
@_memoized_methods ||= []
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
# rubocop:enable Lint/NestedMethodDefinition
|
54
|
+
|
55
|
+
def memoized_ivar_for(method_name, as = nil)
|
56
|
+
"@#{memoized_prefix(as)}_#{escape_punctuation(method_name)}"
|
57
|
+
end
|
58
|
+
|
59
|
+
def unmemoized_method_for(method_name, as = nil)
|
60
|
+
"#{unmemoized_prefix(as)}_#{method_name}".to_sym
|
61
|
+
end
|
62
|
+
|
63
|
+
def memoized_prefix(as = nil)
|
64
|
+
return '_memoized' unless as
|
65
|
+
|
66
|
+
"_memoized_#{as}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def unmemoized_prefix(as = nil)
|
70
|
+
return '_unmemoized' unless as
|
71
|
+
|
72
|
+
"_unmemoized_#{as}"
|
73
|
+
end
|
74
|
+
|
75
|
+
def escape_punctuation(string)
|
76
|
+
string = string.is_a?(String) ? string.dup : string.to_s
|
77
|
+
return string unless string.end_with?('?', '!')
|
78
|
+
|
79
|
+
string.sub!(/\?\Z/, '_query') || string.sub!(/!\Z/, '_bang') || string
|
80
|
+
end
|
81
|
+
|
82
|
+
def memoist_eval(klass, *args, &block)
|
83
|
+
return klass.class_eval(*args, &block) if klass.respond_to?(:class_eval)
|
84
|
+
|
85
|
+
klass.singleton_class.class_eval(*args, &block)
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
def all_memoized_structs
|
91
|
+
@all_memoized_structs ||= begin
|
92
|
+
structs = memoized_methods.dup
|
93
|
+
|
94
|
+
ancestors.grep(Lite::Memoize::Alias).each do |ancestor|
|
95
|
+
ancestor.memoized_methods.each do |m|
|
96
|
+
next if structs.any? { |am| am.memoized_method == m.memoized_method }
|
97
|
+
|
98
|
+
structs << m
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
structs
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def clear_structs
|
107
|
+
@all_memoized_structs = nil
|
108
|
+
end
|
109
|
+
|
110
|
+
# rubocop:disable Metrics/AbcSize, Metrics/BlockLength, Metrics/CyclomaticComplexity
|
111
|
+
# rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
|
112
|
+
def memoize(*method_names)
|
113
|
+
as = method_names.pop[:as] if method_names.last.is_a?(Hash)
|
114
|
+
|
115
|
+
method_names.each do |method_name|
|
116
|
+
unmemoized_method = Lite::Memoize::Alias.unmemoized_method_for(method_name, as)
|
117
|
+
memoized_ivar = Lite::Memoize::Alias.memoized_ivar_for(method_name, as)
|
118
|
+
|
119
|
+
Lite::Memoize::Alias.memoist_eval(self) do
|
120
|
+
include InstanceMethods
|
121
|
+
|
122
|
+
# rubocop:disable Lint/NonLocalExitFromIterator
|
123
|
+
if method_defined?(unmemoized_method)
|
124
|
+
warn "Already memoized #{method_name}"
|
125
|
+
return
|
126
|
+
end
|
127
|
+
# rubocop:enable Lint/NonLocalExitFromIterator
|
128
|
+
|
129
|
+
alias_method unmemoized_method, method_name
|
130
|
+
|
131
|
+
mm = MemoizedMethod.new(method_name, memoized_ivar, instance_method(method_name).arity)
|
132
|
+
memoized_methods << mm
|
133
|
+
|
134
|
+
if mm.arity.zero?
|
135
|
+
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
136
|
+
def #{method_name}(reload: false)
|
137
|
+
skip_cache = reload || !instance_variable_defined?("#{memoized_ivar}")
|
138
|
+
set_cache = skip_cache && !frozen?
|
139
|
+
|
140
|
+
if skip_cache
|
141
|
+
value = #{unmemoized_method}
|
142
|
+
else
|
143
|
+
value = #{memoized_ivar}
|
144
|
+
end
|
145
|
+
|
146
|
+
if set_cache
|
147
|
+
#{memoized_ivar} = value
|
148
|
+
end
|
149
|
+
|
150
|
+
value
|
151
|
+
end
|
152
|
+
RUBY
|
153
|
+
else
|
154
|
+
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
155
|
+
def #{method_name}(*args, reload: false)
|
156
|
+
skip_cache = reload || !(instance_variable_defined?(#{memoized_ivar.inspect}) && #{memoized_ivar} && #{memoized_ivar}.key?(args))
|
157
|
+
set_cache = skip_cache && !frozen?
|
158
|
+
|
159
|
+
if skip_cache
|
160
|
+
value = #{unmemoized_method}(*args)
|
161
|
+
else
|
162
|
+
value = #{memoized_ivar}[args]
|
163
|
+
end
|
164
|
+
|
165
|
+
if set_cache
|
166
|
+
#{memoized_ivar} ||= {}
|
167
|
+
#{memoized_ivar}[args] = value
|
168
|
+
end
|
169
|
+
|
170
|
+
value
|
171
|
+
end
|
172
|
+
RUBY
|
173
|
+
end
|
174
|
+
|
175
|
+
if private_method_defined?(unmemoized_method)
|
176
|
+
private method_name
|
177
|
+
elsif protected_method_defined?(unmemoized_method)
|
178
|
+
protected method_name
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
method_names.size == 1 ? method_names.first : method_names
|
184
|
+
end
|
185
|
+
# rubocop:enable Metrics/MethodLength, Metrics/PerceivedComplexity
|
186
|
+
# rubocop:enable Metrics/AbcSize, Metrics/BlockLength, Metrics/CyclomaticComplexity
|
187
|
+
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -4,59 +4,62 @@ module Lite
|
|
4
4
|
module Memoize
|
5
5
|
class Instance
|
6
6
|
|
7
|
-
include Lite::Memoize::
|
8
|
-
|
9
|
-
CALLER_METHOD_REGEX ||= /`([^']*)'/.freeze
|
7
|
+
include Lite::Memoize::Mixin
|
10
8
|
|
11
9
|
def initialize; end
|
12
10
|
|
13
11
|
def [](key)
|
14
|
-
|
12
|
+
store[key]
|
15
13
|
end
|
16
14
|
|
17
15
|
def []=(key, val)
|
18
|
-
|
16
|
+
store[key] = val
|
19
17
|
end
|
20
18
|
|
21
19
|
def clear
|
22
|
-
|
20
|
+
store.clear
|
23
21
|
end
|
24
22
|
|
23
|
+
alias flush clear
|
24
|
+
|
25
25
|
def delete(key)
|
26
|
-
|
26
|
+
store.delete(key)
|
27
27
|
end
|
28
28
|
|
29
29
|
# :nocov:
|
30
30
|
def each
|
31
|
-
|
31
|
+
store.each { |key, val| yield(key, val) }
|
32
32
|
end
|
33
33
|
# :nocov:
|
34
34
|
|
35
35
|
def empty?
|
36
|
-
|
36
|
+
store.empty?
|
37
37
|
end
|
38
38
|
|
39
39
|
alias blank? empty?
|
40
40
|
|
41
41
|
def key?(key)
|
42
|
-
|
42
|
+
store.key?(key)
|
43
43
|
end
|
44
44
|
|
45
45
|
alias hit? key?
|
46
46
|
|
47
47
|
def keys
|
48
|
-
|
48
|
+
store.keys
|
49
49
|
end
|
50
50
|
|
51
51
|
def merge!(hash)
|
52
|
-
|
52
|
+
store.merge!(hash)
|
53
53
|
end
|
54
54
|
|
55
|
-
def memoize(as: nil,
|
56
|
-
key =
|
57
|
-
return cache[key] if !refresh && cache.key?(key)
|
55
|
+
def memoize(as: nil, args: nil, reload: false, &block)
|
56
|
+
key = caller_key(args || block, as: as)
|
58
57
|
|
59
|
-
|
58
|
+
if reload
|
59
|
+
store[key] = yield
|
60
|
+
else
|
61
|
+
store[key] ||= yield
|
62
|
+
end
|
60
63
|
end
|
61
64
|
|
62
65
|
def present?
|
@@ -66,37 +69,23 @@ module Lite
|
|
66
69
|
alias hits? present?
|
67
70
|
|
68
71
|
def size
|
69
|
-
|
72
|
+
store.size
|
70
73
|
end
|
71
74
|
|
72
75
|
def slice!(*keys)
|
73
|
-
keys.each { |key|
|
74
|
-
|
76
|
+
keys.each { |key| store.delete(key) }
|
77
|
+
store
|
75
78
|
end
|
76
79
|
|
77
80
|
def to_hash
|
78
|
-
|
81
|
+
store
|
79
82
|
end
|
80
83
|
|
81
84
|
alias as_json to_hash
|
82
85
|
alias hits to_hash
|
83
86
|
|
84
87
|
def values
|
85
|
-
|
86
|
-
end
|
87
|
-
|
88
|
-
private
|
89
|
-
|
90
|
-
def caller_locals(block)
|
91
|
-
local_vars = block.binding.local_variables
|
92
|
-
local_vars.flat_map { |name| [name, block.binding.local_variable_get(name)] }
|
93
|
-
end
|
94
|
-
|
95
|
-
def caller_method
|
96
|
-
val = caller(2..2).first[CALLER_METHOD_REGEX, 1]
|
97
|
-
return val unless val.include?('<top (required)>')
|
98
|
-
|
99
|
-
caller(1..1).first[CALLER_METHOD_REGEX, 1]
|
88
|
+
store.values
|
100
89
|
end
|
101
90
|
|
102
91
|
end
|
data/lib/lite/memoize/klass.rb
CHANGED
@@ -4,14 +4,15 @@ module Lite
|
|
4
4
|
module Memoize
|
5
5
|
module Klass
|
6
6
|
|
7
|
-
include Lite::Memoize::
|
7
|
+
include Lite::Memoize::Mixin
|
8
8
|
|
9
9
|
def memoize(method_name, as: nil)
|
10
10
|
inner_method = instance_method(method_name)
|
11
11
|
|
12
12
|
define_method(method_name) do |*args|
|
13
|
-
key = self.class.
|
14
|
-
|
13
|
+
key = self.class.caller_key(args, as: as || method_name)
|
14
|
+
|
15
|
+
self.class.store[key] ||= inner_method.bind(self).call(*args)
|
15
16
|
end
|
16
17
|
end
|
17
18
|
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lite
|
4
|
+
module Memoize
|
5
|
+
module Mixin
|
6
|
+
|
7
|
+
def store
|
8
|
+
@_memoized_methods ||= {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def caller_key(block, as: nil)
|
12
|
+
name = as ? [as] : block.source_location
|
13
|
+
return name.concat(block) if block.is_a?(Array)
|
14
|
+
|
15
|
+
block.binding.local_variables.each_with_object(name) do |local_name, array|
|
16
|
+
array << local_name
|
17
|
+
array << block.binding.local_variable_get(local_name)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def memoize(method_name, args: nil, reload: false)
|
22
|
+
key = "#{method_name}:#{args}"
|
23
|
+
|
24
|
+
if reload
|
25
|
+
store[key] = yield
|
26
|
+
else
|
27
|
+
store[key] ||= yield
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/lite/memoize/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lite-memoize
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Juan Gomez
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-07-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -128,12 +128,22 @@ files:
|
|
128
128
|
- README.md
|
129
129
|
- Rakefile
|
130
130
|
- _config.yml
|
131
|
+
- benchmarks/base.rb
|
132
|
+
- benchmarks/memery.rb
|
133
|
+
- benchmarks/memist.rb
|
134
|
+
- benchmarks/memo-it.rb
|
135
|
+
- benchmarks/memoist.rb
|
136
|
+
- benchmarks/memoist2.rb
|
137
|
+
- benchmarks/memoit.rb
|
138
|
+
- benchmarks/memoizable.rb
|
139
|
+
- benchmarks/memoizer.rb
|
131
140
|
- bin/console
|
132
141
|
- bin/setup
|
133
142
|
- lib/lite/memoize.rb
|
143
|
+
- lib/lite/memoize/alias.rb
|
134
144
|
- lib/lite/memoize/instance.rb
|
135
145
|
- lib/lite/memoize/klass.rb
|
136
|
-
- lib/lite/memoize/
|
146
|
+
- lib/lite/memoize/mixin.rb
|
137
147
|
- lib/lite/memoize/version.rb
|
138
148
|
- lite-memoize.gemspec
|
139
149
|
homepage: http://drexed.github.io/lite-memoize
|
data/lib/lite/memoize/shared.rb
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'digest'
|
4
|
-
|
5
|
-
module Lite
|
6
|
-
module Memoize
|
7
|
-
module Shared
|
8
|
-
|
9
|
-
def cache
|
10
|
-
@cache ||= {}
|
11
|
-
end
|
12
|
-
|
13
|
-
def key(method_name, method_args)
|
14
|
-
return method_name.to_s if method_args.empty?
|
15
|
-
|
16
|
-
method_sha1 = Digest::SHA1.hexdigest(method_args.to_s)
|
17
|
-
"#{method_name}:#{method_sha1}"
|
18
|
-
end
|
19
|
-
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|