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.
@@ -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: []