abprof 0.2.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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +291 -0
- data/Rakefile +10 -0
- data/abprof.gemspec +36 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/examples/alt_ruby.rb +11 -0
- data/examples/command_dsl.rb +13 -0
- data/examples/for_loop_10k.rb +11 -0
- data/examples/inline_ruby_1800.rb +11 -0
- data/examples/inline_ruby_2500.rb +11 -0
- data/examples/inlined_ruby.rb +11 -0
- data/examples/profiling_ruby.rb +11 -0
- data/examples/simple_dsl.rb +20 -0
- data/examples/sleep.rb +11 -0
- data/examples/sleep_longer.rb +9 -0
- data/examples/vanilla_ruby.rb +11 -0
- data/exe/abcompare +1 -0
- data/exe/abprof +120 -0
- data/lib/abprof.rb +280 -0
- data/lib/abprof/benchmark_dsl.rb +162 -0
- data/lib/abprof/version.rb +3 -0
- metadata +175 -0
@@ -0,0 +1,162 @@
|
|
1
|
+
require "abprof"
|
2
|
+
require "statsample"
|
3
|
+
|
4
|
+
module ABProf
|
5
|
+
class BenchmarkInstance
|
6
|
+
attr_reader :reports
|
7
|
+
|
8
|
+
ABProf::PROPERTIES.each do |prop|
|
9
|
+
define_method(prop) do |*args|
|
10
|
+
raise "Wrong number of arguments to #{prop.inspect}!" unless args.size < 2
|
11
|
+
instance_variable_set("@#{prop}", args[0]) if args.size == 1
|
12
|
+
instance_variable_get "@#{prop}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@pvalue = 0.05
|
18
|
+
@burnin = 10
|
19
|
+
@min_trials = 1
|
20
|
+
@max_trials = 20
|
21
|
+
@iters_per_trial = 10
|
22
|
+
@bare = false
|
23
|
+
|
24
|
+
@state = {
|
25
|
+
:samples => [[], []],
|
26
|
+
:p_tests => [],
|
27
|
+
:iter => 0,
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
# Aliases
|
32
|
+
alias_method :warmup, :burnin
|
33
|
+
alias_method :p_value, :pvalue
|
34
|
+
|
35
|
+
def report_command(cmd)
|
36
|
+
@reports ||= []
|
37
|
+
@reports.push cmd
|
38
|
+
end
|
39
|
+
|
40
|
+
def report(&block)
|
41
|
+
@reports ||= []
|
42
|
+
@reports.push block
|
43
|
+
end
|
44
|
+
|
45
|
+
def run_burnin(opts = {})
|
46
|
+
return unless @burnin > 0
|
47
|
+
|
48
|
+
@process1.run_iters @burnin
|
49
|
+
@process2.run_iters @burnin
|
50
|
+
end
|
51
|
+
|
52
|
+
def run_one_iteration(pts = {})
|
53
|
+
@state[:samples][0] += @process1.run_iters @iters_per_trial
|
54
|
+
@state[:samples][1] += @process2.run_iters @iters_per_trial
|
55
|
+
@state[:iter] += 1
|
56
|
+
end
|
57
|
+
|
58
|
+
def run_sampling(opts = {})
|
59
|
+
process_type = @bare ? ABProf::ABBareProcess : ABProf::ABHarnessProcess
|
60
|
+
command1 = @reports[0]
|
61
|
+
command2 = @reports[1]
|
62
|
+
|
63
|
+
@process1 = process_type.new command1, :debug => @debug
|
64
|
+
@process2 = process_type.new command2, :debug => @debug
|
65
|
+
|
66
|
+
puts "Beginning #{@burnin} iterations of burn-in for each process." if opts[:print_output]
|
67
|
+
run_burnin opts
|
68
|
+
|
69
|
+
puts "Beginning sampling from processes." if opts[:print_output]
|
70
|
+
|
71
|
+
# Sampling
|
72
|
+
p_val = 1.0
|
73
|
+
@max_trials.times do
|
74
|
+
run_one_iteration opts
|
75
|
+
|
76
|
+
# No t-test without 3+ samples
|
77
|
+
if @state[:samples][0].size > 2
|
78
|
+
# Evaluate the Welch's t-test
|
79
|
+
t = Statsample::Test.t_two_samples_independent(@state[:samples][0].to_vector, @state[:samples][1].to_vector)
|
80
|
+
p_val = t.probability_not_equal_variance
|
81
|
+
@state[:p_tests].push p_val
|
82
|
+
puts "Trial #{@state[:iter]}, Welch's T-test p value: #{p_val.inspect}" if opts[:print_output]
|
83
|
+
end
|
84
|
+
|
85
|
+
# Just finished trial number i+1. So we can exit only if i+1 was at least
|
86
|
+
# the minimum number of trials.
|
87
|
+
break if p_val < @pvalue && (@state[:iter] + 1 >= @min_trials)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Clean up processes
|
91
|
+
@process1.kill
|
92
|
+
@process2.kill
|
93
|
+
|
94
|
+
print_summary opts unless opts[:no_print_summary]
|
95
|
+
|
96
|
+
p_val = @state[:p_tests][-1]
|
97
|
+
if p_val >= @pvalue && @fail_on_divergence
|
98
|
+
STDERR.puts "Measured P value of #{p_val.inspect} is higher than allowable P value of #{@pvalue.inspect}!"
|
99
|
+
exit 2
|
100
|
+
end
|
101
|
+
|
102
|
+
@state
|
103
|
+
end
|
104
|
+
|
105
|
+
def print_summary(opts = {})
|
106
|
+
p_val = @state[:p_tests][-1]
|
107
|
+
diverged = false
|
108
|
+
if p_val < @pvalue
|
109
|
+
puts "Based on measured P value #{p_val}, we believe there is a speed difference."
|
110
|
+
puts "As of end of run, p value is #{p_val}. Now run more times to check, or with lower p."
|
111
|
+
|
112
|
+
summary11 = ABProf.summarize("mean", @state[:samples][0])
|
113
|
+
summary12 = ABProf.summarize("median", @state[:samples][0])
|
114
|
+
summary21 = ABProf.summarize("mean", @state[:samples][1])
|
115
|
+
summary22 = ABProf.summarize("median", @state[:samples][1])
|
116
|
+
|
117
|
+
fastest = "1"
|
118
|
+
command = @reports[0]
|
119
|
+
mean_times = summary21 / summary11
|
120
|
+
median_times = summary22 / summary12
|
121
|
+
if summary11 > summary21
|
122
|
+
fastest = "2"
|
123
|
+
command = @reports[1]
|
124
|
+
mean_times = summary11 / summary21
|
125
|
+
median_times = summary12 / summary22
|
126
|
+
end
|
127
|
+
|
128
|
+
puts "Lower (faster?) process is #{fastest}, command line: #{command.inspect}"
|
129
|
+
puts "Lower command is (very) roughly #{median_times} times lower (faster?) -- assuming linear sampling."
|
130
|
+
|
131
|
+
print "\n"
|
132
|
+
puts "Process 1 mean result: #{summary11}"
|
133
|
+
puts "Process 1 median result: #{summary12}"
|
134
|
+
puts "Process 2 mean result: #{summary21}"
|
135
|
+
puts "Process 2 median result: #{summary22}"
|
136
|
+
else
|
137
|
+
puts "Based on measured P value #{p_val} and threshold #{@pvalue}, we believe there is"
|
138
|
+
puts "no significant difference detectable with this set of trials."
|
139
|
+
puts "If you believe there is a small difference that wasn't detected, try raising the number"
|
140
|
+
puts "of iterations per trial, or the maximum number of trials."
|
141
|
+
diverged = true
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.compare(opts = {}, &block)
|
147
|
+
c = ABProf::BenchmarkInstance.new
|
148
|
+
c.instance_eval &block
|
149
|
+
|
150
|
+
raise "A DSL file must declare exactly two reports!" unless c.reports.size == 2
|
151
|
+
|
152
|
+
unless opts[:no_at_exit]
|
153
|
+
at_exit do
|
154
|
+
puts "Exit handler" if opts[:print_output]
|
155
|
+
c.run_sampling opts
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
c
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
metadata
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: abprof
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Noah Gibbs
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-08-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.12'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.12'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: trollop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.1'
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: 2.1.0
|
65
|
+
type: :runtime
|
66
|
+
prerelease: false
|
67
|
+
version_requirements: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - "~>"
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '2.1'
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: 2.1.0
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: statsample
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '2.0'
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: 2.0.0
|
85
|
+
type: :runtime
|
86
|
+
prerelease: false
|
87
|
+
version_requirements: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - "~>"
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '2.0'
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: 2.0.0
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: multi_json
|
97
|
+
requirement: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - "~>"
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '1.12'
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: 1.12.0
|
105
|
+
type: :runtime
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - "~>"
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '1.12'
|
112
|
+
- - ">="
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: 1.12.0
|
115
|
+
description: Determine which of two program variants is faster, using A/B-Testing-style
|
116
|
+
statistical techniques.
|
117
|
+
email:
|
118
|
+
- noah.gibbs@appfolio.com
|
119
|
+
executables:
|
120
|
+
- abcompare
|
121
|
+
- abprof
|
122
|
+
extensions: []
|
123
|
+
extra_rdoc_files: []
|
124
|
+
files:
|
125
|
+
- ".gitignore"
|
126
|
+
- ".travis.yml"
|
127
|
+
- CODE_OF_CONDUCT.md
|
128
|
+
- Gemfile
|
129
|
+
- LICENSE.txt
|
130
|
+
- README.md
|
131
|
+
- Rakefile
|
132
|
+
- abprof.gemspec
|
133
|
+
- bin/console
|
134
|
+
- bin/setup
|
135
|
+
- examples/alt_ruby.rb
|
136
|
+
- examples/command_dsl.rb
|
137
|
+
- examples/for_loop_10k.rb
|
138
|
+
- examples/inline_ruby_1800.rb
|
139
|
+
- examples/inline_ruby_2500.rb
|
140
|
+
- examples/inlined_ruby.rb
|
141
|
+
- examples/profiling_ruby.rb
|
142
|
+
- examples/simple_dsl.rb
|
143
|
+
- examples/sleep.rb
|
144
|
+
- examples/sleep_longer.rb
|
145
|
+
- examples/vanilla_ruby.rb
|
146
|
+
- exe/abcompare
|
147
|
+
- exe/abprof
|
148
|
+
- lib/abprof.rb
|
149
|
+
- lib/abprof/benchmark_dsl.rb
|
150
|
+
- lib/abprof/version.rb
|
151
|
+
homepage: https://github.com/appfolio/abprof
|
152
|
+
licenses:
|
153
|
+
- MIT
|
154
|
+
metadata: {}
|
155
|
+
post_install_message:
|
156
|
+
rdoc_options: []
|
157
|
+
require_paths:
|
158
|
+
- lib
|
159
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
160
|
+
requirements:
|
161
|
+
- - ">="
|
162
|
+
- !ruby/object:Gem::Version
|
163
|
+
version: '0'
|
164
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
165
|
+
requirements:
|
166
|
+
- - ">="
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '0'
|
169
|
+
requirements: []
|
170
|
+
rubyforge_project:
|
171
|
+
rubygems_version: 2.5.1
|
172
|
+
signing_key:
|
173
|
+
specification_version: 4
|
174
|
+
summary: Determine which of two programs is faster, statistically.
|
175
|
+
test_files: []
|