comp_tree 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.rdoc +9 -1
- data/MANIFEST +2 -3
- data/README.rdoc +16 -12
- data/Rakefile +5 -4
- data/devel/{jumpstart.rb → levitate.rb} +236 -273
- data/lib/comp_tree.rb +52 -2
- data/lib/comp_tree/algorithm.rb +23 -26
- data/lib/comp_tree/driver.rb +19 -26
- data/lib/comp_tree/queue/queue_18.rb +1 -3
- data/lib/comp_tree/queue/queue_19.rb +1 -3
- data/lib/comp_tree/version.rb +3 -0
- data/test/basic_test.rb +15 -12
- data/test/circular_test.rb +2 -2
- data/test/comp_tree_test_base.rb +3 -2
- data/test/drain_test.rb +3 -5
- data/test/exception_test.rb +6 -9
- data/test/flood_test.rb +3 -3
- data/test/grind_test.rb +3 -5
- data/test/readme_test.rb +3 -3
- data/test/sequential_test.rb +3 -3
- data/test/throw_test.rb +2 -2
- metadata +13 -42
- data/install.rb +0 -2
- data/lib/comp_tree/comp_tree.rb +0 -55
data/lib/comp_tree.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
# Copyright (c) 2008
|
2
|
+
# Copyright (c) 2008-2011 James M. Lawrence. All rights reserved.
|
3
3
|
#
|
4
4
|
# Permission is hereby granted, free of charge, to any person
|
5
5
|
# obtaining a copy of this software and associated documentation files
|
@@ -27,4 +27,54 @@ require 'comp_tree/queue/queue'
|
|
27
27
|
require 'comp_tree/node'
|
28
28
|
require 'comp_tree/algorithm'
|
29
29
|
require 'comp_tree/driver'
|
30
|
-
|
30
|
+
|
31
|
+
#
|
32
|
+
# CompTree -- Parallel Computation Tree.
|
33
|
+
#
|
34
|
+
# See README.rdoc.
|
35
|
+
#
|
36
|
+
module CompTree
|
37
|
+
#
|
38
|
+
# :call-seq:
|
39
|
+
# build { |driver| ... }
|
40
|
+
#
|
41
|
+
# Build a new computation tree. A Driver instance is passed to the
|
42
|
+
# given block.
|
43
|
+
#
|
44
|
+
# Example:
|
45
|
+
# CompTree.build do |driver|
|
46
|
+
#
|
47
|
+
# # Define a function named 'area' taking these two arguments.
|
48
|
+
# driver.define(:area, :width, :height) { |width, height|
|
49
|
+
# width*height
|
50
|
+
# }
|
51
|
+
#
|
52
|
+
# # Define a function 'width' which takes a 'border' argument.
|
53
|
+
# driver.define(:width, :border) { |border|
|
54
|
+
# 7 + border
|
55
|
+
# }
|
56
|
+
#
|
57
|
+
# # Ditto for 'height'.
|
58
|
+
# driver.define(:height, :border) { |border|
|
59
|
+
# 5 + border
|
60
|
+
# }
|
61
|
+
#
|
62
|
+
# #
|
63
|
+
# # Define a constant function 'border'.
|
64
|
+
# driver.define(:border) {
|
65
|
+
# 2
|
66
|
+
# }
|
67
|
+
#
|
68
|
+
# # Compute the area using four parallel threads.
|
69
|
+
# puts driver.compute(:area, 4)
|
70
|
+
# # => 63
|
71
|
+
#
|
72
|
+
# # We've done this computation.
|
73
|
+
# puts((7 + 2)*(5 + 2))
|
74
|
+
# # => 63
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
def self.build(opts = {})
|
78
|
+
yield Driver.new(opts)
|
79
|
+
end
|
80
|
+
end
|
data/lib/comp_tree/algorithm.rb
CHANGED
@@ -3,40 +3,35 @@ module CompTree
|
|
3
3
|
module Algorithm
|
4
4
|
module_function
|
5
5
|
|
6
|
-
def compute_parallel(root,
|
6
|
+
def compute_parallel(root, max_threads)
|
7
|
+
workers = []
|
7
8
|
from_workers = Queue.new
|
8
9
|
to_workers = Queue.new
|
9
10
|
|
10
|
-
|
11
|
-
Thread.new {
|
12
|
-
worker_loop(from_workers, to_workers)
|
13
|
-
}
|
14
|
-
}
|
15
|
-
|
16
|
-
node = master_loop(root, num_threads, from_workers, to_workers)
|
11
|
+
node = master_loop(root, max_threads, workers, from_workers, to_workers)
|
17
12
|
|
18
|
-
|
13
|
+
workers.size.times { to_workers.push(nil) }
|
19
14
|
workers.each { |t| t.join }
|
20
15
|
|
21
|
-
if node.computed.is_a? Exception
|
22
|
-
|
23
|
-
else
|
24
|
-
node.result
|
25
|
-
end
|
16
|
+
raise node.computed if node.computed.is_a? Exception
|
17
|
+
node.result
|
26
18
|
end
|
27
19
|
|
28
|
-
def
|
29
|
-
|
30
|
-
node.
|
31
|
-
|
32
|
-
|
20
|
+
def new_worker(from_workers, to_workers)
|
21
|
+
Thread.new {
|
22
|
+
while node = to_workers.pop
|
23
|
+
node.compute
|
24
|
+
from_workers.push(node)
|
25
|
+
end
|
26
|
+
}
|
33
27
|
end
|
34
28
|
|
35
|
-
def master_loop(root,
|
29
|
+
def master_loop(root, arg1, workers, from_workers, to_workers)
|
30
|
+
max_threads = arg1 == 0 ? nil : arg1
|
36
31
|
num_working = 0
|
37
32
|
node = nil
|
38
33
|
while true
|
39
|
-
if num_working ==
|
34
|
+
if num_working == max_threads or !(node = find_node(root))
|
40
35
|
#
|
41
36
|
# maxed out or no nodes available -- wait for results
|
42
37
|
#
|
@@ -44,12 +39,16 @@ module CompTree
|
|
44
39
|
node.unlock
|
45
40
|
num_working -= 1
|
46
41
|
if node == root or node.computed.is_a? Exception
|
47
|
-
|
42
|
+
return node
|
48
43
|
end
|
49
44
|
else
|
50
45
|
#
|
51
|
-
# found a node
|
46
|
+
# not maxed out and found a node -- compute it
|
52
47
|
#
|
48
|
+
if (!max_threads.nil? and workers.size < max_threads) or
|
49
|
+
(max_threads.nil? and num_working == workers.size)
|
50
|
+
workers << new_worker(from_workers, to_workers)
|
51
|
+
end
|
53
52
|
num_working += 1
|
54
53
|
node.lock
|
55
54
|
to_workers.push(node)
|
@@ -74,9 +73,7 @@ module CompTree
|
|
74
73
|
# locked or children not computed; recurse to children
|
75
74
|
#
|
76
75
|
node.each_child { |child|
|
77
|
-
|
78
|
-
return found
|
79
|
-
end
|
76
|
+
found = find_node(child) and return found
|
80
77
|
}
|
81
78
|
nil
|
82
79
|
end
|
data/lib/comp_tree/driver.rb
CHANGED
@@ -38,30 +38,24 @@ module CompTree
|
|
38
38
|
#
|
39
39
|
def define(name, *child_names, &block)
|
40
40
|
#
|
41
|
-
# retrieve or create node
|
41
|
+
# retrieve or create node
|
42
42
|
#
|
43
|
-
|
44
|
-
|
45
|
-
@nodes[name] = @node_class.new(name)
|
46
|
-
}
|
47
|
-
if node.function
|
48
|
-
raise RedefinitionError.new(node.name)
|
49
|
-
end
|
43
|
+
node = @nodes[name] ||= @node_class.new(name)
|
44
|
+
raise RedefinitionError.new(node.name) if node.function
|
50
45
|
node.function = block
|
51
|
-
|
46
|
+
|
47
|
+
#
|
48
|
+
# retrieve or create children
|
49
|
+
#
|
52
50
|
children = child_names.map { |child_name|
|
53
|
-
@nodes.
|
54
|
-
@nodes[child_name] = @node_class.new(child_name)
|
55
|
-
}
|
51
|
+
@nodes[child_name] ||= @node_class.new(child_name)
|
56
52
|
}
|
57
53
|
|
58
54
|
#
|
59
55
|
# link
|
60
56
|
#
|
61
57
|
node.children = children
|
62
|
-
children.each { |child|
|
63
|
-
child.parents << node
|
64
|
-
}
|
58
|
+
children.each { |child| child.parents << node }
|
65
59
|
|
66
60
|
node
|
67
61
|
end
|
@@ -98,7 +92,8 @@ module CompTree
|
|
98
92
|
#
|
99
93
|
# _name_ -- unique node identifier (for example a symbol).
|
100
94
|
#
|
101
|
-
#
|
95
|
+
# _max_threads_ -- maximum number of threads, or +0+ to indicate
|
96
|
+
# no limit.
|
102
97
|
#
|
103
98
|
# Compute the tree below _name_ and return the result.
|
104
99
|
#
|
@@ -108,24 +103,22 @@ module CompTree
|
|
108
103
|
# It is your responsibility to call reset() before attempting the
|
109
104
|
# computation again, otherwise the result will be undefined.
|
110
105
|
#
|
111
|
-
def compute(name,
|
106
|
+
def compute(name, max_threads)
|
112
107
|
begin
|
113
|
-
|
108
|
+
max_threads = max_threads.to_int
|
114
109
|
rescue NoMethodError
|
115
|
-
raise TypeError, "can't convert #{
|
110
|
+
raise TypeError, "can't convert #{max_threads.class} into Integer"
|
116
111
|
end
|
117
|
-
|
118
|
-
raise RangeError, "number of threads must be
|
112
|
+
if max_threads < 0
|
113
|
+
raise RangeError, "number of threads must be nonnegative"
|
119
114
|
end
|
120
|
-
root = @nodes.
|
121
|
-
raise NoNodeError.new(name)
|
122
|
-
}
|
115
|
+
root = @nodes[name] or raise NoNodeError.new(name)
|
123
116
|
if root.computed
|
124
117
|
root.result
|
125
|
-
elsif
|
118
|
+
elsif max_threads == 1
|
126
119
|
root.compute_now
|
127
120
|
else
|
128
|
-
Algorithm.compute_parallel(root,
|
121
|
+
Algorithm.compute_parallel(root, max_threads)
|
129
122
|
end
|
130
123
|
end
|
131
124
|
end
|
data/test/basic_test.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/comp_tree_test_base'
|
1
|
+
require File.expand_path(File.dirname(__FILE__)) + '/comp_tree_test_base'
|
2
2
|
|
3
|
-
class
|
3
|
+
class BasicTest < CompTreeTest
|
4
4
|
def test_define
|
5
|
-
(
|
5
|
+
(0..20).each { |threads|
|
6
6
|
CompTree.build { |driver|
|
7
7
|
driver.define(:area, :width, :height, :offset) { |width, height, offset|
|
8
8
|
width*height - offset
|
@@ -33,7 +33,7 @@ class TestBasic < Test::Unit::TestCase
|
|
33
33
|
[nil, false, true, 33].each { |result|
|
34
34
|
CompTree.build { |driver|
|
35
35
|
driver.define(:a) { result }
|
36
|
-
(
|
36
|
+
(0..6).each { |n|
|
37
37
|
assert_equal(result, driver.compute(:a, n))
|
38
38
|
}
|
39
39
|
}
|
@@ -41,7 +41,7 @@ class TestBasic < Test::Unit::TestCase
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def test_threads_opt
|
44
|
-
(
|
44
|
+
(0..20).each { |threads|
|
45
45
|
CompTree.build do |driver|
|
46
46
|
driver.define(:a) { 33 }
|
47
47
|
assert_equal(33, driver.compute(:a, threads))
|
@@ -51,11 +51,11 @@ class TestBasic < Test::Unit::TestCase
|
|
51
51
|
|
52
52
|
def test_malformed
|
53
53
|
CompTree.build { |driver|
|
54
|
-
|
54
|
+
assert_raises(ArgumentError) {
|
55
55
|
driver.define {
|
56
56
|
}
|
57
57
|
}
|
58
|
-
error =
|
58
|
+
error = assert_raises(CompTree::RedefinitionError) {
|
59
59
|
driver.define(:a) {
|
60
60
|
}
|
61
61
|
driver.define(:a) {
|
@@ -89,8 +89,11 @@ class TestBasic < Test::Unit::TestCase
|
|
89
89
|
7
|
90
90
|
}
|
91
91
|
|
92
|
-
|
93
|
-
|
92
|
+
(0..20).each { |n|
|
93
|
+
assert_raises(test_error) {
|
94
|
+
driver.compute(:area, n)
|
95
|
+
}
|
96
|
+
driver.reset(:area)
|
94
97
|
}
|
95
98
|
}
|
96
99
|
end
|
@@ -111,7 +114,7 @@ class TestBasic < Test::Unit::TestCase
|
|
111
114
|
def test_non_symbols
|
112
115
|
width_id = Object.new
|
113
116
|
height_id = 272727
|
114
|
-
(
|
117
|
+
(0..6).each { |threads|
|
115
118
|
CompTree.build { |driver|
|
116
119
|
driver.define("area", width_id, height_id, :offset) {
|
117
120
|
|width, height, offset|
|
@@ -142,7 +145,7 @@ class TestBasic < Test::Unit::TestCase
|
|
142
145
|
def test_node_name_equality_comparison
|
143
146
|
CompTree.build { |driver|
|
144
147
|
driver.define("hello") { }
|
145
|
-
|
148
|
+
assert_raises(CompTree::RedefinitionError) {
|
146
149
|
driver.define("hello") { }
|
147
150
|
}
|
148
151
|
}
|
@@ -150,7 +153,7 @@ class TestBasic < Test::Unit::TestCase
|
|
150
153
|
|
151
154
|
def test_result_variety
|
152
155
|
[true, false, nil, Object.new, 33].each { |result|
|
153
|
-
(
|
156
|
+
(0..20).each { |threads|
|
154
157
|
CompTree.build { |driver|
|
155
158
|
driver.define(:area, :width, :height, :offset) {
|
156
159
|
|width, height, offset|
|
data/test/circular_test.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/comp_tree_test_base'
|
1
|
+
require File.expand_path(File.dirname(__FILE__)) + '/comp_tree_test_base'
|
2
2
|
|
3
|
-
class
|
3
|
+
class CircularTest < CompTreeTest
|
4
4
|
def test_circular
|
5
5
|
CompTree.build { |driver|
|
6
6
|
driver.define(:area, :width, :height, :offset) { |width, height, offset|
|
data/test/comp_tree_test_base.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
|
2
2
|
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../devel'
|
3
3
|
|
4
|
-
require '
|
4
|
+
require 'minitest/unit'
|
5
|
+
require 'minitest/autorun' unless defined? Rake
|
5
6
|
require 'comp_tree'
|
6
7
|
|
7
|
-
|
8
|
+
class CompTreeTest < MiniTest::Unit::TestCase
|
8
9
|
if ARGV.include?("--bench")
|
9
10
|
require 'benchmark'
|
10
11
|
|
data/test/drain_test.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/comp_tree_test_base'
|
2
|
-
|
3
|
-
class TestDrain < Test::Unit::TestCase
|
4
|
-
include TestBase
|
1
|
+
require File.expand_path(File.dirname(__FILE__)) + '/comp_tree_test_base'
|
5
2
|
|
3
|
+
class DrainTest < CompTreeTest
|
6
4
|
def drain
|
7
5
|
500000.times { }
|
8
6
|
end
|
@@ -24,7 +22,7 @@ class TestDrain < Test::Unit::TestCase
|
|
24
22
|
end
|
25
23
|
|
26
24
|
def each_drain
|
27
|
-
(
|
25
|
+
(0..10).each { |threads|
|
28
26
|
yield threads
|
29
27
|
}
|
30
28
|
end
|
data/test/exception_test.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/comp_tree_test_base'
|
1
|
+
require File.expand_path(File.dirname(__FILE__)) + '/comp_tree_test_base'
|
2
2
|
|
3
|
-
class
|
3
|
+
class ExceptionTest < CompTreeTest
|
4
4
|
def test_exception
|
5
5
|
test_error = Class.new StandardError
|
6
6
|
[true, false].each { |define_all|
|
7
7
|
[true, false].each { |abort_on_exception|
|
8
|
-
(
|
8
|
+
(0..20).each { |num_threads|
|
9
9
|
error = (
|
10
10
|
begin
|
11
11
|
CompTree.build { |driver|
|
@@ -63,7 +63,6 @@ class TestException < Test::Unit::TestCase
|
|
63
63
|
def test_num_threads
|
64
64
|
CompTree.build do |driver|
|
65
65
|
driver.define(:root) { }
|
66
|
-
assert_raises(RangeError) { driver.compute(:root, 0) }
|
67
66
|
assert_raises(RangeError) { driver.compute(:root, -1) }
|
68
67
|
assert_raises(RangeError) { driver.compute(:root, -11) }
|
69
68
|
|
@@ -76,12 +75,10 @@ class TestException < Test::Unit::TestCase
|
|
76
75
|
end
|
77
76
|
|
78
77
|
def test_invalid_node
|
79
|
-
(
|
78
|
+
(0..20).each { |num_threads|
|
80
79
|
CompTree.build do |driver|
|
81
80
|
driver.define(:root) { }
|
82
|
-
|
83
|
-
driver.compute(:root, num_threads)
|
84
|
-
}
|
81
|
+
driver.compute(:root, num_threads)
|
85
82
|
|
86
83
|
error = assert_raises(CompTree::NoNodeError) {
|
87
84
|
driver.compute(:a, num_threads)
|
@@ -100,7 +97,7 @@ class TestException < Test::Unit::TestCase
|
|
100
97
|
end
|
101
98
|
|
102
99
|
def test_missing_function
|
103
|
-
(
|
100
|
+
(0..20).each { |num_threads|
|
104
101
|
CompTree.build { |driver|
|
105
102
|
driver.define(:f, :x) { |x|
|
106
103
|
x + 33
|
data/test/flood_test.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/comp_tree_test_base'
|
1
|
+
require File.expand_path(File.dirname(__FILE__)) + '/comp_tree_test_base'
|
2
2
|
|
3
|
-
class
|
3
|
+
class FloodTest < CompTreeTest
|
4
4
|
def test_thread_flood
|
5
|
-
(
|
5
|
+
(0..200).each { |num_threads|
|
6
6
|
CompTree.build { |driver|
|
7
7
|
noop = lambda { |*args| true }
|
8
8
|
driver.define(:a, :b, &noop)
|