abprof 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,3 @@
1
+ module Abprof
2
+ VERSION = "0.2.0"
3
+ 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: []