benchmark-ips 2.3.0 → 2.11.0

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