d_heap 0.2.2 → 0.6.1
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/.github/workflows/main.yml +2 -2
- data/.gitignore +1 -0
- data/.rubocop.yml +40 -1
- data/.yardopts +10 -0
- data/CHANGELOG.md +76 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +26 -1
- data/N +7 -0
- data/README.md +358 -147
- data/benchmarks/perf.rb +29 -0
- data/benchmarks/push_n.yml +35 -0
- data/benchmarks/push_n_pop_n.yml +52 -0
- data/benchmarks/push_pop.yml +32 -0
- data/benchmarks/stackprof.rb +31 -0
- data/bin/bench_charts +13 -0
- data/bin/bench_n +7 -0
- data/bin/benchmark-driver +29 -0
- data/bin/benchmarks +10 -0
- data/bin/profile +10 -0
- data/d_heap.gemspec +5 -2
- data/docs/benchmarks-2.txt +75 -0
- data/docs/benchmarks-mem.txt +39 -0
- data/docs/benchmarks.txt +515 -0
- data/docs/profile.txt +392 -0
- data/ext/d_heap/d_heap.c +824 -246
- data/ext/d_heap/extconf.rb +16 -3
- data/images/push_n.png +0 -0
- data/images/push_n_pop_n.png +0 -0
- data/images/push_pop.png +0 -0
- data/images/wikipedia-min-heap.png +0 -0
- data/lib/benchmark_driver/runner/ips_zero_fail.rb +158 -0
- data/lib/d_heap.rb +92 -3
- data/lib/d_heap/benchmarks.rb +112 -0
- data/lib/d_heap/benchmarks/benchmarker.rb +116 -0
- data/lib/d_heap/benchmarks/implementations.rb +224 -0
- data/lib/d_heap/benchmarks/profiler.rb +71 -0
- data/lib/d_heap/benchmarks/rspec_matchers.rb +352 -0
- data/lib/d_heap/version.rb +1 -1
- metadata +60 -6
- data/ext/d_heap/d_heap.h +0 -65
data/ext/d_heap/extconf.rb
CHANGED
@@ -2,8 +2,21 @@
|
|
2
2
|
|
3
3
|
require "mkmf"
|
4
4
|
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
5
|
+
# For testing in CI (because I don't otherwise have easy access to Mac OS):
|
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
|
+
if enable_config("debug")
|
12
|
+
CONFIG["warnflags"] << " -Werror -Wpedantic "
|
13
|
+
end
|
14
|
+
|
15
|
+
have_func "rb_gc_mark_movable" # since ruby-2.7
|
16
|
+
|
17
|
+
check_sizeof("long")
|
18
|
+
check_sizeof("unsigned long long")
|
19
|
+
check_sizeof("long double")
|
20
|
+
have_macro("LDBL_MANT_DIG", "float.h")
|
8
21
|
|
9
22
|
create_makefile("d_heap/d_heap")
|
data/images/push_n.png
ADDED
Binary file
|
Binary file
|
data/images/push_pop.png
ADDED
Binary file
|
Binary file
|
@@ -0,0 +1,158 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "English" # $CHILD_STATUS
|
4
|
+
require "timeout" # Timeout::Error
|
5
|
+
|
6
|
+
require "benchmark_driver"
|
7
|
+
|
8
|
+
# monkey-patch to convert miniscule values to 0.0
|
9
|
+
class BenchmarkDriver::Output::Compare
|
10
|
+
|
11
|
+
# monkey-patch to convert miniscule values to 0.0
|
12
|
+
module MinisculeToZero
|
13
|
+
|
14
|
+
def humanize(value, width = 10)
|
15
|
+
value <= 0.0.next_float.next_float ? 0.0 : super(value, width)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
prepend MinisculeToZero
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
# A simple patch to let slow specs error out without
|
25
|
+
class BenchmarkDriver::Runner::IpsZeroFail < BenchmarkDriver::Runner::Ips
|
26
|
+
METRIC = BenchmarkDriver::Runner::Ips::METRIC
|
27
|
+
|
28
|
+
# always run at least once
|
29
|
+
class Job < BenchmarkDriver::DefaultJob
|
30
|
+
attr_accessor :warmup_value, :warmup_duration, :warmup_loop_count
|
31
|
+
|
32
|
+
def add_warmup_attrs(value, duration, loop_count)
|
33
|
+
self.warmup_value = value
|
34
|
+
self.warmup_duration = duration
|
35
|
+
self.warmup_loop_count = loop_count
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
# BenchmarkDriver::Runner looks for this class
|
41
|
+
JobParser = BenchmarkDriver::DefaultJobParser.for(klass: Job, metrics: [METRIC])
|
42
|
+
|
43
|
+
# This method is dynamically called by `BenchmarkDriver::JobRunner.run`
|
44
|
+
# @param [Array<BenchmarkDriver::Default::Job>] jobs
|
45
|
+
def run(jobs)
|
46
|
+
jobs = run_all_jobs_warmup(jobs)
|
47
|
+
run_all_jobs_benchmarks(jobs)
|
48
|
+
end
|
49
|
+
|
50
|
+
def run_all_jobs_warmup(jobs)
|
51
|
+
return jobs if jobs.all?(&:loop_count)
|
52
|
+
@output.with_warmup do
|
53
|
+
jobs.map! {|job|
|
54
|
+
# skip warmup if loop_count is set
|
55
|
+
job.loop_count ? job : output_warmup_and_config_job(job)
|
56
|
+
}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def run_all_jobs_benchmarks(jobs)
|
61
|
+
@output.with_benchmark do
|
62
|
+
jobs.each do |job|
|
63
|
+
@output.with_job(name: job.name) do
|
64
|
+
job.runnable_contexts(@contexts).each do |context|
|
65
|
+
run_and_report_job(job, context)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def output_warmup_and_config_job(job)
|
73
|
+
@output.with_job(name: job.name) do
|
74
|
+
context = job.runnable_contexts(@contexts).first
|
75
|
+
value, duration, warmup_loop_count = run_and_report_warmup_job(job, context)
|
76
|
+
loop_count = (warmup_loop_count.to_f * @config.run_duration / duration).floor
|
77
|
+
Job.new(**job.to_h.merge(loop_count: loop_count))
|
78
|
+
.tap {|j| j.add_warmup_attrs(value, duration, warmup_loop_count) }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def run_and_report_warmup_job(job, context)
|
83
|
+
duration, loop_count = run_warmup(job, context: context)
|
84
|
+
value, duration = value_duration(duration: duration, loop_count: loop_count)
|
85
|
+
output_with_context(context) do
|
86
|
+
@output.report(
|
87
|
+
values: {metric => value}, duration: duration, loop_count: loop_count
|
88
|
+
)
|
89
|
+
end
|
90
|
+
[value, duration, loop_count]
|
91
|
+
end
|
92
|
+
|
93
|
+
def run_and_report_job(job, context)
|
94
|
+
result, loop_count = run_job_with_repeater(job, context)
|
95
|
+
value, duration = result.value
|
96
|
+
output_with_context(context) do
|
97
|
+
@output.report(
|
98
|
+
values: { metric => value },
|
99
|
+
all_values: { metric => result.all_values },
|
100
|
+
duration: duration,
|
101
|
+
loop_count: loop_count,
|
102
|
+
)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def output_with_context(context, &block)
|
107
|
+
@output.with_context(
|
108
|
+
name: context.name,
|
109
|
+
executable: context.executable,
|
110
|
+
gems: context.gems,
|
111
|
+
prelude: context.prelude,
|
112
|
+
&block
|
113
|
+
)
|
114
|
+
end
|
115
|
+
|
116
|
+
def run_job_with_repeater(job, context)
|
117
|
+
repeat_params = { config: @config, larger_better: true, rest_on_average: :average }
|
118
|
+
if job.loop_count&.positive?
|
119
|
+
run_job_with_own_loop_count(job, context, repeat_params)
|
120
|
+
else
|
121
|
+
run_job_with_warmup_loop_count(job, context, repeat_params)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def run_job_with_own_loop_count(job, context, repeat_params)
|
126
|
+
loop_count = job.loop_count
|
127
|
+
result = BenchmarkDriver::Repeater.with_repeat(**repeat_params) {
|
128
|
+
run_benchmark(job, context: context)
|
129
|
+
}
|
130
|
+
[result, loop_count]
|
131
|
+
end
|
132
|
+
|
133
|
+
def run_job_with_warmup_loop_count(job, context, repeat_params)
|
134
|
+
loop_count = job.warmup_loop_count
|
135
|
+
repeater_value = [job.warmup_value, job.warmup_duration]
|
136
|
+
result = BenchmarkDriver::Repeater::RepeatResult.new(
|
137
|
+
value: repeater_value, all_values: [repeater_value]
|
138
|
+
)
|
139
|
+
[result, loop_count]
|
140
|
+
end
|
141
|
+
|
142
|
+
def run_warmup(job, context:)
|
143
|
+
start = Time.now
|
144
|
+
super(job, context: context)
|
145
|
+
rescue Timeout::Error
|
146
|
+
[Time.now - start, 0.0.next_float]
|
147
|
+
end
|
148
|
+
|
149
|
+
def execute(*args, exception: true)
|
150
|
+
super
|
151
|
+
rescue RuntimeError => ex
|
152
|
+
if args.include?("timeout") && $CHILD_STATUS&.exitstatus == 124
|
153
|
+
raise Timeout::Error, ex.message
|
154
|
+
end
|
155
|
+
raise ex
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
data/lib/d_heap.rb
CHANGED
@@ -10,13 +10,102 @@ require "d_heap/version"
|
|
10
10
|
# the nodes have _d_ children instead of 2. This allows for "decrease priority"
|
11
11
|
# operations to be performed more quickly with the tradeoff of slower delete
|
12
12
|
# minimum. Additionally, _d_-ary heaps can have better memory cache behavior than
|
13
|
-
# binary heaps, allowing them to
|
13
|
+
# binary heaps, allowing them to pop more quickly in practice despite slower
|
14
14
|
# worst-case time complexity.
|
15
15
|
#
|
16
|
+
# Although _d_ can be configured when creating the heap, it's usually best to
|
17
|
+
# keep the default value of 4, because d=4 gives the smallest coefficient for
|
18
|
+
# <tt>(d + 1) log n / log d</tt> result. As always, use benchmarks for your
|
19
|
+
# particular use-case.
|
20
|
+
#
|
21
|
+
# @example Basic push, peek, and pop
|
22
|
+
# # create some example objects to place in our heap
|
23
|
+
# Task = Struct.new(:id, :time) do
|
24
|
+
# def to_f; time.to_f end
|
25
|
+
# end
|
26
|
+
# t1 = Task.new(1, Time.now + 5*60)
|
27
|
+
# t2 = Task.new(2, Time.now + 50)
|
28
|
+
# t3 = Task.new(3, Time.now + 60)
|
29
|
+
# t4 = Task.new(4, Time.now + 5)
|
30
|
+
#
|
31
|
+
# # create the heap
|
32
|
+
# require "d_heap"
|
33
|
+
# heap = DHeap.new
|
34
|
+
#
|
35
|
+
# # push with an explicit score (which might be extrinsic to the value)
|
36
|
+
# heap.push t1, t1.to_f
|
37
|
+
#
|
38
|
+
# # the score will be implicitly cast with Float, so any object with #to_f
|
39
|
+
# heap.push t2, t2
|
40
|
+
#
|
41
|
+
# # if the object has an intrinsic score via #to_f, "<<" is the simplest API
|
42
|
+
# heap << t3 << t4
|
43
|
+
#
|
44
|
+
# # pop returns the lowest scored item, and removes it from the heap
|
45
|
+
# heap.pop # => #<struct Task id=4, time=2021-01-17 17:02:22.5574 -0500>
|
46
|
+
# heap.pop # => #<struct Task id=2, time=2021-01-17 17:03:07.5574 -0500>
|
47
|
+
#
|
48
|
+
# # peek returns the lowest scored item, without removing it from the heap
|
49
|
+
# heap.peek # => #<struct Task id=3, time=2021-01-17 17:03:17.5574 -0500>
|
50
|
+
# heap.pop # => #<struct Task id=3, time=2021-01-17 17:03:17.5574 -0500>
|
51
|
+
#
|
52
|
+
# # pop_lte handles the common "h.pop if h.peek_score < max" pattern
|
53
|
+
# heap.pop_lte(Time.now + 65) # => nil
|
54
|
+
#
|
55
|
+
# # the heap size can be inspected with size and empty?
|
56
|
+
# heap.empty? # => false
|
57
|
+
# heap.size # => 1
|
58
|
+
# heap.pop # => #<struct Task id=1, time=2021-01-17 17:07:17.5574 -0500>
|
59
|
+
# heap.empty? # => true
|
60
|
+
# heap.size # => 0
|
61
|
+
#
|
62
|
+
# # popping from an empty heap returns nil
|
63
|
+
# heap.pop # => nil
|
64
|
+
#
|
16
65
|
class DHeap
|
66
|
+
alias deq pop
|
67
|
+
alias shift pop
|
68
|
+
alias next pop
|
69
|
+
alias pop_all_lt pop_all_below
|
70
|
+
alias pop_below pop_lt
|
71
|
+
|
72
|
+
alias enq push
|
73
|
+
|
74
|
+
alias first peek
|
75
|
+
|
76
|
+
alias length size
|
77
|
+
alias count size
|
78
|
+
|
79
|
+
# Initialize a _d_-ary min-heap.
|
80
|
+
#
|
81
|
+
# @param d [Integer] Number of children for each parent node.
|
82
|
+
# Higher values generally speed up push but slow down pop.
|
83
|
+
# If all pushes are popped, the default is probably best.
|
84
|
+
# @param capacity [Integer] initial capacity of the heap.
|
85
|
+
def initialize(d: DEFAULT_D, capacity: DEFAULT_CAPA) # rubocop:disable Naming/MethodParameterName
|
86
|
+
__init_without_kw__(d, capacity)
|
87
|
+
end
|
17
88
|
|
18
|
-
|
19
|
-
|
89
|
+
# Consumes the heap by popping each minumum value until it is empty.
|
90
|
+
#
|
91
|
+
# If you want to iterate over the heap without consuming it, you will need to
|
92
|
+
# first call +#dup+
|
93
|
+
#
|
94
|
+
# @param with_score [Boolean] if scores shoul also be yielded
|
95
|
+
#
|
96
|
+
# @yieldparam value [Object] each value that would be popped
|
97
|
+
# @yieldparam score [Numeric] each value's score, if +with_scores+ is true
|
98
|
+
#
|
99
|
+
# @return [Enumerator] if no block is given
|
100
|
+
# @return [nil] if a block is given
|
101
|
+
def each_pop(with_scores: false)
|
102
|
+
return to_enum(__method__, with_scores: with_scores) unless block_given?
|
103
|
+
if with_scores
|
104
|
+
yield(*pop_with_score) until empty?
|
105
|
+
else
|
106
|
+
yield pop until empty?
|
107
|
+
end
|
108
|
+
nil
|
20
109
|
end
|
21
110
|
|
22
111
|
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "d_heap"
|
4
|
+
require "ostruct"
|
5
|
+
|
6
|
+
# Different benchmark scenarios and implementations to benchmark
|
7
|
+
module DHeap::Benchmarks
|
8
|
+
|
9
|
+
def self.puts_version_info(type = "Benchmark", io = $stdout)
|
10
|
+
io.puts "#{type} run at %s" % [Time.now]
|
11
|
+
io.puts "ruby v%s, DHeap v%s" % [RUBY_VERSION, DHeap::VERSION]
|
12
|
+
io.puts
|
13
|
+
end
|
14
|
+
|
15
|
+
# rubocop:disable Style/NumericPredicate
|
16
|
+
|
17
|
+
# moves "rand" outside the benchmarked code, to avoid measuring that too.
|
18
|
+
module Randomness
|
19
|
+
|
20
|
+
def default_randomness_size; 1_000_000 end
|
21
|
+
|
22
|
+
def fill_random_vals(target_size = default_randomness_size, io: $stdout)
|
23
|
+
@dheap_bm_random_vals ||= []
|
24
|
+
count = target_size - @dheap_bm_random_vals.length
|
25
|
+
return 0 if count <= 0
|
26
|
+
millions = (count / 1_000_000.0).round(3)
|
27
|
+
io&.puts "~~~~~~ filling @dheap_bm_random_vals with #{millions}M ~~~~~~"
|
28
|
+
io&.flush
|
29
|
+
count.times do @dheap_bm_random_vals << rand(0..10_000) end
|
30
|
+
@dheap_bm_random_len = @dheap_bm_random_vals.length
|
31
|
+
@dheap_bm_random_idx = (((@dheap_bm_random_idx || -1) + 1) % @dheap_bm_random_len)
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def random_val
|
36
|
+
@dheap_bm_random_vals.fetch(
|
37
|
+
@dheap_bm_random_idx = ((@dheap_bm_random_idx + 1) % @dheap_bm_random_len)
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
# different scenarios to be benchmarked or profiled
|
44
|
+
module Scenarios
|
45
|
+
|
46
|
+
def push_n_multiple_queues(count, *queues)
|
47
|
+
while 0 < count
|
48
|
+
value = @dheap_bm_random_vals.fetch(
|
49
|
+
@dheap_bm_random_idx = ((@dheap_bm_random_idx + 1) % @dheap_bm_random_len)
|
50
|
+
)
|
51
|
+
queues.each do |queue|
|
52
|
+
queue << value
|
53
|
+
end
|
54
|
+
count -= 1
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def push_n(queue, count)
|
59
|
+
while 0 < count
|
60
|
+
queue << @dheap_bm_random_vals.fetch(
|
61
|
+
@dheap_bm_random_idx = ((@dheap_bm_random_idx + 1) % @dheap_bm_random_len)
|
62
|
+
)
|
63
|
+
count -= 1
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def push_n_then_pop_n(queue, count) # rubocop:disable Metrics/MethodLength
|
68
|
+
i = 0
|
69
|
+
while i < count
|
70
|
+
queue << @dheap_bm_random_vals.fetch(
|
71
|
+
@dheap_bm_random_idx = ((@dheap_bm_random_idx + 1) % @dheap_bm_random_len)
|
72
|
+
)
|
73
|
+
i += 1
|
74
|
+
end
|
75
|
+
while 0 < i
|
76
|
+
queue.pop
|
77
|
+
i -= 1
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def repeated_push_pop(queue, count)
|
82
|
+
while 0 < count
|
83
|
+
queue << @dheap_bm_random_vals.fetch(
|
84
|
+
@dheap_bm_random_idx = ((@dheap_bm_random_idx + 1) % @dheap_bm_random_len)
|
85
|
+
)
|
86
|
+
queue.pop
|
87
|
+
count -= 1
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
include Randomness
|
94
|
+
include Scenarios
|
95
|
+
|
96
|
+
def initq(klass, count = 0, clear: false)
|
97
|
+
queue = klass.new
|
98
|
+
while 0 < count
|
99
|
+
queue << @dheap_bm_random_vals.fetch(
|
100
|
+
@dheap_bm_random_idx = ((@dheap_bm_random_idx + 1) % @dheap_bm_random_len)
|
101
|
+
)
|
102
|
+
count -= 1
|
103
|
+
end
|
104
|
+
queue.clear if clear
|
105
|
+
queue
|
106
|
+
end
|
107
|
+
|
108
|
+
# rubocop:enable Style/NumericPredicate
|
109
|
+
|
110
|
+
require "d_heap/benchmarks/implementations"
|
111
|
+
|
112
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "d_heap/benchmarks"
|
4
|
+
|
5
|
+
require "benchmark_driver"
|
6
|
+
require "shellwords"
|
7
|
+
require "English"
|
8
|
+
|
9
|
+
module DHeap::Benchmarks
|
10
|
+
# Benchmarks different implementations with different sizes
|
11
|
+
class Benchmarker
|
12
|
+
include Randomness
|
13
|
+
include Scenarios
|
14
|
+
|
15
|
+
N_COUNTS = [
|
16
|
+
5, # 1 + 4
|
17
|
+
21, # 1 + 4 + 16
|
18
|
+
85, # 1 + 4 + 16 + 64
|
19
|
+
341, # 1 + 4 + 16 + 64 + 256
|
20
|
+
1365, # 1 + 4 + 16 + 64 + 256 + 1024
|
21
|
+
5461, # 1 + 4 + 16 + 64 + 256 + 1024 + 4096
|
22
|
+
21_845, # 1 + 4 + 16 + 64 + 256 + 1024 + 4096 + 16384
|
23
|
+
87_381, # 1 + 4 + 16 + 64 + 256 + 1024 + 4096 + 16384 + 65536
|
24
|
+
].freeze
|
25
|
+
|
26
|
+
attr_reader :time
|
27
|
+
attr_reader :iterations_for_push_pop
|
28
|
+
attr_reader :io
|
29
|
+
|
30
|
+
def initialize(
|
31
|
+
time: Integer(ENV.fetch("BENCHMARK_TIME", 10)),
|
32
|
+
iterations_for_push_pop: 10_000,
|
33
|
+
io: $stdout
|
34
|
+
)
|
35
|
+
@time = time
|
36
|
+
@iterations_for_push_pop = Integer(iterations_for_push_pop)
|
37
|
+
@io = io
|
38
|
+
end
|
39
|
+
|
40
|
+
def call(queue_size: ENV.fetch("BENCHMARK_QUEUE_SIZE", :unset))
|
41
|
+
DHeap::Benchmarks.puts_version_info("Benchmarking")
|
42
|
+
sizes = (queue_size == :unset) ? N_COUNTS : [Integer(queue_size)]
|
43
|
+
sizes.each do |size|
|
44
|
+
benchmark_size(size)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def benchmark_size(size)
|
49
|
+
sep "#", "Benchmarks with N=#{size} (t=#{time}sec/benchmark)", big: true
|
50
|
+
io.puts
|
51
|
+
benchmark_push_n size
|
52
|
+
benchmark_push_n_then_pop_n size
|
53
|
+
benchmark_repeated_push_pop size
|
54
|
+
end
|
55
|
+
|
56
|
+
def benchmark_push_n(queue_size)
|
57
|
+
benchmarking("push N", "push_n", queue_size)
|
58
|
+
end
|
59
|
+
|
60
|
+
def benchmark_push_n_then_pop_n(queue_size)
|
61
|
+
benchmarking("push N then pop N", "push_n_pop_n", queue_size)
|
62
|
+
end
|
63
|
+
|
64
|
+
def benchmark_repeated_push_pop(queue_size)
|
65
|
+
benchmarking(
|
66
|
+
"Push/pop with pre-filled queue (size=N)", "push_pop", queue_size
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
# TODO: move somewhere else...
|
73
|
+
def skip_profiling?(queue_size, impl)
|
74
|
+
impl.klass == DHeap::Benchmarks::PushAndResort && 10_000 < queue_size
|
75
|
+
end
|
76
|
+
|
77
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
78
|
+
|
79
|
+
def benchmarking(name, file, size)
|
80
|
+
Bundler.with_unbundled_env do
|
81
|
+
sep "==", "#{name} (N=#{size})"
|
82
|
+
cmd = %W[
|
83
|
+
bin/benchmark-driver
|
84
|
+
--bundler
|
85
|
+
--run-duration 6
|
86
|
+
--timeout 15
|
87
|
+
--runner ips_zero_fail
|
88
|
+
benchmarks/#{file}.yml
|
89
|
+
]
|
90
|
+
if file == "push_n"
|
91
|
+
cmd << "--filter" << /dheap|\bstl\b|\bbsearch\b|\brb_heap\b/.to_s
|
92
|
+
end
|
93
|
+
env = ENV.to_h.merge(
|
94
|
+
"BENCH_N" => size.to_s,
|
95
|
+
"RUBYLIB" => File.expand_path("../..", __dir__),
|
96
|
+
)
|
97
|
+
system(env, *cmd)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def sep(sep, msg = "", width: 80, big: false)
|
102
|
+
txt = String.new
|
103
|
+
txt += "#{sep * (width / sep.length)}\n" if big
|
104
|
+
txt += sep
|
105
|
+
txt += " #{msg}" if msg && !msg.empty?
|
106
|
+
txt += " " unless big
|
107
|
+
txt += sep * ((width - txt.length) / sep.length) unless big
|
108
|
+
txt += "\n"
|
109
|
+
txt += "#{sep * (width / sep.length)}\n" if big
|
110
|
+
io.print txt
|
111
|
+
end
|
112
|
+
|
113
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|