memo_wise 1.1.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2fa12aeb35a6c4ca923eb23d70867b41600795143570f3d7c5b6444c76547980
4
- data.tar.gz: bac4074e758731ac5d20925ab32ef3cd78d10a1c030228df76c1868e104e1a40
3
+ metadata.gz: 9d94c6c36ed049177dffb78f2bc5b0196d06e7b334643770163b7140ee2e77e2
4
+ data.tar.gz: 2be9bd8578a7357aabe70d9f748cb57fedf50708028ca7c3ed55815814b7ddc2
5
5
  SHA512:
6
- metadata.gz: 52e8b1543bca54371a2743fbc22b3cee32ba68a050ac414e373ca2027faba214956bca3aef61a05d25ca1d339b3e4ae01a417cdb5d1a4a407fba16b0a3cae56e
7
- data.tar.gz: 1bc40ccaab12ed598cc86d62d51ca8081a386b208559ac49788ff87601c45809958c2e936692fbb2e78f3ff0f04b124636f795d941558239fc20edf04f7b979b
6
+ metadata.gz: 848080fdd9596f0a0f477464ddbc2009ab2c5f3caec56d3fa3c97dd275aca7687c94793af8124a7dd567a1c33abcdf3474cb2e13ab6b8b91f47302a0059b6916
7
+ data.tar.gz: e79de4347bbc5cd0701095574dfb7faafb320dc3f6e1de1cb0978ebd1fcdce5e65ca0c8fa2e0079d72385acabea582caac41a0c6c553078f69d1337006422e0e
@@ -1,4 +1,4 @@
1
1
  **Before merging:**
2
2
 
3
- - [ ] Copy the latest benchmark results into the `README.md` and update this PR
4
- - [ ] If this change merits an update to `CHANGELOG.md`, add an entry following Keep a Changelog [guidelines](https://keepachangelog.com/en/1.0.0/) with [semantic versioning](https://semver.org/)
3
+ - [ ] Copy the table printed at the end of the latest benchmark results into the `README.md` and update this PR
4
+ - [ ] If this change merits an update to `CHANGELOG.md`, add an entry following Keep a Changelog [guidelines](https://keepachangelog.com/en/1.0.0/) with [semantic versioning](https://semver.org/)
@@ -12,7 +12,7 @@ jobs:
12
12
  strategy:
13
13
  fail-fast: false
14
14
  matrix:
15
- ruby: [jruby, 2.4, 2.5, 2.6, 2.7, 3.0]
15
+ ruby: [jruby, 2.4, 2.5, 2.6, 2.7, 3.0, truffleruby-head]
16
16
  runs-on: ubuntu-latest
17
17
  steps:
18
18
  - uses: actions/checkout@v2
data/.rubocop.yml CHANGED
@@ -1,5 +1,17 @@
1
1
  inherit_gem:
2
- panolint: rubocop.yml
2
+ panolint: panolint-rubocop.yml
3
+
4
+ Layout/LineLength:
5
+ Max: 120
6
+
7
+ Metrics/ModuleLength:
8
+ Enabled: false
9
+
10
+ Metrics/PerceivedComplexity:
11
+ Enabled: false
3
12
 
4
13
  Style/DocumentDynamicEvalDefinition:
5
14
  Enabled: false
15
+
16
+ Style/MultipleComparison:
17
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -6,24 +6,59 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  ## Unreleased
9
+
10
+ - Nothing yet!
11
+
12
+ ## [1.5.0] - 2021-12-17
13
+
14
+ - Remove optimization for truthy results to fix race condition bugs
15
+ - Switch to a simpler internal data structure to fix several classes of bugs
16
+ that the previous few versions were unable to sufficiently address
17
+
18
+ ## [1.4.0] - 2021-12-10
19
+
20
+ ### Fixed
21
+
22
+ - Fix several bugs related to classes inheriting memoized methods
23
+ from multiple modules or a parent class ([#241](https://github.com/panorama-ed/memo_wise/pull/241))
24
+
25
+ ## [1.3.0] - 2021-11-22
26
+
27
+ ### Fixed
28
+
29
+ - Fix thread-safety issue in concurrent calls to zero-arg method in unmemoized
30
+ state which resulted in a `nil` value being accidentally returned in one thread
31
+ - Fix bugs related to child classes inheriting from parent classes that use
32
+ `MemoWise`
33
+
34
+ ## [1.2.0] - 2021-11-10
35
+
9
36
  ### Updated
10
- - (Nothing, yet)
37
+ - Improved performance of all methods by using an outer Array instead of a Hash
38
+ - Improved performance for multi-argument methods and simplify internal data
39
+ structures
40
+
11
41
  ### Fixed
12
- - (Nothing, yet)
42
+ - Removed use of #hash due to potential of hash collisions
43
+ - Updated internal local variable names to avoid name collisions with method
44
+ arguments
45
+
13
46
  ### Breaking Changes
14
47
  - None
15
48
 
16
49
  ## [1.1.0] - 2021-07-29
17
50
  ### Updated
18
- - Improve performance across the board by:
51
+ - Improved performance across the board by:
19
52
  - removing `Hash#fetch`
20
53
  - using `Array#hash`
21
54
  - avoiding multi-layer hash lookups for multi-argument methods
22
55
  - optimizing for truthy results
23
56
  - Add `dry-core` to benchmarks in README
57
+
24
58
  ### Fixed
25
- - Fix usage on module singleton classes
26
- - Fix usage on module which would be extended by other classes
59
+ - Fixed usage on module singleton classes
60
+ - Fixed usage on module which would be extended by other classes
61
+
27
62
  ### Breaking Changes
28
63
  - None
29
64
 
@@ -31,6 +66,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
31
66
  ### Added
32
67
  - Support for `.preset_memo_wise` on class methods
33
68
  - Support for `.reset_memo_wise` on class methods
69
+
34
70
  ### Updated
35
71
  - Improved performance for common cases by reducing array allocations
36
72
 
@@ -80,7 +116,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
80
116
  - Panolint
81
117
  - Dependabot setup
82
118
 
83
- [Unreleased]: https://github.com/panorama-ed/memo_wise/compare/v1.1.0...HEAD
119
+ [Unreleased]: https://github.com/panorama-ed/memo_wise/compare/v1.5.0...HEAD
120
+ [1.5.0]: https://github.com/panorama-ed/memo_wise/compare/v1.4.0...v1.5.0
121
+ [1.4.0]: https://github.com/panorama-ed/memo_wise/compare/v1.3.0...v1.4.0
122
+ [1.3.0]: https://github.com/panorama-ed/memo_wise/compare/v1.2.0...v1.3.0
123
+ [1.2.0]: https://github.com/panorama-ed/memo_wise/compare/v1.1.0...v1.2.0
84
124
  [1.1.0]: https://github.com/panorama-ed/memo_wise/compare/v1.0.0...v1.1.0
85
125
  [1.0.0]: https://github.com/panorama-ed/memo_wise/compare/v0.4.0...v1.0.0
86
126
  [0.4.0]: https://github.com/panorama-ed/memo_wise/compare/v0.3.0...v0.4.0
data/Gemfile.lock CHANGED
@@ -1,9 +1,9 @@
1
1
  GIT
2
2
  remote: https://github.com/panorama-ed/panolint.git
3
- revision: c709ebcc5fd9593db959df4a92c12cae1d1fb9af
3
+ revision: 5850a218b96da7dd196438fb65d41169cd6747e5
4
4
  branch: main
5
5
  specs:
6
- panolint (0.1.3)
6
+ panolint (0.1.4)
7
7
  brakeman (>= 4.8, < 6.0)
8
8
  rubocop (>= 0.83, < 2.0)
9
9
  rubocop-performance (~> 1.5)
@@ -14,20 +14,21 @@ GIT
14
14
  PATH
15
15
  remote: .
16
16
  specs:
17
- memo_wise (1.1.0)
17
+ memo_wise (1.5.0)
18
18
 
19
19
  GEM
20
20
  remote: https://rubygems.org/
21
21
  specs:
22
- activesupport (5.2.6)
22
+ activesupport (6.1.4.1)
23
23
  concurrent-ruby (~> 1.0, >= 1.0.2)
24
- i18n (>= 0.7, < 2)
25
- minitest (~> 5.1)
26
- tzinfo (~> 1.1)
24
+ i18n (>= 1.6, < 2)
25
+ minitest (>= 5.1)
26
+ tzinfo (~> 2.0)
27
+ zeitwerk (~> 2.3)
27
28
  ansi (1.5.0)
28
29
  ast (2.4.2)
29
- brakeman (5.1.1)
30
- codecov (0.5.2)
30
+ brakeman (5.1.2)
31
+ codecov (0.6.0)
31
32
  simplecov (>= 0.15, < 0.22)
32
33
  concurrent-ruby (1.1.9)
33
34
  diff-lcs (1.4.4)
@@ -36,11 +37,11 @@ GEM
36
37
  ansi
37
38
  rouge
38
39
  slop (~> 3)
39
- i18n (1.8.10)
40
+ i18n (1.8.11)
40
41
  concurrent-ruby (~> 1.0)
41
42
  minitest (5.14.4)
42
- parallel (1.20.1)
43
- parser (3.0.2.0)
43
+ parallel (1.21.0)
44
+ parser (3.0.3.1)
44
45
  ast (~> 2.4.1)
45
46
  rack (2.2.3)
46
47
  rainbow (3.0.0)
@@ -62,44 +63,43 @@ GEM
62
63
  diff-lcs (>= 1.2.0, < 2.0)
63
64
  rspec-support (~> 3.10.0)
64
65
  rspec-support (3.10.0)
65
- rubocop (1.12.1)
66
+ rubocop (1.23.0)
66
67
  parallel (~> 1.10)
67
68
  parser (>= 3.0.0.0)
68
69
  rainbow (>= 2.2.2, < 4.0)
69
70
  regexp_parser (>= 1.8, < 3.0)
70
71
  rexml
71
- rubocop-ast (>= 1.2.0, < 2.0)
72
+ rubocop-ast (>= 1.12.0, < 2.0)
72
73
  ruby-progressbar (~> 1.7)
73
74
  unicode-display_width (>= 1.4.0, < 3.0)
74
- rubocop-ast (1.4.1)
75
- parser (>= 2.7.1.5)
76
- rubocop-performance (1.10.2)
77
- rubocop (>= 0.90.0, < 2.0)
75
+ rubocop-ast (1.13.0)
76
+ parser (>= 3.0.1.1)
77
+ rubocop-performance (1.12.0)
78
+ rubocop (>= 1.7.0, < 2.0)
78
79
  rubocop-ast (>= 0.4.0)
79
- rubocop-rails (2.9.1)
80
+ rubocop-rails (2.12.4)
80
81
  activesupport (>= 4.2.0)
81
82
  rack (>= 1.1)
82
- rubocop (>= 0.90.0, < 2.0)
83
- rubocop-rake (0.5.1)
84
- rubocop
85
- rubocop-rspec (2.2.0)
83
+ rubocop (>= 1.7.0, < 2.0)
84
+ rubocop-rake (0.6.0)
86
85
  rubocop (~> 1.0)
87
- rubocop-ast (>= 1.1.0)
86
+ rubocop-rspec (2.6.0)
87
+ rubocop (~> 1.19)
88
88
  ruby-progressbar (1.11.0)
89
89
  simplecov (0.18.5)
90
90
  docile (~> 1.1)
91
91
  simplecov-html (~> 0.11)
92
92
  simplecov-html (0.12.3)
93
93
  slop (3.6.0)
94
- thread_safe (0.3.6)
95
- tzinfo (1.2.9)
96
- thread_safe (~> 0.1)
97
- unicode-display_width (2.0.0)
94
+ tzinfo (2.0.4)
95
+ concurrent-ruby (~> 1.0)
96
+ unicode-display_width (2.1.0)
98
97
  values (1.8.0)
99
98
  yard (0.9.26)
100
99
  yard-doctest (0.1.17)
101
100
  minitest
102
101
  yard
102
+ zeitwerk (2.5.1)
103
103
 
104
104
  PLATFORMS
105
105
  ruby
@@ -117,4 +117,4 @@ DEPENDENCIES
117
117
  yard-doctest (~> 0.1)
118
118
 
119
119
  BUNDLED WITH
120
- 2.2.3
120
+ 2.2.32
data/README.md CHANGED
@@ -20,6 +20,8 @@
20
20
  * Support for [resetting](https://rubydoc.info/github/panorama-ed/memo_wise/MemoWise#reset_memo_wise-instance_method) and [presetting](https://rubydoc.info/github/panorama-ed/memo_wise/MemoWise#preset_memo_wise-instance_method) memoized values
21
21
  * Support for memoization on frozen objects
22
22
  * Support for memoization of class and module methods
23
+ * Support for inheritance of memoized class and instance methods
24
+ * Documented and tested [thread-safety guarantees](#thread-safety)
23
25
  * Full [documentation](https://rubydoc.info/github/panorama-ed/memo_wise/MemoWise) and [test coverage](https://codecov.io/gh/panorama-ed/memo_wise)!
24
26
 
25
27
  ## Installation
@@ -56,6 +58,15 @@ class Example
56
58
  x
57
59
  end
58
60
  memo_wise :slow_value
61
+
62
+ private
63
+
64
+ # maintains privacy of the memoized method
65
+ def private_slow_method(x)
66
+ sleep x
67
+ x
68
+ end
69
+ memo_wise :private_slow_method
59
70
  end
60
71
 
61
72
  ex = Example.new
@@ -102,44 +113,38 @@ For more usage details, see our detailed [documentation](#documentation).
102
113
 
103
114
  ## Benchmarks
104
115
 
105
- Benchmarks measure memoized value retrieval time using
106
- [`benchmark-ips`](https://github.com/evanphx/benchmark-ips). Benchmarks are
107
- run in GitHub Actions and updated in every PR that changes code.
108
-
109
- **Values >1.00x represent how much _slower_ each gem’s memoized value retrieval
110
- is than the baseline.**
111
-
112
- Benchmarks using Ruby 3.0.2:
113
-
114
- |Method arguments|`memo_wise` (latest)|`dry-core` (0.7.1)|`memery` (1.4.0)|
115
- |--|--|--|--|
116
- |`()` (none)|baseline|0.98-1.44x|10.40-14.95x|
117
- |`(a)`|baseline|2.10x|8.39x|
118
- |`(a, b)`|baseline|baseline\*|4.45x|
119
- |`(a:)`|baseline|1.89x|17.37x|
120
- |`(a:, b:)`|baseline|baseline\*|9.66x|
121
- |`(a, b:)`|baseline|baseline\*|8.78x|
122
- |`(a, *args)`|baseline|1.88x|4.07x|
123
- |`(a:, **kwargs)`|baseline|1.41x|4.98x|
124
- |`(a, *args, b:, **kwargs)`|baseline|baseline\*|2.50x|
125
-
126
- \*Indicates a run that was slower than the baseline but the difference was not
127
- significant.
128
-
129
- The following benchmarks are run on Ruby 2.7.4 because these gems raise errors
130
- in Ruby 3.0.2 due to their incorrect handling of keyword arguments:
131
-
132
- |Method arguments|`memo_wise` (latest)|`ddmemoize` (1.0.0)|`memoist` (0.16.2)|`memoized` (1.0.2)|`memoizer` (1.0.3)|
133
- |--|--|--|--|--|--|
134
- |`()` (none)|baseline|20.96-25.44x|2.49-3.31x|1.06-1.48x|2.96-3.83x|
135
- |`(a)`|baseline|19.37x|13.28x|9.31x|10.78x|
136
- |`(a, b)`|baseline|3.65x|2.64x|2.00x|2.28x|
137
- |`(a:)`|baseline|25.88x|21.74x|17.68x|19.17x|
138
- |`(a:, b:)`|baseline|5.72x|4.57x|3.90x|4.42x|
139
- |`(a, b:)`|baseline|5.63x|5.06x|4.20x|4.18x|
140
- |`(a, *args)`|baseline|5.13x|3.89x|3.25x|3.31x|
141
- |`(a:, **kwargs)`|baseline|3.23x|2.55x|2.33x|2.47x|
142
- |`(a, *args, b:, **kwargs)`|baseline|2.51x|2.00x|1.84x|1.88x|
116
+ Benchmarks are run in GitHub Actions, and the tables below are updated with every code change. **Values >1.00x represent how much _slower_ each gem’s memoized value retrieval is than the latest commit of `MemoWise`**, according to [`benchmark-ips`](https://github.com/evanphx/benchmark-ips) (2.9.2).
117
+
118
+ Results using Ruby 3.0.3:
119
+
120
+ |Method arguments|`Dry::Core`\* (0.7.1)|`Memery` (1.4.0)|
121
+ |--|--|--|
122
+ |`()` (none)|1.06x|11.96x|
123
+ |`(a)`|1.99x|10.19x|
124
+ |`(a, b)`|0.39x|1.94x|
125
+ |`(a:)`|1.80x|19.36x|
126
+ |`(a:, b:)`|0.43x|4.26x|
127
+ |`(a, b:)`|0.39x|3.97x|
128
+ |`(a, *args)`|0.83x|1.85x|
129
+ |`(a:, **kwargs)`|0.77x|2.94x|
130
+ |`(a, *args, b:, **kwargs)`|0.59x|1.57x|
131
+
132
+ \* `Dry::Core`
133
+ [may cause incorrect behavior caused by hash collisions](https://github.com/dry-rb/dry-core/issues/63).
134
+
135
+ Results using Ruby 2.7.5 (because these gems raise errors in Ruby 3.x):
136
+
137
+ |Method arguments|`DDMemoize` (1.0.0)|`Memoist` (0.16.2)|`Memoized` (1.0.2)|`Memoizer` (1.0.3)|
138
+ |--|--|--|--|--|
139
+ |`()` (none)|24.30x|2.57x|1.19x|2.98x|
140
+ |`(a)`|21.68x|14.63x|11.13x|12.71x|
141
+ |`(a, b)`|3.18x|2.36x|1.86x|2.06x|
142
+ |`(a:)`|30.62x|24.52x|21.44x|22.61x|
143
+ |`(a:, b:)`|5.25x|4.40x|3.80x|4.04x|
144
+ |`(a, b:)`|4.91x|4.06x|3.55x|3.83x|
145
+ |`(a, *args)`|3.10x|2.31x|1.96x|1.98x|
146
+ |`(a:, **kwargs)`|2.87x|2.40x|2.09x|2.20x|
147
+ |`(a, *args, b:, **kwargs)`|2.08x|1.82x|1.67x|1.70x|
143
148
 
144
149
  You can run benchmarks yourself with:
145
150
 
@@ -152,6 +157,24 @@ $ bundle exec ruby benchmarks.rb
152
157
  If your results differ from what's posted here,
153
158
  [let us know](https://github.com/panorama-ed/memo_wise/issues/new)!
154
159
 
160
+ ## Thread Safety
161
+
162
+ MemoWise makes the following **thread safety** guarantees on all supported Ruby
163
+ versions:
164
+
165
+ 1. **Before** a value has been memoized
166
+
167
+ * Contended calls from multiple threads...
168
+ * May each call the original method
169
+ * May return different valid results (when the method is nondeterministic,
170
+ like `rand`)
171
+ * Will memoize exactly one valid return value
172
+
173
+ 2. **After** a value has been memoized
174
+
175
+ * Contended calls from multiple threads...
176
+ * Always return the same memoized value
177
+
155
178
  ## Documentation
156
179
 
157
180
  ### Documentation is Automatically Generated
@@ -203,10 +226,17 @@ after(:each) { helper.reset_memo_wise }
203
226
 
204
227
  ## Further Reading
205
228
 
206
- We've written more about MemoWise in a series of blog posts:
229
+ We presented at RubyConf 2021:
230
+
231
+ - Achieving Fast Method Metaprogramming: Lessons from `MemoWise`
232
+ ([slides](https://docs.google.com/presentation/d/1XgERQ0YHplwJKM3wNQwZn584d_9szYZp2WsDEXoY_7Y/edit?usp=sharing) /
233
+ [benchmarks](https://gist.github.com/JacobEvelyn/17b7b000e50151c30eaea928f1fcdc11))
234
+
235
+ And we've written more about `MemoWise` in a series of blog posts:
207
236
 
208
237
  - [Introducing: MemoWise](https://medium.com/building-panorama-education/introducing-memowise-51a5f0523489)
209
238
  - [Optimizing MemoWise Performance](https://ja.cob.land/optimizing-memowise-performance)
239
+ - [Esosteric Ruby in MemoWise](https://jemma.dev/blog/esoteric-ruby-in-memowise)
210
240
 
211
241
  ## Logo
212
242
 
@@ -237,11 +267,14 @@ Then carry out these steps:
237
267
 
238
268
  1. Update `CHANGELOG.md`:
239
269
  - Add an entry for the upcoming version _x.y.z_
270
+ - Add a link for this version's comparison to the bottom of `CHANGELOG.md`
240
271
  - Move content from _Unreleased_ to the upcoming version _x.y.z_
272
+ - Change _Unreleased_ section to say `- Nothing yet!`
241
273
  - Commit with title `Update CHANGELOG.md for x.y.z`
242
274
 
243
275
  2. Update `lib/memo_wise/version.rb`
244
276
  - Replace with upcoming version _x.y.z_
277
+ - Run `bundle install` to update `Gemfile.lock`
245
278
  - Commit with title `Bump version to x.y.z`
246
279
 
247
280
  3. `bundle exec rake release`
data/benchmarks/Gemfile CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- ruby ">= 2.7.4"
5
+ ruby ">= 2.7.5"
6
6
 
7
- gem "benchmark-ips", "2.9.1"
7
+ gem "benchmark-ips", "2.9.2"
8
8
 
9
9
  if RUBY_VERSION > "3"
10
10
  gem "dry-core", "0.7.1"