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.
- checksums.yaml +4 -4
- data/README.md +27 -0
- data/lib/project/compare.rb +65 -30
- data/lib/project/ips.rb +67 -236
- data/lib/project/ips/job.rb +351 -0
- data/lib/project/ips/job/entry.rb +77 -0
- data/lib/project/ips/job/stdout_report.rb +64 -0
- data/lib/project/ips/report.rb +189 -0
- data/lib/project/ips/stats/bootstrap.rb +51 -0
- data/lib/project/ips/stats/sd.rb +33 -0
- data/lib/project/timing.rb +52 -12
- metadata +10 -4
@@ -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
|
data/lib/project/timing.rb
CHANGED
@@ -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(
|
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
|
-
|
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.
|
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:
|
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.
|
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
|