fruity 0.1.0 → 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.
- 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:
|