benchmark-ips 2.3.0 → 2.11.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.
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module Benchmark
2
4
  # Functionality of performaing comparison between reports.
3
5
  #
@@ -24,46 +26,80 @@ module Benchmark
24
26
  # Reduce using to_proc: 247295.4 i/s - 1.13x slower
25
27
  #
26
28
  # Besides regular Calculating report, this will also indicates which one is slower.
29
+ #
30
+ # +x.compare!+ also takes an +order: :baseline+ option.
31
+ #
32
+ # Example:
33
+ # > Benchmark.ips do |x|
34
+ # x.report('Reduce using block') { [*1..10].reduce { |sum, n| sum + n } }
35
+ # x.report('Reduce using tag') { [*1..10].reduce(:+) }
36
+ # x.report('Reduce using to_proc') { [*1..10].reduce(&:+) }
37
+ # x.compare!(order: :baseline)
38
+ # end
39
+ #
40
+ # Calculating -------------------------------------
41
+ # Reduce using block 886.202k (± 2.2%) i/s - 4.521M in 5.103774s
42
+ # Reduce using tag 1.821M (± 1.6%) i/s - 9.111M in 5.004183s
43
+ # Reduce using to_proc 895.948k (± 1.6%) i/s - 4.528M in 5.055368s
44
+ #
45
+ # Comparison:
46
+ # Reduce using block: 886202.5 i/s
47
+ # Reduce using tag: 1821055.0 i/s - 2.05x (± 0.00) faster
48
+ # Reduce using to_proc: 895948.1 i/s - same-ish: difference falls within error
49
+ #
50
+ # The first report is considered the baseline against which other reports are compared.
27
51
  module Compare
28
52
 
29
53
  # Compare between reports, prints out facts of each report:
30
54
  # runtime, comparative speed difference.
31
- # @param reports [Array<Report>] Reports to compare.
32
- def compare(*reports)
33
- return if reports.size < 2
55
+ # @param entries [Array<Report::Entry>] Reports to compare.
56
+ def compare(*entries, order: :fastest)
57
+ return if entries.size < 2
34
58
 
35
- iter = false
36
- sorted = reports.sort do |a,b|
37
- if a.respond_to? :ips
38
- iter = true
39
- b.ips <=> a.ips
40
- else
41
- a.runtime <=> b.runtime
42
- end
59
+ case order
60
+ when :baseline
61
+ baseline = entries.shift
62
+ sorted = entries.sort_by{ |e| e.stats.central_tendency }.reverse
63
+ when :fastest
64
+ sorted = entries.sort_by{ |e| e.stats.central_tendency }.reverse
65
+ baseline = sorted.shift
66
+ else
67
+ raise ArgumentError, "Unknwon order: #{order.inspect}"
43
68
  end
44
69
 
45
- best = sorted.shift
46
-
47
70
  $stdout.puts "\nComparison:"
48
71
 
49
- if iter
50
- $stdout.printf "%20s: %10.1f i/s\n", best.label, best.ips
51
- else
52
- $stdout.puts "#{best.rjust(20)}: #{best.runtime}s"
53
- end
72
+ $stdout.printf "%20s: %10.1f i/s\n", baseline.label.to_s, baseline.stats.central_tendency
54
73
 
55
74
  sorted.each do |report|
56
75
  name = report.label.to_s
57
76
 
58
- if iter
59
- x = (best.ips.to_f / report.ips.to_f)
60
- $stdout.printf "%20s: %10.1f i/s - %.2fx slower\n", name, report.ips, x
77
+ $stdout.printf "%20s: %10.1f i/s - ", name, report.stats.central_tendency
78
+
79
+ if report.stats.overlaps?(baseline.stats)
80
+ $stdout.print "same-ish: difference falls within error"
81
+ elsif report.stats.central_tendency > baseline.stats.central_tendency
82
+ speedup, error = report.stats.speedup(baseline.stats)
83
+ $stdout.printf "%.2fx ", speedup
84
+ if error
85
+ $stdout.printf " (± %.2f)", error
86
+ end
87
+ $stdout.print " faster"
61
88
  else
62
- x = "%.2f" % (report.ips.to_f / best.ips.to_f)
63
- $stdout.puts "#{name.rjust(20)}: #{report.runtime}s - #{x}x slower"
89
+ slowdown, error = report.stats.slowdown(baseline.stats)
90
+ $stdout.printf "%.2fx ", slowdown
91
+ if error
92
+ $stdout.printf " (± %.2f)", error
93
+ end
94
+ $stdout.print " slower"
64
95
  end
96
+
97
+ $stdout.puts
65
98
  end
66
99
 
100
+ footer = baseline.stats.footer
101
+ $stdout.puts footer.rjust(40) if footer
102
+
67
103
  $stdout.puts
68
104
  end
69
105
  end
@@ -0,0 +1,95 @@
1
+ module Benchmark
2
+ module IPS
3
+ # Benchmark jobs.
4
+ class Job
5
+ # Entries in Benchmark Jobs.
6
+ class Entry
7
+ # Instantiate the Benchmark::IPS::Job::Entry.
8
+ # @param label [#to_s] Label of Benchmarked code.
9
+ # @param action [String, Proc] Code to be benchmarked.
10
+ # @raise [ArgumentError] Raises when action is not String or not responding to +call+.
11
+ def initialize(label, action)
12
+ @label = label
13
+
14
+ # We define #call_times on the singleton class of each Entry instance.
15
+ # That way, there is no polymorphism for `@action.call` inside #call_times.
16
+
17
+ if action.kind_of? String
18
+ compile_string action
19
+ @action = self
20
+ else
21
+ unless action.respond_to? :call
22
+ raise ArgumentError, "invalid action, must respond to #call"
23
+ end
24
+
25
+ @action = action
26
+
27
+ if action.respond_to? :arity and action.arity > 0
28
+ compile_block_with_manual_loop
29
+ else
30
+ compile_block
31
+ end
32
+ end
33
+ end
34
+
35
+ # The label of benchmarking action.
36
+ # @return [#to_s] Label of action.
37
+ attr_reader :label
38
+
39
+ # The benchmarking action.
40
+ # @return [String, Proc] Code to be called, could be String / Proc.
41
+ attr_reader :action
42
+
43
+ # Call action by given times.
44
+ # @param times [Integer] Times to call +@action+.
45
+ # @return [Integer] Number of times the +@action+ has been called.
46
+ def call_times(times)
47
+ raise '#call_times should be redefined per Benchmark::IPS::Job::Entry instance'
48
+ end
49
+
50
+ def compile_block
51
+ m = (class << self; self; end)
52
+ code = <<-CODE
53
+ def call_times(times)
54
+ act = @action
55
+
56
+ i = 0
57
+ while i < times
58
+ act.call
59
+ i += 1
60
+ end
61
+ end
62
+ CODE
63
+ m.class_eval code
64
+ end
65
+
66
+ def compile_block_with_manual_loop
67
+ m = (class << self; self; end)
68
+ code = <<-CODE
69
+ def call_times(times)
70
+ @action.call(times)
71
+ end
72
+ CODE
73
+ m.class_eval code
74
+ end
75
+
76
+ # Compile code into +call_times+ method.
77
+ # @param str [String] Code to be compiled.
78
+ # @return [Symbol] :call_times.
79
+ def compile_string(str)
80
+ m = (class << self; self; end)
81
+ code = <<-CODE
82
+ def call_times(__total);
83
+ __i = 0
84
+ while __i < __total
85
+ #{str};
86
+ __i += 1
87
+ end
88
+ end
89
+ CODE
90
+ m.class_eval code
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,27 @@
1
+ module Benchmark
2
+ module IPS
3
+ class Job
4
+ class NoopReport
5
+ def start_warming
6
+ end
7
+
8
+ def start_running
9
+ end
10
+
11
+ def footer
12
+ end
13
+
14
+ def warming(a, b)
15
+ end
16
+
17
+ def warmup_stats(a, b)
18
+ end
19
+
20
+ def add_report(a, b)
21
+ end
22
+
23
+ alias_method :running, :warming
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,64 @@
1
+ module Benchmark
2
+ module IPS
3
+ class Job
4
+ class StdoutReport
5
+ def initialize
6
+ @last_item = nil
7
+ end
8
+
9
+ def start_warming
10
+ $stdout.puts "Warming up --------------------------------------"
11
+ end
12
+
13
+ def start_running
14
+ $stdout.puts "Calculating -------------------------------------"
15
+ end
16
+
17
+ def warming(label, _warmup)
18
+ $stdout.print rjust(label)
19
+ end
20
+
21
+ def warmup_stats(_warmup_time_us, timing)
22
+ case format
23
+ when :human
24
+ $stdout.printf "%s i/100ms\n", Helpers.scale(timing)
25
+ else
26
+ $stdout.printf "%10d i/100ms\n", timing
27
+ end
28
+ end
29
+
30
+ alias_method :running, :warming
31
+
32
+ def add_report(item, caller)
33
+ $stdout.puts " #{item.body}"
34
+ @last_item = item
35
+ end
36
+
37
+ def footer
38
+ return unless @last_item
39
+ footer = @last_item.stats.footer
40
+ $stdout.puts footer.rjust(40) if footer
41
+ end
42
+
43
+ private
44
+
45
+ # @return [Symbol] format used for benchmarking
46
+ def format
47
+ Benchmark::IPS.options[:format]
48
+ end
49
+
50
+ # Add padding to label's right if label's length < 20,
51
+ # Otherwise add a new line and 20 whitespaces.
52
+ # @return [String] Right justified label.
53
+ def rjust(label)
54
+ label = label.to_s
55
+ if label.size > 20
56
+ "#{label}\n#{' ' * 20}"
57
+ else
58
+ label.rjust(20)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end