persistent_open_struct 0.0.1 → 0.0.2
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/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
|