motion-benchmark-ips 1.0 → 1.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.
@@ -0,0 +1,189 @@
1
+ # encoding: utf-8
2
+
3
+ module Benchmark
4
+ module IPS
5
+
6
+ # Report contains benchmarking entries.
7
+ # Perform operations like add new entry, run comparison between entries.
8
+ class Report
9
+
10
+ # Represents benchmarking code data for Report.
11
+ class Entry
12
+ # Instantiate the Benchmark::IPS::Report::Entry.
13
+ # @param [#to_s] label Label of entry.
14
+ # @param [Integer] us Measured time in microsecond.
15
+ # @param [Integer] iters Iterations.
16
+ # @param [Object] stats Statistics.
17
+ # @param [Integer] cycles Number of Cycles.
18
+ def initialize(label, us, iters, stats, cycles)
19
+ @label = label
20
+ @microseconds = us
21
+ @iterations = iters
22
+ @stats = stats
23
+ @measurement_cycle = cycles
24
+ @show_total_time = false
25
+ end
26
+
27
+ # Label of entry.
28
+ # @return [String] the label of entry.
29
+ attr_reader :label
30
+
31
+ # Measured time in microsecond.
32
+ # @return [Integer] number of microseconds.
33
+ attr_reader :microseconds
34
+
35
+ # Number of Iterations.
36
+ # @return [Integer] number of iterations.
37
+ attr_reader :iterations
38
+
39
+ # Statistical summary of samples.
40
+ # @return [Object] statisical summary.
41
+ attr_reader :stats
42
+
43
+ # LEGACY: Iterations per second.
44
+ # @return [Float] number of iterations per second.
45
+ def ips
46
+ @stats.central_tendency
47
+ end
48
+
49
+ # LEGACY: Standard deviation of iteration per second.
50
+ # @return [Float] standard deviation of iteration per second.
51
+ def ips_sd
52
+ @stats.error
53
+ end
54
+
55
+ # Number of Cycles.
56
+ # @return [Integer] number of cycles.
57
+ attr_reader :measurement_cycle
58
+
59
+ # Control if the total time the job took is reported.
60
+ # Typically this value is not significant because it's very
61
+ # close to the expected time, so it's supressed by default.
62
+ def show_total_time!
63
+ @show_total_time = true
64
+ end
65
+
66
+ # Return entry's microseconds in seconds.
67
+ # @return [Float] +@microseconds+ in seconds.
68
+ def seconds
69
+ @microseconds.to_f / 1_000_000.0
70
+ end
71
+
72
+ # Return entry's standard deviation of iteration per second in percentage.
73
+ # @return [Float] +@ips_sd+ in percentage.
74
+ def error_percentage
75
+ 100.0 * (@stats.error.to_f / @stats.central_tendency)
76
+ end
77
+
78
+ alias_method :runtime, :seconds
79
+
80
+ # Return Entry body text with left padding.
81
+ # Body text contains information of iteration per second with
82
+ # percentage of standard deviation, iterations in runtime.
83
+ # @return [String] Left justified body.
84
+ def body
85
+ case Benchmark::IPS.options[:format]
86
+ when :human
87
+ left = "%s (±%4.1f%%) i/s" % [Helpers.scale(@stats.central_tendency), error_percentage]
88
+ iters = Helpers.scale(@iterations)
89
+
90
+ if @show_total_time
91
+ left.ljust(20) + (" - %s in %10.6fs" % [iters, runtime])
92
+ else
93
+ left.ljust(20) + (" - %s" % iters)
94
+ end
95
+ else
96
+ left = "%10.1f (±%.1f%%) i/s" % [@stats.central_tendency, error_percentage]
97
+
98
+ if @show_total_time
99
+ left.ljust(20) + (" - %10d in %10.6fs" % [@iterations, runtime])
100
+ else
101
+ left.ljust(20) + (" - %10d" % @iterations)
102
+ end
103
+ end
104
+ end
105
+
106
+ # Return header with padding if +@label+ is < length of 20.
107
+ # @return [String] Right justified header (+@label+).
108
+ def header
109
+ @label.to_s.rjust(20)
110
+ end
111
+
112
+ # Return string repesentation of Entry object.
113
+ # @return [String] Header and body.
114
+ def to_s
115
+ "#{header} #{body}"
116
+ end
117
+
118
+ # Print entry to current standard output ($stdout).
119
+ def display
120
+ $stdout.puts to_s
121
+ end
122
+ end # End of Entry
123
+
124
+ # class Report
125
+
126
+ # Entry to represent each benchmarked code in Report.
127
+ # @return [Array<Report::Entry>] Entries in Report.
128
+ attr_reader :entries
129
+
130
+ # Instantiate the Report.
131
+ def initialize
132
+ @entries = []
133
+ @data = nil
134
+ end
135
+
136
+ # Add entry to report.
137
+ # @param label [String] Entry label.
138
+ # @param microseconds [Integer] Measured time in microsecond.
139
+ # @param iters [Integer] Iterations.
140
+ # @param stats [Object] Statistical results.
141
+ # @param measurement_cycle [Integer] Number of cycles.
142
+ # @return [Report::Entry] Last added entry.
143
+ def add_entry label, microseconds, iters, stats, measurement_cycle
144
+ entry = Entry.new(label, microseconds, iters, stats, measurement_cycle)
145
+ @entries.delete_if { |e| e.label == label }
146
+ @entries << entry
147
+ entry
148
+ end
149
+
150
+ # Entries data in array for generate json.
151
+ # Each entry is a hash, consists of:
152
+ # name: Entry#label
153
+ # ips: Entry#ips
154
+ # stddev: Entry#ips_sd
155
+ # microseconds: Entry#microseconds
156
+ # iterations: Entry#iterations
157
+ # cycles: Entry#measurement_cycles
158
+ # @return [Array<Hash<Symbol,String|Float|Integer>] Array of hashes
159
+ def data
160
+ @data ||= @entries.collect do |entry|
161
+ {
162
+ :name => entry.label,
163
+ :central_tendency => entry.stats.central_tendency,
164
+ :ips => entry.stats.central_tendency, # for backwards compatibility
165
+ :error => entry.stats.error,
166
+ :stddev => entry.stats.error, # for backwards compatibility
167
+ :microseconds => entry.microseconds,
168
+ :iterations => entry.iterations,
169
+ :cycles => entry.measurement_cycle,
170
+ }
171
+ end
172
+ end
173
+
174
+ # Run comparison of entries.
175
+ def run_comparison
176
+ Benchmark.compare(*@entries)
177
+ end
178
+
179
+ # Generate json from Report#data to given path.
180
+ # @param path [String] path to generate json.
181
+ def generate_json(path)
182
+ File.open path, "w" do |f|
183
+ require "json"
184
+ f.write JSON.pretty_generate(data)
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,51 @@
1
+ module Benchmark
2
+ module IPS
3
+ module Stats
4
+
5
+ class Bootstrap
6
+
7
+ attr_reader :data
8
+
9
+ def initialize(samples, confidence)
10
+ dependencies
11
+ @iterations = 10_000
12
+ @confidence = (confidence / 100.0).to_s
13
+ @data = Kalibera::Data.new({[0] => samples}, [1, samples.size])
14
+ interval = @data.bootstrap_confidence_interval(@iterations, @confidence)
15
+ @median = interval.median
16
+ @error = interval.error
17
+ end
18
+
19
+ def central_tendency
20
+ @median
21
+ end
22
+
23
+ def error
24
+ @error
25
+ end
26
+
27
+ def slowdown(baseline)
28
+ low, slowdown, high = baseline.data.bootstrap_quotient(@data, @iterations, @confidence)
29
+ error = Timing.mean([slowdown - low, high - slowdown])
30
+ [slowdown, error]
31
+ end
32
+
33
+ def footer
34
+ "with #{(@confidence.to_f * 100).round(1)}% confidence"
35
+ end
36
+
37
+ def dependencies
38
+ require 'kalibera'
39
+ rescue LoadError
40
+ puts
41
+ puts "Can't load the kalibera gem - this is required to use the :bootstrap stats options."
42
+ puts "It's optional, so we don't formally depend on it and it isn't installed along with benchmark-ips."
43
+ puts "You probably want to do something like 'gem install kalibera' to fix this."
44
+ abort
45
+ end
46
+
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,33 @@
1
+ module Benchmark
2
+ module IPS
3
+ module Stats
4
+
5
+ class SD
6
+
7
+ def initialize(samples)
8
+ @mean = Timing.mean(samples)
9
+ @error = Timing.stddev(samples, @mean).round
10
+ end
11
+
12
+ def central_tendency
13
+ @mean
14
+ end
15
+
16
+ def error
17
+ @error
18
+ end
19
+
20
+ def slowdown(baseline)
21
+ slowdown = baseline.central_tendency.to_f / central_tendency
22
+ [slowdown, nil]
23
+ end
24
+
25
+ def footer
26
+ nil
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -1,10 +1,20 @@
1
1
  module Benchmark
2
+ # Perform caclulations on Timing results.
2
3
  module Timing
4
+ # Microseconds per second.
5
+ MICROSECONDS_PER_SECOND = 1_000_000
6
+
7
+ # Calculate (arithmetic) mean of given samples.
8
+ # @param [Array] samples Samples to calculate mean.
9
+ # @return [Float] Mean of given samples.
3
10
  def self.mean(samples)
4
- sum = samples.inject(0) { |acc, i| acc + i }
11
+ sum = samples.inject(:+)
5
12
  sum / samples.size
6
13
  end
7
14
 
15
+ # Calculate variance of given samples.
16
+ # @param [Float] m Optional mean (Expected value).
17
+ # @return [Float] Variance of given samples.
8
18
  def self.variance(samples, m=nil)
9
19
  m ||= mean(samples)
10
20
 
@@ -13,21 +23,15 @@ module Benchmark
13
23
  total / samples.size
14
24
  end
15
25
 
26
+ # Calculate standard deviation of given samples.
27
+ # @param [Array] samples Samples to calculate standard deviation.
28
+ # @param [Float] m Optional mean (Expected value).
29
+ # @return [Float] standard deviation of given samples.
16
30
  def self.stddev(samples, m=nil)
17
31
  Math.sqrt variance(samples, m)
18
32
  end
19
33
 
20
- def self.resample_mean(samples, resample_times=100)
21
- resamples = []
22
-
23
- resample_times.times do
24
- resample = samples.map { samples[rand(samples.size)] }
25
- resamples << Timing.mean(resample)
26
- end
27
-
28
- resamples
29
- end
30
-
34
+ # Recycle used objects by starting Garbage Collector.
31
35
  def self.clean_env
32
36
  # rbx
33
37
  if GC.respond_to? :run
@@ -36,5 +40,41 @@ module Benchmark
36
40
  GC.start
37
41
  end
38
42
  end
43
+
44
+ # Use a monotonic clock if available, otherwise use Time
45
+ begin
46
+ ## RubyMotion has not supported Process.clock_gettime
47
+ # Process.clock_gettime Process::CLOCK_MONOTONIC, :float_microsecond
48
+
49
+ # # Get an object that represents now and can be converted to microseconds
50
+ # def self.now
51
+ # Process.clock_gettime Process::CLOCK_MONOTONIC, :float_microsecond
52
+ # end
53
+
54
+ # # Add one second to the time represenetation
55
+ # def self.add_second(t, s)
56
+ # t + (s * MICROSECONDS_PER_SECOND)
57
+ # end
58
+
59
+ # # Return the number of microseconds between the 2 moments
60
+ # def self.time_us(before, after)
61
+ # after - before
62
+ # end
63
+ # rescue NameError
64
+ # Get an object that represents now and can be converted to microseconds
65
+ def self.now
66
+ Time.now
67
+ end
68
+
69
+ # Add one second to the time represenetation
70
+ def self.add_second(t, s)
71
+ t + s
72
+ end
73
+
74
+ # Return the number of microseconds between the 2 moments
75
+ def self.time_us(before, after)
76
+ (after.to_f - before.to_f) * MICROSECONDS_PER_SECOND
77
+ end
78
+ end
39
79
  end
40
80
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: motion-benchmark-ips
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.0'
4
+ version: '1.1'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Watson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-01-24 00:00:00.000000000 Z
11
+ date: 2017-05-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -35,8 +35,14 @@ files:
35
35
  - lib/motion-benchmark-ips.rb
36
36
  - lib/project/compare.rb
37
37
  - lib/project/ips.rb
38
+ - lib/project/ips/job.rb
39
+ - lib/project/ips/job/entry.rb
40
+ - lib/project/ips/job/stdout_report.rb
41
+ - lib/project/ips/report.rb
42
+ - lib/project/ips/stats/bootstrap.rb
43
+ - lib/project/ips/stats/sd.rb
38
44
  - lib/project/timing.rb
39
- homepage: ''
45
+ homepage: https://github.com/Watson1978/motion-benchmark-ips
40
46
  licenses:
41
47
  - MIT
42
48
  metadata: {}
@@ -56,7 +62,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
56
62
  version: '0'
57
63
  requirements: []
58
64
  rubyforge_project:
59
- rubygems_version: 2.2.0
65
+ rubygems_version: 2.6.11
60
66
  signing_key:
61
67
  specification_version: 4
62
68
  summary: Provides iteration per second benchmarking for RubyMotion