ddmemoize 1.0.0a1 → 1.0.0a2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/NEWS.md +10 -0
- data/README.md +24 -1
- data/ddmemoize.gemspec +1 -0
- data/lib/ddmemoize.rb +57 -1
- data/lib/ddmemoize/version.rb +1 -1
- data/samples/fib.rb +24 -0
- data/spec/ddmemoize_spec.rb +71 -0
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eecfd413a7d098193d530f6b890934993b41a9bc5ce83caddf6c34a94309db22
|
4
|
+
data.tar.gz: a5f07ddba37a86bf0dc6af6d34426ca9d1b16003f7ae0e20116de174be73afdf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e3dcd35b56f321b3abfffe266d765d78ff969622dff3ec7930a580f2023a32ade26a9dcb642715caca800a9eaf466aae6ec66a6e1c091b01dae3fec901f060e3
|
7
|
+
data.tar.gz: ed990b6c32a034c7aab031a634ca7331556863a4eea6aa5e623c545f8de22a594bc70e4f8a7a336c5555dff9da73c44021064106697035a6a3705314a5471fe6
|
data/NEWS.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
# DDMemoize news
|
2
2
|
|
3
|
+
## 1.0.0a2 (2017-12-10)
|
4
|
+
|
5
|
+
Features:
|
6
|
+
|
7
|
+
* Added telemetry support (#1)
|
8
|
+
|
9
|
+
Fixes:
|
10
|
+
|
11
|
+
* Fixed handling of objects that override `#equal?` (but why would you do that anyway?) (#2)
|
12
|
+
|
3
13
|
## 1.0.0a1 (2017-12-02)
|
4
14
|
|
5
15
|
Initial release.
|
data/README.md
CHANGED
@@ -30,6 +30,7 @@ Features:
|
|
30
30
|
|
31
31
|
* Supports memoizing functions on frozen objects
|
32
32
|
* Releases memoized values when needed in order to reduce memory pressure
|
33
|
+
* Optionally records telemetry
|
33
34
|
|
34
35
|
## Installation
|
35
36
|
|
@@ -78,7 +79,29 @@ Alternatively, prepend `memoized` to the function definition:
|
|
78
79
|
end
|
79
80
|
```
|
80
81
|
|
81
|
-
Do not
|
82
|
+
Do not memoize functions that depend on mutable state.
|
83
|
+
|
84
|
+
### Telemetry
|
85
|
+
|
86
|
+
To activate telemetry for a given class, call `DDMemoize.activate` with a `telemetry` keyword argument, passing in a telemetry instance:
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
require 'ddmemoize'
|
90
|
+
|
91
|
+
TELEMETRY = DDTelemetry.new
|
92
|
+
|
93
|
+
class FibFast
|
94
|
+
DDMemoize.activate(self, telemetry: TELEMETRY)
|
95
|
+
|
96
|
+
# …
|
97
|
+
end
|
98
|
+
```
|
99
|
+
|
100
|
+
To print the collected metrics, call `DDMemoize.print_telemetry`, passing in the telemetry instance:
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
DDMemoize.print_telemetry(TELEMETRY)
|
104
|
+
```
|
82
105
|
|
83
106
|
## Development
|
84
107
|
|
data/ddmemoize.gemspec
CHANGED
data/lib/ddmemoize.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'ref'
|
4
|
+
require 'ddtelemetry'
|
5
|
+
require 'singleton'
|
4
6
|
|
5
7
|
require_relative 'ddmemoize/version'
|
6
8
|
|
@@ -13,10 +15,53 @@ module DDMemoize
|
|
13
15
|
end
|
14
16
|
end
|
15
17
|
|
18
|
+
# @api private
|
19
|
+
class TelemetryMap
|
20
|
+
include Singleton
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@map = {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def [](mod)
|
27
|
+
@map[mod]
|
28
|
+
end
|
29
|
+
|
30
|
+
def []=(mod, telemetry)
|
31
|
+
@map[mod] = telemetry
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
16
35
|
NONE = Object.new
|
17
36
|
|
18
|
-
def self.activate(mod)
|
37
|
+
def self.activate(mod, telemetry: nil)
|
19
38
|
mod.extend(Mixin)
|
39
|
+
TelemetryMap.instance[mod] = telemetry
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.telemetry_for(mod)
|
43
|
+
TelemetryMap.instance[mod]
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.print_telemetry(telemetry)
|
47
|
+
headers = %w[memoization hit miss %]
|
48
|
+
|
49
|
+
rows_raw = telemetry.counter(:memoization).map do |(name, type), counter|
|
50
|
+
{ name: name, type: type, count: counter.value }
|
51
|
+
end
|
52
|
+
|
53
|
+
rows = rows_raw.group_by { |r| r[:name] }.map do |name, rows_for_name|
|
54
|
+
rows_by_type = rows_for_name.group_by { |r| r[:type] }
|
55
|
+
|
56
|
+
num_hit = rows_by_type.fetch(:hit, []).fetch(0, {}).fetch(:count, 0)
|
57
|
+
num_miss = rows_by_type.fetch(:miss, []).fetch(0, {}).fetch(:count, 0)
|
58
|
+
pct = num_hit.to_f / (num_hit + num_miss).to_f
|
59
|
+
|
60
|
+
[name, num_hit.to_s, num_miss.to_s, "#{format('%3.1f', pct * 100)}%"]
|
61
|
+
end
|
62
|
+
|
63
|
+
all_rows = [headers] + rows
|
64
|
+
puts DDTelemetry::Table.new(all_rows).to_s
|
20
65
|
end
|
21
66
|
|
22
67
|
module Mixin
|
@@ -25,6 +70,7 @@ module DDMemoize
|
|
25
70
|
alias_method original_method_name, method_name
|
26
71
|
|
27
72
|
instance_cache = Hash.new { |hash, key| hash[key] = {} }
|
73
|
+
telemetry = DDMemoize.telemetry_for(self)
|
28
74
|
|
29
75
|
define_method(method_name) do |*args|
|
30
76
|
instance_method_cache = instance_cache[self]
|
@@ -35,6 +81,16 @@ module DDMemoize
|
|
35
81
|
value = object ? object.value : NONE
|
36
82
|
end
|
37
83
|
|
84
|
+
if telemetry
|
85
|
+
counter_label = is_a?(Class) ? "#{self}.#{method_name}" : "#{self.class}##{method_name}"
|
86
|
+
|
87
|
+
if NONE.equal?(value)
|
88
|
+
telemetry.counter(:memoization).increment([counter_label, :miss])
|
89
|
+
else
|
90
|
+
telemetry.counter(:memoization).increment([counter_label, :hit])
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
38
94
|
if value.equal?(NONE)
|
39
95
|
send(original_method_name, *args).tap do |r|
|
40
96
|
instance_method_cache[args] = Ref::SoftReference.new(Value.new(r))
|
data/lib/ddmemoize/version.rb
CHANGED
data/samples/fib.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ddmemoize'
|
4
|
+
|
5
|
+
TELEMETRY = DDTelemetry.new
|
6
|
+
|
7
|
+
class FibFast
|
8
|
+
DDMemoize.activate(self, telemetry: TELEMETRY)
|
9
|
+
|
10
|
+
memoized def fib(n)
|
11
|
+
case n
|
12
|
+
when 0
|
13
|
+
0
|
14
|
+
when 1
|
15
|
+
1
|
16
|
+
else
|
17
|
+
fib(n - 1) + fib(n - 2)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
p FibFast.new.fib(1000)
|
23
|
+
|
24
|
+
DDMemoize.print_telemetry(TELEMETRY)
|
data/spec/ddmemoize_spec.rb
CHANGED
@@ -40,6 +40,22 @@ describe DDMemoize do
|
|
40
40
|
memoize :run
|
41
41
|
end
|
42
42
|
|
43
|
+
class MemoizationSpecEqual
|
44
|
+
TELEMETRY = ::DDTelemetry.new
|
45
|
+
DDMemoize.activate(self, telemetry: TELEMETRY)
|
46
|
+
|
47
|
+
class EqualToEverythingValue
|
48
|
+
def equal?(*)
|
49
|
+
true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def run
|
54
|
+
EqualToEverythingValue.new
|
55
|
+
end
|
56
|
+
memoize :run
|
57
|
+
end
|
58
|
+
|
43
59
|
class MemoizationSpecUpcaserInlineSyntax
|
44
60
|
DDMemoize.activate(self)
|
45
61
|
|
@@ -62,6 +78,16 @@ describe DDMemoize do
|
|
62
78
|
record memoized def run; end
|
63
79
|
end
|
64
80
|
|
81
|
+
class MemoizationSpecWithTelemetry
|
82
|
+
TELEMETRY = ::DDTelemetry.new
|
83
|
+
DDMemoize.activate(self, telemetry: TELEMETRY)
|
84
|
+
|
85
|
+
def run(value)
|
86
|
+
value.upcase
|
87
|
+
end
|
88
|
+
memoize :run
|
89
|
+
end
|
90
|
+
|
65
91
|
example do
|
66
92
|
sample1a = MemoizationSpecSample1.new(10)
|
67
93
|
sample1b = MemoizationSpecSample1.new(15)
|
@@ -82,6 +108,20 @@ describe DDMemoize do
|
|
82
108
|
sample.run(5)
|
83
109
|
end
|
84
110
|
|
111
|
+
it 'supports objects that bizarrely override #equal?' do
|
112
|
+
sample = MemoizationSpecEqual.new
|
113
|
+
sample.freeze
|
114
|
+
sample.run
|
115
|
+
sample.run
|
116
|
+
sample.run
|
117
|
+
|
118
|
+
telemetry = MemoizationSpecEqual::TELEMETRY
|
119
|
+
counter = telemetry.counter(:memoization)
|
120
|
+
|
121
|
+
expect(counter.value(['MemoizationSpecEqual#run', :miss])).to eq(1)
|
122
|
+
expect(counter.value(['MemoizationSpecEqual#run', :hit])).to eq(2)
|
123
|
+
end
|
124
|
+
|
85
125
|
it 'supports memoized def … syntax' do
|
86
126
|
upcaser = MemoizationSpecUpcaserInlineSyntax.new
|
87
127
|
expect(upcaser.run('hi')).to eq('HI')
|
@@ -102,4 +142,35 @@ describe DDMemoize do
|
|
102
142
|
it 'returns method name' do
|
103
143
|
expect(MemoizationSpecInlineSyntaxReturn.sym).to eq(:run)
|
104
144
|
end
|
145
|
+
|
146
|
+
it 'records telemetry' do
|
147
|
+
sample = MemoizationSpecWithTelemetry.new
|
148
|
+
|
149
|
+
sample.run('denis')
|
150
|
+
sample.run('denis')
|
151
|
+
sample.run('defreyne')
|
152
|
+
|
153
|
+
telemetry = MemoizationSpecWithTelemetry::TELEMETRY
|
154
|
+
counter = telemetry.counter(:memoization)
|
155
|
+
|
156
|
+
expect(counter.value(['MemoizationSpecWithTelemetry#run', :miss])).to eq(2)
|
157
|
+
expect(counter.value(['MemoizationSpecWithTelemetry#run', :hit])).to eq(1)
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'prints recorded telemetry' do
|
161
|
+
sample = MemoizationSpecWithTelemetry.new
|
162
|
+
|
163
|
+
sample.run('denis')
|
164
|
+
sample.run('denis')
|
165
|
+
sample.run('defreyne')
|
166
|
+
|
167
|
+
expected = <<~OUTPUT
|
168
|
+
memoization │ hit miss %
|
169
|
+
─────────────────────────────────┼───────────────────
|
170
|
+
MemoizationSpecWithTelemetry#run │ 2 4 33.3%
|
171
|
+
OUTPUT
|
172
|
+
|
173
|
+
telemetry = MemoizationSpecWithTelemetry::TELEMETRY
|
174
|
+
expect { DDMemoize.print_telemetry(telemetry) }.to output(expected).to_stdout
|
175
|
+
end
|
105
176
|
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ddmemoize
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.0a2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Denis Defreyne
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-12-
|
11
|
+
date: 2017-12-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ddtelemetry
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.0.0a1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.0.0a1
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: ref
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -44,6 +58,7 @@ files:
|
|
44
58
|
- ddmemoize.gemspec
|
45
59
|
- lib/ddmemoize.rb
|
46
60
|
- lib/ddmemoize/version.rb
|
61
|
+
- samples/fib.rb
|
47
62
|
- scripts/release
|
48
63
|
- spec/ddmemoize_spec.rb
|
49
64
|
- spec/spec_helper.rb
|