comp_tree 0.7.1 → 0.7.2
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 → 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
|