ddmemoize 1.0.0a1 → 1.0.0a2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|