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.
@@ -11,11 +11,17 @@
11
11
  // comparisons as d gets further from 4.
12
12
  #define DHEAP_MAX_D 32
13
13
 
14
- #define DHEAP_DEFAULT_SIZE 16
15
- #define DHEAP_MAX_SIZE (LONG_MAX / (int)sizeof(long double))
14
+ typedef long double SCORE;
16
15
 
17
- // 10MB
18
- #define DHEAP_CAPA_INCR_MAX (10 * 1024 * 1024 / (int)sizeof(long double))
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
 
@@ -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")
@@ -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
@@ -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
- def initialize
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 <<(score)
102
- raise ArgumentError unless score
103
- @a.push(score)
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
- break if @a[parent_index] <= @a[index]
109
- @a[index] = @a[parent_index]
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
- self
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
- @a[0] = shifting = @a.last
121
- @a.pop
122
- # shift down
123
- index = 0
127
+ value = @a.pop
124
128
  last_index = @a.size - 1
125
- while (child_index = index * 2 + 1) <= last_index
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 && @a[child_index + 1] < @a[child_index]
128
- child_index += 1
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 @a[index] <= @a[child_index]
131
- @a[index] = @a[child_index]
146
+ break if value <= child_value
147
+ @a[index] = child_value
132
148
  index = child_index
133
- @a[index] = shifting
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, last = @a.size - 1)
141
- pscore = @a[idx]
142
- child = idx * 2 + 1
143
- if child <= last
144
- cscore = check_heap!(child)
145
- raise "#{pscore} > #{cscore}" if pscore > cscore
146
- end
147
- child += 1
148
- if child <= last
149
- check_heap!(child)
150
- cscore = check_heap!(child)
151
- raise "#{pscore} > #{cscore}" if pscore > cscore
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
- pscore
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
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class DHeap
4
- VERSION = "0.4.0"
4
+ VERSION = "0.5.0"
5
5
 
6
6
  end
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.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-12 00:00:00.000000000 Z
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