d_heap 0.4.0 → 0.5.0
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/CHANGELOG.md +19 -2
- data/Gemfile +4 -0
- data/Gemfile.lock +7 -1
- data/README.md +131 -107
- data/benchmarks/perf.rb +29 -0
- data/benchmarks/push_n.yml +6 -3
- data/benchmarks/push_n_pop_n.yml +4 -0
- data/benchmarks/push_pop.yml +6 -3
- data/benchmarks/stackprof.rb +31 -0
- data/docs/benchmarks-2.txt +63 -40
- data/docs/benchmarks-mem.txt +39 -0
- data/docs/benchmarks.txt +337 -265
- data/ext/d_heap/d_heap.c +202 -304
- data/ext/d_heap/d_heap.h +10 -4
- data/ext/d_heap/extconf.rb +8 -1
- data/lib/d_heap.rb +24 -0
- data/lib/d_heap/benchmarks.rb +2 -1
- data/lib/d_heap/benchmarks/benchmarker.rb +3 -0
- data/lib/d_heap/benchmarks/implementations.rb +86 -32
- data/lib/d_heap/version.rb +1 -1
- metadata +5 -2
data/ext/d_heap/d_heap.h
CHANGED
@@ -11,11 +11,17 @@
|
|
11
11
|
// comparisons as d gets further from 4.
|
12
12
|
#define DHEAP_MAX_D 32
|
13
13
|
|
14
|
-
|
15
|
-
#define DHEAP_MAX_SIZE (LONG_MAX / (int)sizeof(long double))
|
14
|
+
typedef long double SCORE;
|
16
15
|
|
17
|
-
|
18
|
-
|
16
|
+
typedef struct dheap_entry {
|
17
|
+
SCORE score;
|
18
|
+
VALUE value;
|
19
|
+
} ENTRY;
|
20
|
+
|
21
|
+
#define DHEAP_DEFAULT_SIZE 256
|
22
|
+
#define DHEAP_MAX_SIZE (LONG_MAX / (int)sizeof(ENTRY))
|
23
|
+
|
24
|
+
#define DHEAP_CAPA_INCR_MAX (10 * 1024 * 1024 / (int)sizeof(ENTRY))
|
19
25
|
|
20
26
|
VALUE rb_cDHeap;
|
21
27
|
|
data/ext/d_heap/extconf.rb
CHANGED
@@ -4,6 +4,14 @@ require "mkmf"
|
|
4
4
|
|
5
5
|
# For testing in CI (because I don't otherwise have easy access to Mac OS):
|
6
6
|
# $CFLAGS << " -D__D_HEAP_DEBUG" if /darwin/ =~ RUBY_PLATFORM
|
7
|
+
# $CFLAGS << " -debug inline-debug-info "
|
8
|
+
# $CFLAGS << " -g -ginline-points "
|
9
|
+
# $CFLAGS << " -fno-omit-frame-pointer "
|
10
|
+
|
11
|
+
# CONFIG["debugflags"] << " -ggdb3 -gstatement-frontiers -ginline-points "
|
12
|
+
CONFIG["optflags"] << " -O3 "
|
13
|
+
CONFIG["optflags"] << " -fno-omit-frame-pointer "
|
14
|
+
CONFIG["warnflags"] << " -Werror"
|
7
15
|
|
8
16
|
have_func "rb_gc_mark_movable" # since ruby-2.7
|
9
17
|
|
@@ -12,5 +20,4 @@ check_sizeof("unsigned long long")
|
|
12
20
|
check_sizeof("long double")
|
13
21
|
have_macro("LDBL_MANT_DIG", "float.h")
|
14
22
|
|
15
|
-
CONFIG["warnflags"] << " -Werror"
|
16
23
|
create_makefile("d_heap/d_heap")
|
data/lib/d_heap.rb
CHANGED
@@ -14,11 +14,35 @@ require "d_heap/version"
|
|
14
14
|
# worst-case time complexity.
|
15
15
|
#
|
16
16
|
class DHeap
|
17
|
+
alias deq pop
|
18
|
+
alias enq push
|
19
|
+
alias first peek
|
20
|
+
alias pop_below pop_lt
|
21
|
+
|
22
|
+
alias length size
|
23
|
+
alias count size
|
24
|
+
|
17
25
|
# ruby 3.0+ (2.x can just use inherited initialize_clone)
|
18
26
|
if Object.instance_method(:initialize_clone).arity == -1
|
27
|
+
# @!visibility private
|
19
28
|
def initialize_clone(other, freeze: nil)
|
20
29
|
__init_clone__(other, freeze ? true : freeze)
|
21
30
|
end
|
22
31
|
end
|
23
32
|
|
33
|
+
# Consumes the heap by popping each minumum value until it is empty.
|
34
|
+
#
|
35
|
+
# If you want to iterate over the heap without consuming it, you will need to
|
36
|
+
# first call +#dup+
|
37
|
+
#
|
38
|
+
# @yieldparam value [Object] each value that would be popped
|
39
|
+
#
|
40
|
+
# @return [Enumerator] if no block is given
|
41
|
+
# @return [nil] if a block is given
|
42
|
+
def each_pop
|
43
|
+
return to_enum(__method__) unless block_given?
|
44
|
+
yield pop until empty?
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
|
24
48
|
end
|
data/lib/d_heap/benchmarks.rb
CHANGED
@@ -93,7 +93,7 @@ module DHeap::Benchmarks
|
|
93
93
|
include Randomness
|
94
94
|
include Scenarios
|
95
95
|
|
96
|
-
def initq(klass, count = 0)
|
96
|
+
def initq(klass, count = 0, clear: false)
|
97
97
|
queue = klass.new
|
98
98
|
while 0 < count
|
99
99
|
queue << @dheap_bm_random_vals.fetch(
|
@@ -101,6 +101,7 @@ module DHeap::Benchmarks
|
|
101
101
|
)
|
102
102
|
count -= 1
|
103
103
|
end
|
104
|
+
queue.clear if clear
|
104
105
|
queue
|
105
106
|
end
|
106
107
|
|
@@ -87,6 +87,9 @@ module DHeap::Benchmarks
|
|
87
87
|
--runner ips_zero_fail
|
88
88
|
benchmarks/#{file}.yml
|
89
89
|
]
|
90
|
+
if file == "push_n"
|
91
|
+
cmd << "--filter" << /dheap|\bstl\b|\bbsearch\b|\brb_heap\b/.to_s
|
92
|
+
end
|
90
93
|
env = ENV.to_h.merge(
|
91
94
|
"BENCH_N" => size.to_s,
|
92
95
|
"RUBYLIB" => File.expand_path("../..", __dir__),
|
@@ -1,13 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "fc"
|
4
|
+
|
3
5
|
module DHeap::Benchmarks
|
4
6
|
|
5
7
|
# base class for example priority queues
|
6
8
|
class ExamplePriorityQueue
|
7
9
|
attr_reader :a
|
8
10
|
|
9
|
-
|
11
|
+
# quick initialization by simply sorting the array once.
|
12
|
+
def initialize(count = nil, &block)
|
10
13
|
@a = []
|
14
|
+
return unless count
|
15
|
+
count.times {|i| @a << block.call(i) }
|
16
|
+
@a.sort!
|
11
17
|
end
|
12
18
|
|
13
19
|
def clear
|
@@ -98,70 +104,118 @@ module DHeap::Benchmarks
|
|
98
104
|
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
99
105
|
class RbHeap < ExamplePriorityQueue
|
100
106
|
|
101
|
-
def <<(
|
102
|
-
raise ArgumentError unless
|
103
|
-
@a.push(
|
107
|
+
def <<(value)
|
108
|
+
raise ArgumentError unless value
|
109
|
+
@a.push(value)
|
104
110
|
# shift up
|
105
111
|
index = @a.size - 1
|
106
112
|
while 0 < index # rubocop:disable Style/NumericPredicate
|
107
113
|
parent_index = (index - 1) / 2
|
108
|
-
|
109
|
-
|
114
|
+
parent_value = @a[parent_index]
|
115
|
+
break if parent_value <= value
|
116
|
+
@a[index] = parent_value
|
110
117
|
index = parent_index
|
111
|
-
@a[index] = score
|
112
|
-
# check_heap!(index)
|
113
118
|
end
|
114
|
-
|
119
|
+
@a[index] = value
|
120
|
+
# dbg "__push__(%p)" % [value]
|
121
|
+
# check_heap!(index)
|
115
122
|
end
|
116
123
|
|
117
124
|
def pop
|
118
125
|
return if @a.empty?
|
119
126
|
popped = @a.first
|
120
|
-
|
121
|
-
@a.pop
|
122
|
-
# shift down
|
123
|
-
index = 0
|
127
|
+
value = @a.pop
|
124
128
|
last_index = @a.size - 1
|
125
|
-
|
129
|
+
last_parent = (last_index - 1) / 2
|
130
|
+
return popped unless 0 <= last_index
|
131
|
+
|
132
|
+
# sift down from 0
|
133
|
+
index = 0
|
134
|
+
child_index = 1
|
135
|
+
while index <= last_parent
|
136
|
+
child_value = @a[child_index]
|
126
137
|
# select min child
|
127
|
-
if child_index < last_index
|
128
|
-
child_index
|
138
|
+
if child_index < last_index
|
139
|
+
other_child_index = child_index + 1
|
140
|
+
other_child_value = @a[other_child_index]
|
141
|
+
if other_child_value < child_value
|
142
|
+
child_value = other_child_value
|
143
|
+
child_index = other_child_index
|
144
|
+
end
|
129
145
|
end
|
130
|
-
break if
|
131
|
-
@a[index] =
|
146
|
+
break if value <= child_value
|
147
|
+
@a[index] = child_value
|
132
148
|
index = child_index
|
133
|
-
|
149
|
+
child_index = index * 2 + 1
|
134
150
|
end
|
151
|
+
@a[index] = value
|
152
|
+
|
135
153
|
popped
|
136
154
|
end
|
137
155
|
|
138
156
|
private
|
139
157
|
|
140
|
-
def check_heap!(idx
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
raise "#{
|
158
|
+
def check_heap!(idx)
|
159
|
+
check_heap_up!(idx)
|
160
|
+
check_heap_dn!(idx)
|
161
|
+
end
|
162
|
+
|
163
|
+
# compares index to its parent
|
164
|
+
def check_heap_at!(idx)
|
165
|
+
value = @a[idx]
|
166
|
+
unless idx <= 0
|
167
|
+
pidx = (idx - 1) / 2
|
168
|
+
pval = @a[pidx]
|
169
|
+
raise "@a[#{idx}] == #{value}, #{pval} > #{value}" if pval > value
|
152
170
|
end
|
153
|
-
|
171
|
+
value
|
172
|
+
end
|
173
|
+
|
174
|
+
def check_heap_up!(idx)
|
175
|
+
return if idx <= 0
|
176
|
+
pidx = (idx - 1) / 2
|
177
|
+
check_heap_at!(pidx)
|
178
|
+
check_heap_up!(pidx)
|
179
|
+
end
|
180
|
+
|
181
|
+
def check_heap_dn!(idx)
|
182
|
+
return unless @a.size <= idx
|
183
|
+
check_heap_at!(idx)
|
184
|
+
check_heap_down!(idx * 2 + 1)
|
185
|
+
check_heap_down!(idx * 2 + 2)
|
154
186
|
end
|
155
187
|
|
156
188
|
end
|
157
189
|
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
158
190
|
|
191
|
+
# minor adjustments to the "priority_queue_cxx" gem, to match the API
|
192
|
+
class CppSTL
|
193
|
+
|
194
|
+
def initialize
|
195
|
+
clear
|
196
|
+
end
|
197
|
+
|
198
|
+
def <<(value); @q.push(value, value) end
|
199
|
+
|
200
|
+
def clear
|
201
|
+
@q = FastContainers::PriorityQueue.new(:min)
|
202
|
+
end
|
203
|
+
|
204
|
+
def pop
|
205
|
+
@q.pop
|
206
|
+
rescue RuntimeError
|
207
|
+
nil
|
208
|
+
end
|
209
|
+
|
210
|
+
end
|
211
|
+
|
159
212
|
# Different duck-typed priority queue implemenations
|
160
213
|
IMPLEMENTATIONS = [
|
161
214
|
OpenStruct.new(name: " push and resort", klass: Sorting).freeze,
|
162
215
|
OpenStruct.new(name: " find min + del", klass: FindMin).freeze,
|
163
216
|
OpenStruct.new(name: "bsearch + insert", klass: BSearch).freeze,
|
164
217
|
OpenStruct.new(name: "ruby binary heap", klass: RbHeap).freeze,
|
218
|
+
OpenStruct.new(name: "C++STL PriorityQ", klass: CppSTL).freeze,
|
165
219
|
OpenStruct.new(name: "quaternary DHeap", klass: DHeap).freeze,
|
166
220
|
].freeze
|
167
221
|
|
data/lib/d_heap/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: d_heap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- nicholas a. evans
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-01-
|
11
|
+
date: 2021-01-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: benchmark_driver
|
@@ -60,9 +60,11 @@ files:
|
|
60
60
|
- LICENSE.txt
|
61
61
|
- README.md
|
62
62
|
- Rakefile
|
63
|
+
- benchmarks/perf.rb
|
63
64
|
- benchmarks/push_n.yml
|
64
65
|
- benchmarks/push_n_pop_n.yml
|
65
66
|
- benchmarks/push_pop.yml
|
67
|
+
- benchmarks/stackprof.rb
|
66
68
|
- bin/bench_n
|
67
69
|
- bin/benchmark-driver
|
68
70
|
- bin/benchmarks
|
@@ -74,6 +76,7 @@ files:
|
|
74
76
|
- bin/setup
|
75
77
|
- d_heap.gemspec
|
76
78
|
- docs/benchmarks-2.txt
|
79
|
+
- docs/benchmarks-mem.txt
|
77
80
|
- docs/benchmarks.txt
|
78
81
|
- docs/profile.txt
|
79
82
|
- ext/d_heap/d_heap.c
|