d_heap 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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