motion-benchmark-ips 1.0 → 1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|