comp_tree 1.0.1 → 1.1.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/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)
|