persistent_open_struct 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +0 -1
- data/README.md +86 -69
- data/benchmark/benchmark.rb +37 -0
- data/lib/persistent_open_struct.rb +119 -2
- data/lib/persistent_open_struct/version.rb +1 -1
- data/persistent_open_struct.gemspec +1 -0
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c56926b84113f349bff3d7626376aa08f3bacc67
|
4
|
+
data.tar.gz: 52d2e51a904300c09ddff7cd66500a3986772c1a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c572c33cf67151da6da095d47c55d758bc49ffc21db1bdda311518629f7ac2e12eabc9383e3a0f309b059d603a9371ef748d16acbe0de4bebd9af96238a97c61
|
7
|
+
data.tar.gz: bb0dc99765a194859a37ed1e8e220250a9891013257a64d93b8eb1a7b64b2a033410f220d7725b709e8b409a935810706ffce356b0cf2a7bc4abe6575e7c4f4b
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -3,97 +3,113 @@
|
|
3
3
|
|
4
4
|
# PersistentOpenStruct
|
5
5
|
|
6
|
-
Are you inserting data in the same format into OpenStruct again and again?
|
7
|
-
|
6
|
+
Are you inserting data in the same format into `OpenStruct` again and again?
|
7
|
+
Wish `OpenStruct` wouldn't have to define a new singleton method on each
|
8
8
|
individual object? Here is your solution!
|
9
9
|
|
10
|
-
PersistentOpenStruct defines methods on the class, so as long as you keep using
|
10
|
+
`PersistentOpenStruct` defines methods on the class, so as long as you keep using
|
11
11
|
the same keys, no new methods will be defined. The class quickly learns the
|
12
12
|
shape of your data, so you can use it with minimal overhead. (Though of course
|
13
13
|
it's still not as fast as doing the work of defining a full-fledged class.)
|
14
14
|
|
15
|
-
It obeys the entire interface of OpenStruct
|
15
|
+
It obeys the entire interface of `OpenStruct`, so you can insert it into your code
|
16
16
|
without problem! (Unless you're using `OpenStruct#delete_field` for some reason;
|
17
|
-
PersistentOpenStruct refuses to undefine the methods it defines.)
|
17
|
+
`PersistentOpenStruct` refuses to undefine the methods it defines.)
|
18
18
|
|
19
|
-
This gives a noticeable performance boost. Here are the results of the
|
20
|
-
found at
|
21
|
-
[`benchmark/benchmark.rb`](http://github.com/amcaplan/persistent_open_struct/blob/master/benchmark/benchmark.rb)
|
22
|
-
|
23
|
-
results for [`OpenFastStruct`](http://github.com/arturoherrero/ofstruct) as
|
24
|
-
well, to get a sense of alternative solutions.
|
19
|
+
This gives a noticeable performance boost. Here are the results of the
|
20
|
+
benchmark found at
|
21
|
+
[`benchmark/benchmark.rb`](http://github.com/amcaplan/persistent_open_struct/blob/master/benchmark/benchmark.rb)
|
22
|
+
run on Ruby 2.3.1; the final benchmark is most representative of the average case.
|
25
23
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
require_relative '../lib/persistent_open_struct'
|
30
|
-
|
31
|
-
Benchmark.ips do |x|
|
32
|
-
input_hash = { foo: :bar }
|
33
|
-
x.report('OpenStruct') do
|
34
|
-
OpenStruct.new(input_hash)
|
35
|
-
end
|
36
|
-
|
37
|
-
x.report('PersistentOpenStruct') do
|
38
|
-
PersistentOpenStruct.new(input_hash)
|
39
|
-
end
|
40
|
-
end
|
24
|
+
Also included are results for
|
25
|
+
[`OpenFastStruct`](http://github.com/arturoherrero/ofstruct) as well, to get a
|
26
|
+
sense of alternative solutions.
|
41
27
|
|
42
|
-
|
28
|
+
More is better.
|
43
29
|
|
44
|
-
Benchmark.ips do |x|
|
45
|
-
x.report('OpenStruct') do
|
46
|
-
OpenStruct.new.foo = :bar
|
47
|
-
end
|
48
|
-
|
49
|
-
x.report('PersistentOpenStruct') do
|
50
|
-
PersistentOpenStruct.new.foo = :bar
|
51
|
-
end
|
52
|
-
end
|
53
30
|
```
|
54
|
-
|
55
|
-
```
|
56
|
-
$ ruby benchmark/benchmark.rb
|
31
|
+
$ ruby benchmark/benchmark.rb
|
57
32
|
Initialization benchmark
|
58
33
|
|
34
|
+
Warming up --------------------------------------
|
35
|
+
OpenStruct 88.289k i/100ms
|
36
|
+
PersistentOpenStruct 78.440k i/100ms
|
37
|
+
OpenFastStruct 81.306k i/100ms
|
38
|
+
RegularClass 200.536k i/100ms
|
59
39
|
Calculating -------------------------------------
|
60
|
-
OpenStruct
|
61
|
-
PersistentOpenStruct
|
62
|
-
OpenFastStruct
|
63
|
-
RegularClass
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
OpenFastStruct
|
68
|
-
|
40
|
+
OpenStruct 981.150k (± 7.8%) i/s - 4.944M in 5.069950s
|
41
|
+
PersistentOpenStruct 898.432k (± 9.5%) i/s - 4.471M in 5.022044s
|
42
|
+
OpenFastStruct 1.059M (± 5.6%) i/s - 5.366M in 5.086061s
|
43
|
+
RegularClass 3.860M (± 9.6%) i/s - 19.251M in 5.034804s
|
44
|
+
|
45
|
+
Comparison:
|
46
|
+
RegularClass: 3859650.0 i/s
|
47
|
+
OpenFastStruct: 1058578.4 i/s - 3.65x slower
|
48
|
+
OpenStruct: 981149.5 i/s - 3.93x slower
|
49
|
+
PersistentOpenStruct: 898431.6 i/s - 4.30x slower
|
50
|
+
|
69
51
|
|
70
52
|
|
71
53
|
Assignment Benchmark
|
72
54
|
|
55
|
+
Warming up --------------------------------------
|
56
|
+
OpenStruct 199.451k i/100ms
|
57
|
+
PersistentOpenStruct 214.181k i/100ms
|
58
|
+
OpenFastStruct 99.324k i/100ms
|
59
|
+
RegularClass 312.190k i/100ms
|
73
60
|
Calculating -------------------------------------
|
74
|
-
OpenStruct
|
75
|
-
PersistentOpenStruct
|
76
|
-
OpenFastStruct
|
77
|
-
RegularClass
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
61
|
+
OpenStruct 4.505M (± 5.4%) i/s - 22.538M in 5.019146s
|
62
|
+
PersistentOpenStruct 4.375M (± 4.2%) i/s - 21.846M in 5.002085s
|
63
|
+
OpenFastStruct 1.405M (± 5.0%) i/s - 7.052M in 5.033620s
|
64
|
+
RegularClass 11.113M (± 5.2%) i/s - 55.570M in 5.015664s
|
65
|
+
|
66
|
+
Comparison:
|
67
|
+
RegularClass: 11112511.1 i/s
|
68
|
+
OpenStruct: 4504735.3 i/s - 2.47x slower
|
69
|
+
PersistentOpenStruct: 4375412.9 i/s - 2.54x slower
|
70
|
+
OpenFastStruct: 1404724.1 i/s - 7.91x slower
|
71
|
+
|
83
72
|
|
84
73
|
|
85
74
|
Access Benchmark
|
86
75
|
|
76
|
+
Warming up --------------------------------------
|
77
|
+
OpenStruct 256.277k i/100ms
|
78
|
+
PersistentOpenStruct 259.536k i/100ms
|
79
|
+
OpenFastStruct 227.602k i/100ms
|
80
|
+
RegularClass 260.242k i/100ms
|
81
|
+
Calculating -------------------------------------
|
82
|
+
OpenStruct 6.798M (± 5.0%) i/s - 34.085M in 5.027391s
|
83
|
+
PersistentOpenStruct 6.539M (± 6.0%) i/s - 32.702M in 5.019393s
|
84
|
+
OpenFastStruct 4.875M (± 4.0%) i/s - 24.353M in 5.004194s
|
85
|
+
RegularClass 6.654M (± 4.7%) i/s - 33.311M in 5.018183s
|
86
|
+
|
87
|
+
Comparison:
|
88
|
+
OpenStruct: 6797834.1 i/s
|
89
|
+
RegularClass: 6653907.4 i/s - same-ish: difference falls within error
|
90
|
+
PersistentOpenStruct: 6538883.0 i/s - same-ish: difference falls within error
|
91
|
+
OpenFastStruct: 4875059.7 i/s - 1.39x slower
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
All-Together benchmark
|
96
|
+
|
97
|
+
Warming up --------------------------------------
|
98
|
+
OpenStruct 14.490k i/100ms
|
99
|
+
PersistentOpenStruct 63.043k i/100ms
|
100
|
+
OpenFastStruct 47.777k i/100ms
|
101
|
+
RegularClass 197.293k i/100ms
|
87
102
|
Calculating -------------------------------------
|
88
|
-
OpenStruct
|
89
|
-
PersistentOpenStruct
|
90
|
-
OpenFastStruct
|
91
|
-
RegularClass
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
103
|
+
OpenStruct 155.130k (± 4.5%) i/s - 782.460k in 5.054693s
|
104
|
+
PersistentOpenStruct 764.359k (± 6.6%) i/s - 3.846M in 5.053760s
|
105
|
+
OpenFastStruct 546.809k (± 4.7%) i/s - 2.771M in 5.079275s
|
106
|
+
RegularClass 3.674M (± 9.1%) i/s - 18.348M in 5.054680s
|
107
|
+
|
108
|
+
Comparison:
|
109
|
+
RegularClass: 3673779.6 i/s
|
110
|
+
PersistentOpenStruct: 764359.4 i/s - 4.81x slower
|
111
|
+
OpenFastStruct: 546808.8 i/s - 6.72x slower
|
112
|
+
OpenStruct: 155130.1 i/s - 23.68x slower
|
97
113
|
```
|
98
114
|
|
99
115
|
## Installation
|
@@ -126,11 +142,12 @@ datum2.respond_to?(:foo) #=> true
|
|
126
142
|
|
127
143
|
## Contributing
|
128
144
|
|
129
|
-
1. Fork it ( https://github.com/
|
145
|
+
1. Fork it ( https://github.com/amcaplan/persistent_open_struct/fork )
|
130
146
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
131
147
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
132
148
|
4. Push to the branch (`git push origin my-new-feature`)
|
133
149
|
5. Create a new Pull Request
|
134
150
|
|
135
|
-
|
136
|
-
|
151
|
+
Changes to functionality require testing. Performance improvements should
|
152
|
+
include before/after benchmarks (or ideally just update the results in the
|
153
|
+
README above).
|
data/benchmark/benchmark.rb
CHANGED
@@ -30,6 +30,8 @@ Benchmark.ips do |x|
|
|
30
30
|
x.report('RegularClass') do
|
31
31
|
RegularClass.new(input_hash)
|
32
32
|
end
|
33
|
+
|
34
|
+
x.compare!
|
33
35
|
end
|
34
36
|
|
35
37
|
puts "\n\nAssignment Benchmark\n\n"
|
@@ -55,6 +57,8 @@ Benchmark.ips do |x|
|
|
55
57
|
x.report('RegularClass') do
|
56
58
|
rgc.foo = :bar
|
57
59
|
end
|
60
|
+
|
61
|
+
x.compare!
|
58
62
|
end
|
59
63
|
|
60
64
|
puts "\n\nAccess Benchmark\n\n"
|
@@ -80,4 +84,37 @@ Benchmark.ips do |x|
|
|
80
84
|
x.report('RegularClass') do
|
81
85
|
pos.foo
|
82
86
|
end
|
87
|
+
|
88
|
+
x.compare!
|
89
|
+
end
|
90
|
+
|
91
|
+
puts "\n\nAll-Together benchmark\n\n"
|
92
|
+
|
93
|
+
Benchmark.ips do |x|
|
94
|
+
input_hash = { foo: :bar }
|
95
|
+
x.report('OpenStruct') do
|
96
|
+
os = OpenStruct.new(input_hash)
|
97
|
+
os.foo = :bar
|
98
|
+
os.foo
|
99
|
+
end
|
100
|
+
|
101
|
+
x.report('PersistentOpenStruct') do
|
102
|
+
pos = PersistentOpenStruct.new(input_hash)
|
103
|
+
pos.foo = :bar
|
104
|
+
pos.foo
|
105
|
+
end
|
106
|
+
|
107
|
+
x.report('OpenFastStruct') do
|
108
|
+
ofs = OpenFastStruct.new(input_hash)
|
109
|
+
ofs.foo = :bar
|
110
|
+
ofs.foo
|
111
|
+
end
|
112
|
+
|
113
|
+
x.report('RegularClass') do
|
114
|
+
rgc = RegularClass.new(input_hash)
|
115
|
+
rgc.foo = :bar
|
116
|
+
rgc.foo
|
117
|
+
end
|
118
|
+
|
119
|
+
x.compare!
|
83
120
|
end
|
@@ -1,6 +1,8 @@
|
|
1
|
-
|
1
|
+
class PersistentOpenStruct
|
2
|
+
# The following 2 methods are altered from the original OpenStruct.
|
3
|
+
# Everything else is copied from OpenStruct on Ruby 2.2. (The performance
|
4
|
+
# enhancements in 2.3 don't make sense for this implementation.)
|
2
5
|
|
3
|
-
class PersistentOpenStruct < OpenStruct
|
4
6
|
def new_ostruct_member(name)
|
5
7
|
name = name.to_sym
|
6
8
|
unless respond_to?(name)
|
@@ -14,4 +16,119 @@ class PersistentOpenStruct < OpenStruct
|
|
14
16
|
def delete_field(name)
|
15
17
|
@table.delete(name)
|
16
18
|
end
|
19
|
+
|
20
|
+
# From here on was copied from https://github.com/ruby/ruby/blob/3c7a96bfa2d21336d985ceda544c4ccabafebed5/lib/ostruct.rb
|
21
|
+
# to avoid changes to internals negatively affecting performance.
|
22
|
+
|
23
|
+
def initialize(hash=nil)
|
24
|
+
@table = {}
|
25
|
+
if hash
|
26
|
+
hash.each_pair do |k, v|
|
27
|
+
k = k.to_sym
|
28
|
+
@table[k] = v
|
29
|
+
new_ostruct_member(k)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize_copy(orig)
|
35
|
+
super
|
36
|
+
@table = @table.dup
|
37
|
+
@table.each_key{|key| new_ostruct_member(key)}
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_h
|
41
|
+
@table.dup
|
42
|
+
end
|
43
|
+
|
44
|
+
def each_pair
|
45
|
+
return to_enum(__method__) { @table.size } unless block_given?
|
46
|
+
@table.each_pair{|p| yield p}
|
47
|
+
end
|
48
|
+
|
49
|
+
def marshal_dump
|
50
|
+
@table
|
51
|
+
end
|
52
|
+
|
53
|
+
def marshal_load(x)
|
54
|
+
@table = x
|
55
|
+
@table.each_key{|key| new_ostruct_member(key)}
|
56
|
+
end
|
57
|
+
|
58
|
+
def modifiable
|
59
|
+
begin
|
60
|
+
@modifiable = true
|
61
|
+
rescue
|
62
|
+
raise RuntimeError, "can't modify frozen #{self.class}", caller(3)
|
63
|
+
end
|
64
|
+
@table
|
65
|
+
end
|
66
|
+
protected :modifiable
|
67
|
+
|
68
|
+
def method_missing(mid, *args) # :nodoc:
|
69
|
+
mname = mid.id2name
|
70
|
+
len = args.length
|
71
|
+
if mname.chomp!('=')
|
72
|
+
if len != 1
|
73
|
+
raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1)
|
74
|
+
end
|
75
|
+
modifiable[new_ostruct_member(mname)] = args[0]
|
76
|
+
elsif len == 0
|
77
|
+
@table[mid]
|
78
|
+
else
|
79
|
+
err = NoMethodError.new "undefined method `#{mid}' for #{self}", mid, args
|
80
|
+
err.set_backtrace caller(1)
|
81
|
+
raise err
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def [](name)
|
86
|
+
@table[name.to_sym]
|
87
|
+
end
|
88
|
+
|
89
|
+
def []=(name, value)
|
90
|
+
modifiable[new_ostruct_member(name)] = value
|
91
|
+
end
|
92
|
+
|
93
|
+
InspectKey = :__inspect_key__ # :nodoc:
|
94
|
+
|
95
|
+
def inspect
|
96
|
+
str = "#<#{self.class}"
|
97
|
+
|
98
|
+
ids = (Thread.current[InspectKey] ||= [])
|
99
|
+
if ids.include?(object_id)
|
100
|
+
return str << ' ...>'
|
101
|
+
end
|
102
|
+
|
103
|
+
ids << object_id
|
104
|
+
begin
|
105
|
+
first = true
|
106
|
+
for k,v in @table
|
107
|
+
str << "," unless first
|
108
|
+
first = false
|
109
|
+
str << " #{k}=#{v.inspect}"
|
110
|
+
end
|
111
|
+
return str << '>'
|
112
|
+
ensure
|
113
|
+
ids.pop
|
114
|
+
end
|
115
|
+
end
|
116
|
+
alias :to_s :inspect
|
117
|
+
|
118
|
+
attr_reader :table # :nodoc:
|
119
|
+
protected :table
|
120
|
+
|
121
|
+
def ==(other)
|
122
|
+
return false unless other.kind_of?(OpenStruct)
|
123
|
+
@table == other.table
|
124
|
+
end
|
125
|
+
|
126
|
+
def eql?(other)
|
127
|
+
return false unless other.kind_of?(OpenStruct)
|
128
|
+
@table.eql?(other.table)
|
129
|
+
end
|
130
|
+
|
131
|
+
def hash
|
132
|
+
@table.hash
|
133
|
+
end
|
17
134
|
end
|
@@ -22,4 +22,5 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.add_development_dependency "bundler", "~> 1.6"
|
23
23
|
spec.add_development_dependency 'rake', '~> 10.4', '>= 10.4.2'
|
24
24
|
spec.add_development_dependency 'minitest', '~> 5.4', '>= 5.4.3'
|
25
|
+
spec.add_development_dependency 'benchmark-ips', '~> 2.6'
|
25
26
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: persistent_open_struct
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- amcaplan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-07-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -64,6 +64,20 @@ dependencies:
|
|
64
64
|
- - ">="
|
65
65
|
- !ruby/object:Gem::Version
|
66
66
|
version: 5.4.3
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: benchmark-ips
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - "~>"
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '2.6'
|
74
|
+
type: :development
|
75
|
+
prerelease: false
|
76
|
+
version_requirements: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - "~>"
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '2.6'
|
67
81
|
description: Unlike OpenStruct, which defines singleton methods on an object, PersistentOpenStruct
|
68
82
|
defines methods on the class. This is useful when storing many hashes with the
|
69
83
|
same keys as OpenStructs.
|
@@ -105,7 +119,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
105
119
|
version: '0'
|
106
120
|
requirements: []
|
107
121
|
rubyforge_project:
|
108
|
-
rubygems_version: 2.
|
122
|
+
rubygems_version: 2.5.1
|
109
123
|
signing_key:
|
110
124
|
specification_version: 4
|
111
125
|
summary: A variant of OpenStruct that persists defined methods
|