fruity 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +2 -2
- data/VERSION +1 -1
- data/fruity.gemspec +71 -0
- data/lib/fruity/base.rb +3 -3
- data/lib/fruity/group.rb +8 -8
- data/lib/fruity/runner.rb +3 -3
- data/lib/fruity/util.rb +12 -12
- data/spec/find_thresold.rb +1 -1
- data/spec/runner_spec.rb +3 -3
- data/spec/util_spec.rb +5 -5
- metadata +9 -8
data/README.rdoc
CHANGED
@@ -98,8 +98,8 @@ In addition, all benchmarking tools require the user to either write their own i
|
|
98
98
|
|
99
99
|
== Algorithm
|
100
100
|
|
101
|
-
We first determine the number of inner iterations needed to get a meaningful clock measurement (see
|
102
|
-
When timing an execution, we always compare to a baseline (the time taken by an empty loop of the same
|
101
|
+
We first determine the number of inner iterations needed to get a meaningful clock measurement (see sufficient_magnification).
|
102
|
+
When timing an execution, we always compare to a baseline (the time taken by an empty loop of the same magnification).
|
103
103
|
We call the different executables in succession, to minimize the impact on the order of execution.
|
104
104
|
We calculate the error by taking into account the standard deviation of the time samples.
|
105
105
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/fruity.gemspec
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "fruity"
|
8
|
+
s.version = "0.2.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Marc-Andre Lafortune"]
|
12
|
+
s.date = "2012-02-08"
|
13
|
+
s.description = "Comparing apples with apples"
|
14
|
+
s.email = "github@marc-andre.ca"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".irbrc",
|
21
|
+
"Gemfile",
|
22
|
+
"Gemfile.lock",
|
23
|
+
"LICENSE.txt",
|
24
|
+
"README.rdoc",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"fruity.gemspec",
|
28
|
+
"lib/fruity.rb",
|
29
|
+
"lib/fruity/base.rb",
|
30
|
+
"lib/fruity/baseline.rb",
|
31
|
+
"lib/fruity/comparison_run.rb",
|
32
|
+
"lib/fruity/group.rb",
|
33
|
+
"lib/fruity/named_block_collector.rb",
|
34
|
+
"lib/fruity/runner.rb",
|
35
|
+
"lib/fruity/util.rb",
|
36
|
+
"spec/baseline_spec.rb",
|
37
|
+
"spec/comparison_run_spec.rb",
|
38
|
+
"spec/find_thresold.rb",
|
39
|
+
"spec/fruity_spec.rb",
|
40
|
+
"spec/group_spec.rb",
|
41
|
+
"spec/manual_test.rb",
|
42
|
+
"spec/runner_spec.rb",
|
43
|
+
"spec/spec.opts",
|
44
|
+
"spec/spec_helper.rb",
|
45
|
+
"spec/util_spec.rb"
|
46
|
+
]
|
47
|
+
s.homepage = "http://github.com/marcandre/fruity"
|
48
|
+
s.licenses = ["MIT"]
|
49
|
+
s.require_paths = ["lib"]
|
50
|
+
s.rubygems_version = "1.8.10"
|
51
|
+
s.summary = "Cool performance comparison tool"
|
52
|
+
|
53
|
+
if s.respond_to? :specification_version then
|
54
|
+
s.specification_version = 3
|
55
|
+
|
56
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
57
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
|
58
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
59
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
|
60
|
+
else
|
61
|
+
s.add_dependency(%q<rspec>, ["~> 2.3.0"])
|
62
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
63
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
|
64
|
+
end
|
65
|
+
else
|
66
|
+
s.add_dependency(%q<rspec>, ["~> 2.3.0"])
|
67
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
68
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
data/lib/fruity/base.rb
CHANGED
@@ -12,12 +12,12 @@ module Fruity
|
|
12
12
|
:on => GLOBAL_SCOPE,
|
13
13
|
:samples => 20,
|
14
14
|
:disable_gc => false,
|
15
|
-
:filter => [0, 0.2],
|
16
|
-
:baseline => :split
|
15
|
+
:filter => [0, 0.2], # Proportion of samples to discard [lower_end, upper_end]
|
16
|
+
:baseline => :single, # Either :none, :single or :split
|
17
17
|
}
|
18
18
|
|
19
19
|
OTHER_OPTIONS = [
|
20
|
-
:
|
20
|
+
:magnify,
|
21
21
|
:args,
|
22
22
|
:self,
|
23
23
|
:verbose,
|
data/lib/fruity/group.rb
CHANGED
@@ -37,19 +37,19 @@ module Fruity
|
|
37
37
|
compare_block(block) if block
|
38
38
|
end
|
39
39
|
|
40
|
-
# Returns the maximal
|
41
|
-
# See Util.
|
40
|
+
# Returns the maximal sufficient_magnification for all elements
|
41
|
+
# See Util.sufficient_magnification
|
42
42
|
#
|
43
|
-
def
|
44
|
-
elements.map{|name, exec| Util.
|
43
|
+
def sufficient_magnification
|
44
|
+
elements.map{|name, exec| Util.sufficient_magnification(exec, options) }.max
|
45
45
|
end
|
46
46
|
|
47
|
-
# Returns the maximal
|
47
|
+
# Returns the maximal sufficient_magnification for all elements
|
48
48
|
# and the approximate delay taken for the whole group
|
49
|
-
# See Util.
|
49
|
+
# See Util.sufficient_magnification
|
50
50
|
#
|
51
|
-
def
|
52
|
-
mags_and_delays = elements.map{|name, exec| Util.
|
51
|
+
def sufficient_magnification_and_delay
|
52
|
+
mags_and_delays = elements.map{|name, exec| Util.sufficient_magnification_and_delay(exec, options) }
|
53
53
|
mag = mags_and_delays.map(&:first).max
|
54
54
|
delay = mags_and_delays.map{|m, d| d * mag / m}.inject(:+)
|
55
55
|
[mag, delay]
|
data/lib/fruity/runner.rb
CHANGED
@@ -9,7 +9,7 @@ module Fruity
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def feedback
|
12
|
-
mess = "Running each test " << (options[:
|
12
|
+
mess = "Running each test " << (options[:magnify] == 1 ? "once." : "#{options[:magnify]} times.")
|
13
13
|
if d = delay
|
14
14
|
if d > 60
|
15
15
|
d = (d / 60).round
|
@@ -23,8 +23,8 @@ module Fruity
|
|
23
23
|
private
|
24
24
|
def prepare(opt)
|
25
25
|
@options = group.options.merge(opt)
|
26
|
-
unless options[:
|
27
|
-
options[:
|
26
|
+
unless options[:magnify]
|
27
|
+
options[:magnify], @delay = group.sufficient_magnification_and_delay
|
28
28
|
@delay *= options.fetch(:samples)
|
29
29
|
end
|
30
30
|
end
|
data/lib/fruity/util.rb
CHANGED
@@ -38,30 +38,30 @@ module Fruity
|
|
38
38
|
# due in big part to the imprecision of the measurement itself
|
39
39
|
# or the inner loop itself.
|
40
40
|
#
|
41
|
-
def
|
42
|
-
mag, delay =
|
41
|
+
def sufficient_magnification(exec, options = {})
|
42
|
+
mag, delay = sufficient_magnification_and_delay(exec, options)
|
43
43
|
mag
|
44
44
|
end
|
45
45
|
|
46
46
|
BASELINE_THRESHOLD = 1.02 # Ratio between two identical baselines is typically < 1.02, while {2+2} compared to baseline is typically > 1.02
|
47
47
|
|
48
|
-
def
|
48
|
+
def sufficient_magnification_and_delay(exec, options = {})
|
49
49
|
power = 0
|
50
50
|
min_desired_delta = clock_precision * MEASUREMENTS_BY_PROPER_TIME / PROPER_TIME_RELATIVE_ERROR
|
51
51
|
# First, make a gross approximation with a single sample and no baseline
|
52
52
|
min_approx_delay = min_desired_delta / (1 << APPROX_POWER)
|
53
|
-
while (delay = real_time(exec, options.merge(:
|
53
|
+
while (delay = real_time(exec, options.merge(:magnify => 1 << power))) < min_approx_delay
|
54
54
|
power += [Math.log(min_approx_delay.div(delay + clock_precision), 2), 1].max.floor
|
55
55
|
end
|
56
56
|
|
57
57
|
# Then take a couple of samples, along with a baseline
|
58
58
|
power += 1 unless delay > 2 * min_approx_delay
|
59
|
-
group = Group.new(exec, Baseline[exec], options.merge(:baseline => :none, :samples => 5, :filter => [0, 0.25], :
|
59
|
+
group = Group.new(exec, Baseline[exec], options.merge(:baseline => :none, :samples => 5, :filter => [0, 0.25], :magnify => 1 << power))
|
60
60
|
stats = group.run.stats
|
61
61
|
if stats[0][:mean] / stats[1][:mean] < 2
|
62
62
|
# Quite close to baseline, which means we need to be more discriminant
|
63
63
|
power += APPROX_POWER
|
64
|
-
stats = group.run(:samples => 40, :
|
64
|
+
stats = group.run(:samples => 40, :magnify => 1 << power).stats
|
65
65
|
raise "Given callable can not be reasonably distinguished from an empty block" if stats[0][:mean] / stats[1][:mean] < BASELINE_THRESHOLD
|
66
66
|
end
|
67
67
|
delta = stats[0][:mean] - stats[1][:mean]
|
@@ -73,25 +73,25 @@ module Fruity
|
|
73
73
|
end
|
74
74
|
|
75
75
|
# The proper time is the real time taken by calling +exec+
|
76
|
-
# number of times given by +options[:
|
76
|
+
# number of times given by +options[:magnify]+ minus
|
77
77
|
# the real time for calling an empty executable instead.
|
78
78
|
#
|
79
|
-
# If +options[:
|
79
|
+
# If +options[:magnify]+ is not given, it will be calculated to be meaningful.
|
80
80
|
#
|
81
81
|
def proper_time(exec, options = {})
|
82
|
-
unless options.has_key?(:
|
83
|
-
options = {:
|
82
|
+
unless options.has_key?(:magnify)
|
83
|
+
options = {:magnify => sufficient_magnification(exec, options)}.merge(options)
|
84
84
|
end
|
85
85
|
real_time(exec, options) - real_time(Baseline[exec], options)
|
86
86
|
end
|
87
87
|
|
88
88
|
# Returns the real time taken by calling +exec+
|
89
|
-
# number of times given by +options[:
|
89
|
+
# number of times given by +options[:magnify]+
|
90
90
|
#
|
91
91
|
def real_time(exec, options = {})
|
92
92
|
GC.start
|
93
93
|
GC.disable if options[:disable_gc]
|
94
|
-
n = options.fetch(:
|
94
|
+
n = options.fetch(:magnify)
|
95
95
|
if options.has_key?(:self)
|
96
96
|
new_self = options[:self]
|
97
97
|
if args = options[:args] and args.size > 0
|
data/spec/find_thresold.rb
CHANGED
@@ -7,7 +7,7 @@ min = 1
|
|
7
7
|
max = 2
|
8
8
|
s = 10
|
9
9
|
n.times.map do
|
10
|
-
group = Fruity::Group.new(*[->{}] * s, *[->{ 2 + 2 }] * s, :baseline => :none, :samples => 20, :
|
10
|
+
group = Fruity::Group.new(*[->{}] * s, *[->{ 2 + 2 }] * s, :baseline => :none, :samples => 20, :magnify => 1 << power)
|
11
11
|
means = group.run.stats.map{|s| s[:mean]}
|
12
12
|
noops = means.first(s).sort
|
13
13
|
plus = means.last(s).sort
|
data/spec/runner_spec.rb
CHANGED
@@ -6,19 +6,19 @@ module Fruity
|
|
6
6
|
let(:runner){ Runner.new(group) }
|
7
7
|
|
8
8
|
it "runs from a Group" do
|
9
|
-
run = runner.run(:samples => 42, :
|
9
|
+
run = runner.run(:samples => 42, :magnify => 100)
|
10
10
|
run.timings.should be_array_of_size(2, 42)
|
11
11
|
run.baselines.should be_array_of_size(2, 42)
|
12
12
|
end
|
13
13
|
|
14
14
|
it "can use a single baseline" do
|
15
|
-
run = runner.run(:samples => 42, :
|
15
|
+
run = runner.run(:samples => 42, :magnify => 100, :baseline => :single)
|
16
16
|
run.timings.should be_array_of_size(2, 42)
|
17
17
|
run.baselines.should be_array_of_size(42)
|
18
18
|
end
|
19
19
|
|
20
20
|
it "can use no baseline" do
|
21
|
-
run = runner.run(:samples => 42, :
|
21
|
+
run = runner.run(:samples => 42, :magnify => 100, :baseline => :none)
|
22
22
|
run.timings.should be_array_of_size(2, 42)
|
23
23
|
run.baselines.should == nil
|
24
24
|
end
|
data/spec/util_spec.rb
CHANGED
@@ -6,18 +6,18 @@ module Fruity
|
|
6
6
|
|
7
7
|
its(:proper_time_precision) { should == 4.0e-06 }
|
8
8
|
|
9
|
-
describe :
|
9
|
+
describe :sufficient_magnification do
|
10
10
|
it "returns a big value for a quick (but not trivial)" do
|
11
|
-
Util.
|
11
|
+
Util.sufficient_magnification(->{ 2 + 2 }).should > 10000
|
12
12
|
end
|
13
13
|
|
14
14
|
it "return 1 for a sufficiently slow block" do
|
15
|
-
Util.
|
15
|
+
Util.sufficient_magnification(->{sleep(0.01)}).should == 1
|
16
16
|
end
|
17
17
|
|
18
18
|
it "should raise an error for a trivial block" do
|
19
19
|
->{
|
20
|
-
Util.
|
20
|
+
Util.sufficient_magnification(->{})
|
21
21
|
}.should raise_error
|
22
22
|
end
|
23
23
|
end
|
@@ -46,7 +46,7 @@ module Fruity
|
|
46
46
|
|
47
47
|
it "gives similar results when comparing an exec and its baseline from stats on proper_time" do
|
48
48
|
exec = ->{ 2 ** 3 ** 4 }
|
49
|
-
options = {:
|
49
|
+
options = {:magnify => Util.sufficient_magnification(exec) }
|
50
50
|
n = 100
|
51
51
|
timings = [exec, ->{}].map do |e|
|
52
52
|
n.times.map { Util.real_time(e, options) }
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fruity
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -13,7 +13,7 @@ date: 2012-02-08 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
16
|
-
requirement: &
|
16
|
+
requirement: &2158896760 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 2.3.0
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *2158896760
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: bundler
|
27
|
-
requirement: &
|
27
|
+
requirement: &2158895780 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: 1.0.0
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *2158895780
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: jeweler
|
38
|
-
requirement: &
|
38
|
+
requirement: &2158895160 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ~>
|
@@ -43,7 +43,7 @@ dependencies:
|
|
43
43
|
version: 1.6.4
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *2158895160
|
47
47
|
description: Comparing apples with apples
|
48
48
|
email: github@marc-andre.ca
|
49
49
|
executables: []
|
@@ -59,6 +59,7 @@ files:
|
|
59
59
|
- README.rdoc
|
60
60
|
- Rakefile
|
61
61
|
- VERSION
|
62
|
+
- fruity.gemspec
|
62
63
|
- lib/fruity.rb
|
63
64
|
- lib/fruity/base.rb
|
64
65
|
- lib/fruity/baseline.rb
|
@@ -92,7 +93,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
92
93
|
version: '0'
|
93
94
|
segments:
|
94
95
|
- 0
|
95
|
-
hash:
|
96
|
+
hash: 269285252031009011
|
96
97
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
98
|
none: false
|
98
99
|
requirements:
|