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.
@@ -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
@@ -1,7 +1,7 @@
1
1
 
2
2
  require 'rbconfig'
3
3
 
4
- module Jumpstart
4
+ class Jumpstart
5
5
  module Ruby
6
6
  EXECUTABLE = lambda {
7
7
  name = File.join(
@@ -3,7 +3,7 @@ require 'rbconfig'
3
3
  require 'fileutils'
4
4
  require 'find'
5
5
 
6
- module Jumpstart
6
+ class Jumpstart
7
7
  class SimpleInstaller
8
8
  def initialize
9
9
  dest_root = Config::CONFIG["sitelibdir"]
data/lib/comp_tree.rb CHANGED
@@ -28,6 +28,8 @@ require 'comp_tree/driver'
28
28
  # See README.rdoc.
29
29
  #
30
30
  module CompTree
31
+ VERSION = "0.7.2"
32
+
31
33
  class << self
32
34
  #
33
35
  # :call-seq:
@@ -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
- #trace "Computing #{root.name} with #{num_threads} threads"
21
- finished = nil
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
- threads = (0...num_threads).map { |thread_index|
27
- Thread.new {
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
- loop_with(LEAVE, AGAIN) {
38
- node = tree_mutex.synchronize {
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
- #trace "Thread #{thread_index} computing node"
61
- #debug {
62
- # node.trace_compute
63
- #}
16
+ workers = (1..num_threads).map {
17
+ Thread.new {
18
+ until (node = to_workers.pop) == :finished
64
19
  node.compute
65
- #trace "Thread #{thread_index} node computed; waiting for tree lock"
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
- #trace "Main: waiting for threads to launch and block."
105
- until tree_mutex.synchronize { num_threads_ready == num_threads }
106
- Thread.pass
107
- end
108
-
109
- tree_mutex.synchronize {
110
- #trace "Main: entering main loop"
111
- until num_threads_ready == 0
112
- #trace "Main: waking threads"
113
- condition.broadcast
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
- #trace "Main: waiting for a node"
121
- condition.wait(tree_mutex)
122
- #trace "Main: got a node"
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
- #trace "Main: waiting for threads to finish."
127
- threads.each { |t| t.join }
128
-
129
- #trace "Main: computation done."
130
- if finished.is_a? Exception
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
- root.result
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 (children_results = node.find_children_results) and node.try_lock
64
+ elsif not node.locked? and node.children_results
147
65
  #
148
- # Node is not computed and its children are computed;
149
- # and we have the lock. Ready to compute.
66
+ # Node is not computed, not locked, and its children are
67
+ # computed; Ready to compute.
150
68
  #
151
- node.children_results = children_results
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
- next_node = find_node(child) and return next_node
76
+ found = find_node(child) and (
77
+ return found
78
+ )
160
79
  }
161
80
  nil
162
81
  end
@@ -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
@@ -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 :shared_lock #:nodoc:
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
- @shared_lock = 0
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
- # Do not assign to @children_results since own lock is not
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 try_lock #:nodoc:
131
- # --- shared tree mutex and own mutex
132
- if @shared_lock == 0 and @mutex.try_lock
133
- #trace "Locking #{@name}"
134
- each_upward { |node|
135
- node.shared_lock += 1
136
- #trace "#{node.name} locked by #{@name}: level: #{node.shared_lock}"
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.shared_lock -= 1
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
@@ -2,7 +2,7 @@ $LOAD_PATH.unshift File.dirname(__FILE__) + "/../lib"
2
2
 
3
3
  require 'test/unit'
4
4
  require 'comp_tree'
5
- require 'benchmark'
5
+ #require 'benchmark'
6
6
 
7
7
  module TestCommon
8
8
  if ARGV.include?("--bench")
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
- bench_output bench
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
 
@@ -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
- error = (
8
- begin
9
- CompTree.build { |driver|
10
- driver.define(:area, :width, :height, :offset) {
11
- |width, height, offset|
12
- width*height - offset
13
- }
14
-
15
- driver.define(:width, :border) { |border|
16
- 2 + border
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
- end
28
-
29
- driver.define(:offset) {
30
- 7
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
- driver.compute(:area, 99)
34
- }
35
- nil
36
- rescue => e
37
- e
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