comp_tree 0.7.1 → 0.7.2
Sign up to get free protection for your applications and to get access to all the features.
- data/{CHANGES → CHANGES.rdoc} +5 -0
- data/README.rdoc +19 -12
- data/Rakefile +6 -1
- data/devel/jumpstart.rb +580 -0
- data/devel/jumpstart/lazy_attribute.rb +38 -0
- data/{rakelib → devel}/jumpstart/ruby.rb +1 -1
- data/{rakelib → devel}/jumpstart/simple_installer.rb +1 -1
- data/lib/comp_tree.rb +2 -0
- data/lib/comp_tree/algorithm.rb +44 -125
- data/lib/comp_tree/driver.rb +4 -1
- data/lib/comp_tree/node.rb +13 -44
- data/test/common.rb +1 -1
- data/test/test_drain.rb +4 -3
- data/test/test_exception.rb +57 -37
- data/test/test_grind.rb +13 -12
- metadata +22 -21
- data/comp_tree.gemspec +0 -42
- data/rakelib/jumpstart.rake +0 -365
@@ -0,0 +1,38 @@
|
|
1
|
+
|
2
|
+
class Jumpstart
|
3
|
+
#
|
4
|
+
# Mixin for lazily-evaluated attributes.
|
5
|
+
#
|
6
|
+
module LazyAttribute
|
7
|
+
#
|
8
|
+
# &block is evaluated when this attribute is requested. The same
|
9
|
+
# result is returned for subsequent calls until the attribute is
|
10
|
+
# assigned a different value.
|
11
|
+
#
|
12
|
+
def attribute(reader, &block)
|
13
|
+
writer = "#{reader}="
|
14
|
+
|
15
|
+
singleton = (class << self ; self ; end)
|
16
|
+
|
17
|
+
define_evaluated_reader = lambda { |value|
|
18
|
+
singleton.class_eval {
|
19
|
+
remove_method(reader)
|
20
|
+
define_method(reader) { value }
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
singleton.class_eval {
|
25
|
+
define_method(reader) {
|
26
|
+
value = block.call
|
27
|
+
define_evaluated_reader.call(value)
|
28
|
+
value
|
29
|
+
}
|
30
|
+
|
31
|
+
define_method(writer) { |value|
|
32
|
+
define_evaluated_reader.call(value)
|
33
|
+
value
|
34
|
+
}
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/comp_tree.rb
CHANGED
data/lib/comp_tree/algorithm.rb
CHANGED
@@ -1,162 +1,81 @@
|
|
1
1
|
|
2
2
|
module CompTree
|
3
3
|
module Algorithm
|
4
|
-
LEAVE = :__comp_tree_leave
|
5
|
-
AGAIN = :__comp_tree_again
|
6
|
-
|
7
4
|
module_function
|
8
5
|
|
9
|
-
def loop_with(leave, again)
|
10
|
-
catch(leave) {
|
11
|
-
while true
|
12
|
-
catch(again) {
|
13
|
-
yield
|
14
|
-
}
|
15
|
-
end
|
16
|
-
}
|
17
|
-
end
|
18
|
-
|
19
6
|
def compute_parallel(root, num_threads)
|
20
|
-
|
21
|
-
|
22
|
-
tree_mutex = Mutex.new
|
23
|
-
condition = ConditionVariable.new
|
24
|
-
num_threads_ready = 0
|
7
|
+
to_workers = Queue.new
|
8
|
+
from_workers = Queue.new
|
25
9
|
|
26
|
-
|
27
|
-
|
28
|
-
#
|
29
|
-
# wait for main thread
|
30
|
-
#
|
31
|
-
tree_mutex.synchronize {
|
32
|
-
#trace "Thread #{thread_index} waiting to start"
|
33
|
-
num_threads_ready += 1
|
34
|
-
condition.wait(tree_mutex)
|
35
|
-
}
|
10
|
+
node_to_worker = nil
|
11
|
+
node_from_worker = nil
|
36
12
|
|
37
|
-
|
38
|
-
|
39
|
-
#trace "Thread #{thread_index} acquired tree lock; begin search"
|
40
|
-
if finished
|
41
|
-
#trace "Thread #{thread_index} detected finish"
|
42
|
-
num_threads_ready -= 1
|
43
|
-
throw LEAVE
|
44
|
-
else
|
45
|
-
#
|
46
|
-
# Find a node. The node we obtain, if any, will be locked.
|
47
|
-
#
|
48
|
-
node = find_node(root)
|
49
|
-
if node
|
50
|
-
#trace "Thread #{thread_index} found node #{node.name}"
|
51
|
-
node
|
52
|
-
else
|
53
|
-
#trace "Thread #{thread_index}: no node found; sleeping."
|
54
|
-
condition.wait(tree_mutex)
|
55
|
-
throw AGAIN
|
56
|
-
end
|
57
|
-
end
|
58
|
-
}
|
13
|
+
num_working = 0
|
14
|
+
finished = nil
|
59
15
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
#}
|
16
|
+
workers = (1..num_threads).map {
|
17
|
+
Thread.new {
|
18
|
+
until (node = to_workers.pop) == :finished
|
64
19
|
node.compute
|
65
|
-
|
66
|
-
|
67
|
-
tree_mutex.synchronize {
|
68
|
-
#trace "Thread #{thread_index} acquired tree lock"
|
69
|
-
#debug {
|
70
|
-
# name = "#{node.name}" + ((node == root) ? " (ROOT NODE)" : "")
|
71
|
-
# initial = "Thread #{thread_index} compute result for #{name}: "
|
72
|
-
# status = node.computed.is_a?(Exception) ? "error" : "success"
|
73
|
-
# trace initial + status
|
74
|
-
# trace "Thread #{thread_index} node result: #{node.result}"
|
75
|
-
#}
|
76
|
-
|
77
|
-
if node.computed.is_a? Exception
|
78
|
-
#
|
79
|
-
# An error occurred; we are done.
|
80
|
-
#
|
81
|
-
finished = node.computed
|
82
|
-
elsif node == root
|
83
|
-
#
|
84
|
-
# Root node was computed; we are done.
|
85
|
-
#
|
86
|
-
finished = true
|
87
|
-
end
|
88
|
-
|
89
|
-
#
|
90
|
-
# remove locks for this node (shared lock and own lock)
|
91
|
-
#
|
92
|
-
node.unlock
|
93
|
-
|
94
|
-
#
|
95
|
-
# Tell the main thread that another node was computed.
|
96
|
-
#
|
97
|
-
condition.signal
|
98
|
-
}
|
99
|
-
}
|
100
|
-
#trace "Thread #{thread_index} exiting"
|
20
|
+
from_workers.push node
|
21
|
+
end
|
101
22
|
}
|
102
23
|
}
|
103
24
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
if finished
|
116
|
-
#trace "Main: detected finish."
|
25
|
+
while true
|
26
|
+
if num_working == num_threads or not (node_to_worker = find_node(root))
|
27
|
+
#
|
28
|
+
# max computations running or no nodes available -- wait for results
|
29
|
+
#
|
30
|
+
node_from_worker = from_workers.pop
|
31
|
+
node_from_worker.unlock
|
32
|
+
num_working -= 1
|
33
|
+
if node_from_worker == root or
|
34
|
+
node_from_worker.computed.is_a? Exception
|
35
|
+
finished = node_from_worker
|
117
36
|
break
|
118
37
|
end
|
119
|
-
|
120
|
-
#
|
121
|
-
|
122
|
-
#
|
38
|
+
elsif node_to_worker
|
39
|
+
#
|
40
|
+
# found a node
|
41
|
+
#
|
42
|
+
to_workers.push node_to_worker
|
43
|
+
num_working += 1
|
44
|
+
node_to_worker = nil
|
123
45
|
end
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
raise finished
|
46
|
+
end
|
47
|
+
|
48
|
+
num_threads.times { to_workers.push :finished }
|
49
|
+
workers.each { |t| t.join }
|
50
|
+
|
51
|
+
if finished.computed.is_a? Exception
|
52
|
+
raise finished.computed
|
132
53
|
else
|
133
|
-
|
54
|
+
finished.result
|
134
55
|
end
|
135
56
|
end
|
136
57
|
|
137
58
|
def find_node(node)
|
138
|
-
# --- only called inside shared tree mutex
|
139
|
-
#trace "Looking for a node, starting with #{node.name}"
|
140
59
|
if node.computed
|
141
60
|
#
|
142
61
|
# already computed
|
143
62
|
#
|
144
|
-
#trace "#{node.name} has been computed"
|
145
63
|
nil
|
146
|
-
elsif
|
64
|
+
elsif not node.locked? and node.children_results
|
147
65
|
#
|
148
|
-
# Node is not computed and its children are
|
149
|
-
#
|
66
|
+
# Node is not computed, not locked, and its children are
|
67
|
+
# computed; Ready to compute.
|
150
68
|
#
|
151
|
-
node.
|
69
|
+
node.lock
|
152
70
|
node
|
153
71
|
else
|
154
72
|
#
|
155
73
|
# locked or children not computed; recurse to children
|
156
74
|
#
|
157
|
-
#trace "Checking #{node.name}'s children"
|
158
75
|
node.each_child { |child|
|
159
|
-
|
76
|
+
found = find_node(child) and (
|
77
|
+
return found
|
78
|
+
)
|
160
79
|
}
|
161
80
|
nil
|
162
81
|
end
|
data/lib/comp_tree/driver.rb
CHANGED
@@ -118,7 +118,10 @@ module CompTree
|
|
118
118
|
# Any uncomputed children are computed first.
|
119
119
|
#
|
120
120
|
def compute(name, opts)
|
121
|
-
threads = opts.is_a?(Hash) ? opts[:threads] : opts
|
121
|
+
threads = (opts.is_a?(Hash) ? opts[:threads] : opts).to_i
|
122
|
+
unless threads > 0
|
123
|
+
raise ArgumentError, "number of threads must be greater than zero"
|
124
|
+
end
|
122
125
|
root = @nodes[name]
|
123
126
|
if root.computed
|
124
127
|
root.result
|
data/lib/comp_tree/node.rb
CHANGED
@@ -13,9 +13,7 @@ module CompTree
|
|
13
13
|
attr_accessor :function #:nodoc:
|
14
14
|
attr_accessor :result #:nodoc:
|
15
15
|
attr_accessor :computed #:nodoc:
|
16
|
-
attr_accessor :
|
17
|
-
|
18
|
-
attr_writer :children_results #:nodoc:
|
16
|
+
attr_accessor :lock_level #:nodoc:
|
19
17
|
|
20
18
|
#
|
21
19
|
# Create a node
|
@@ -25,7 +23,6 @@ module CompTree
|
|
25
23
|
@parents = []
|
26
24
|
@children = []
|
27
25
|
@function = nil
|
28
|
-
@mutex = Mutex.new
|
29
26
|
reset_self
|
30
27
|
end
|
31
28
|
|
@@ -35,7 +32,7 @@ module CompTree
|
|
35
32
|
def reset_self #:nodoc:
|
36
33
|
@result = nil
|
37
34
|
@computed = nil
|
38
|
-
@
|
35
|
+
@lock_level = 0
|
39
36
|
@children_results = nil
|
40
37
|
end
|
41
38
|
|
@@ -85,12 +82,9 @@ module CompTree
|
|
85
82
|
# If all children have been computed, return their results;
|
86
83
|
# otherwise return nil.
|
87
84
|
#
|
88
|
-
|
89
|
-
# necessarily aquired.
|
90
|
-
#
|
91
|
-
def find_children_results #:nodoc:
|
85
|
+
def children_results #:nodoc:
|
92
86
|
@children_results or (
|
93
|
-
@children.map { |child|
|
87
|
+
@children_results = @children.map { |child|
|
94
88
|
unless child.computed
|
95
89
|
return nil
|
96
90
|
end
|
@@ -99,16 +93,6 @@ module CompTree
|
|
99
93
|
)
|
100
94
|
end
|
101
95
|
|
102
|
-
#def trace_compute #:nodoc:
|
103
|
-
# debug {
|
104
|
-
# # --- own mutex
|
105
|
-
# trace "Computing #{@name}"
|
106
|
-
# raise AssertionFailedError if @computed
|
107
|
-
# raise AssertionFailedError unless @mutex.locked?
|
108
|
-
# raise AssertionFailedError unless @children_results
|
109
|
-
# }
|
110
|
-
#end
|
111
|
-
|
112
96
|
#
|
113
97
|
# Compute this node; children must be computed and lock must be
|
114
98
|
# already acquired.
|
@@ -127,35 +111,20 @@ module CompTree
|
|
127
111
|
@result
|
128
112
|
end
|
129
113
|
|
130
|
-
def
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
true
|
139
|
-
else
|
140
|
-
false
|
141
|
-
end
|
114
|
+
def locked?
|
115
|
+
@lock_level != 0
|
116
|
+
end
|
117
|
+
|
118
|
+
def lock #:nodoc:
|
119
|
+
each_upward { |node|
|
120
|
+
node.lock_level += 1
|
121
|
+
}
|
142
122
|
end
|
143
123
|
|
144
124
|
def unlock #:nodoc:
|
145
|
-
# --- shared tree mutex and own mutex
|
146
|
-
#debug {
|
147
|
-
# raise AssertionFailedError unless @mutex.locked?
|
148
|
-
# trace "Unlocking #{@name}"
|
149
|
-
#}
|
150
125
|
each_upward { |node|
|
151
|
-
node.
|
152
|
-
#debug {
|
153
|
-
# if node.shared_lock == 0
|
154
|
-
# trace "#{node.name} unlocked by #{@name}"
|
155
|
-
# end
|
156
|
-
#}
|
126
|
+
node.lock_level -= 1
|
157
127
|
}
|
158
|
-
@mutex.unlock
|
159
128
|
end
|
160
129
|
end
|
161
130
|
end
|
data/test/common.rb
CHANGED
data/test/test_drain.rb
CHANGED
@@ -17,9 +17,10 @@ class TestDrain < Test::Unit::TestCase
|
|
17
17
|
driver.define(:height, :border, &func)
|
18
18
|
driver.define(:border, &func)
|
19
19
|
driver.define(:offset, &func)
|
20
|
-
bench_output "number of threads: #{threads}"
|
21
|
-
bench = Benchmark.measure { driver.compute(:area, threads) }
|
22
|
-
|
20
|
+
#bench_output "number of threads: #{threads}"
|
21
|
+
#bench = Benchmark.measure { driver.compute(:area, threads) }
|
22
|
+
driver.compute(:area, threads)
|
23
|
+
#bench_output bench
|
23
24
|
}
|
24
25
|
end
|
25
26
|
|
data/test/test_exception.rb
CHANGED
@@ -4,45 +4,65 @@ class TestException < Test::Unit::TestCase
|
|
4
4
|
def test_exception
|
5
5
|
test_error = Class.new StandardError
|
6
6
|
[true, false].each { |define_all|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
driver.define(:height, :border) { |border|
|
20
|
-
3 + border
|
21
|
-
}
|
22
|
-
|
23
|
-
if define_all
|
24
|
-
driver.define(:border) {
|
25
|
-
raise test_error
|
7
|
+
[true, false].each { |abort_on_exception|
|
8
|
+
error = (
|
9
|
+
begin
|
10
|
+
CompTree.build { |driver|
|
11
|
+
driver.define(:area, :width, :height, :offset) {
|
12
|
+
|width, height, offset|
|
13
|
+
width*height - offset
|
14
|
+
}
|
15
|
+
|
16
|
+
driver.define(:width, :border) { |border|
|
17
|
+
2 + border
|
26
18
|
}
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
19
|
+
|
20
|
+
driver.define(:height, :border) { |border|
|
21
|
+
3 + border
|
22
|
+
}
|
23
|
+
|
24
|
+
if define_all
|
25
|
+
driver.define(:border) {
|
26
|
+
raise test_error
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
driver.define(:offset) {
|
31
|
+
7
|
32
|
+
}
|
33
|
+
|
34
|
+
begin
|
35
|
+
previous = Thread.abort_on_exception
|
36
|
+
Thread.abort_on_exception = abort_on_exception
|
37
|
+
driver.compute(:area, 99)
|
38
|
+
ensure
|
39
|
+
Thread.abort_on_exception = previous
|
40
|
+
end
|
31
41
|
}
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
42
|
+
nil
|
43
|
+
rescue => e
|
44
|
+
e
|
45
|
+
end
|
46
|
+
)
|
47
|
+
|
48
|
+
if define_all
|
49
|
+
assert_block { error.is_a? test_error }
|
50
|
+
else
|
51
|
+
assert_block { error.is_a? CompTree::NoFunctionError }
|
38
52
|
end
|
39
|
-
|
40
|
-
|
41
|
-
if define_all
|
42
|
-
assert_block { error.is_a? test_error }
|
43
|
-
else
|
44
|
-
assert_block { error.is_a? CompTree::NoFunctionError }
|
45
|
-
end
|
53
|
+
}
|
46
54
|
}
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_num_threads
|
58
|
+
CompTree.build do |driver|
|
59
|
+
driver.define(:root) { }
|
60
|
+
assert_raises(ArgumentError) { driver.compute(:root, 0) }
|
61
|
+
assert_raises(ArgumentError) { driver.compute(:root, :threads => 0) }
|
62
|
+
assert_raises(ArgumentError) { driver.compute(:root, -1) }
|
63
|
+
assert_raises(ArgumentError) { driver.compute(:root, :threads => -1) }
|
64
|
+
assert_raises(ArgumentError) { driver.compute(:root, -11) }
|
65
|
+
assert_raises(ArgumentError) { driver.compute(:root, :threads => -11) }
|
66
|
+
end
|
47
67
|
end
|
48
|
-
end
|
68
|
+
end
|