comp_tree 0.7.6 → 1.0.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/install.rb CHANGED
@@ -1,3 +1,2 @@
1
- $LOAD_PATH.unshift 'devel'
2
- require 'jumpstart/simple_installer'
1
+ load './devel/jumpstart.rb'
3
2
  Jumpstart::SimpleInstaller.new.run
@@ -1,60 +1,61 @@
1
1
 
2
- require 'comp_tree/queue'
3
-
4
2
  module CompTree
5
3
  module Algorithm
6
4
  module_function
7
5
 
8
6
  def compute_parallel(root, num_threads)
9
- to_workers = Queue.new
10
7
  from_workers = Queue.new
11
- final_node = nil
8
+ to_workers = Queue.new
12
9
 
13
10
  workers = (1..num_threads).map {
14
11
  Thread.new {
15
- until (node = to_workers.pop) == nil
16
- node.compute
17
- from_workers.push node
18
- end
12
+ worker_loop(from_workers, to_workers)
19
13
  }
20
14
  }
21
15
 
22
- Thread.new {
23
- node_to_worker = nil
24
- node_from_worker = nil
25
- num_working = 0
26
- while true
27
- if num_working == num_threads or
28
- (not (node_to_worker = find_node(root)))
29
- #
30
- # maxed out or no nodes available -- wait for results
31
- #
32
- node_from_worker = from_workers.pop
33
- node_from_worker.unlock
34
- num_working -= 1
35
- if node_from_worker == root or
36
- node_from_worker.computed.is_a? Exception
37
- final_node = node_from_worker
38
- break
39
- end
40
- elsif node_to_worker
41
- #
42
- # found a node
43
- #
44
- to_workers.push node_to_worker
45
- num_working += 1
46
- node_to_worker = nil
47
- end
48
- end
49
- num_threads.times { to_workers.push nil }
50
- }.join
16
+ node = Thread.new {
17
+ master_loop(root, num_threads, from_workers, to_workers)
18
+ }.value
51
19
 
20
+ num_threads.times { to_workers.push(nil) }
52
21
  workers.each { |t| t.join }
53
22
 
54
- if final_node.computed.is_a? Exception
55
- raise final_node.computed
23
+ if node.computed.is_a? Exception
24
+ raise node.computed
56
25
  else
57
- final_node.result
26
+ node.result
27
+ end
28
+ end
29
+
30
+ def worker_loop(from_workers, to_workers)
31
+ while node = to_workers.pop
32
+ node.compute
33
+ from_workers.push(node)
34
+ end
35
+ end
36
+
37
+ def master_loop(root, num_threads, from_workers, to_workers)
38
+ num_working = 0
39
+ node = nil
40
+ while true
41
+ if num_working == num_threads or !(node = find_node(root))
42
+ #
43
+ # maxed out or no nodes available -- wait for results
44
+ #
45
+ node = from_workers.pop
46
+ node.unlock
47
+ num_working -= 1
48
+ if node == root or node.computed.is_a? Exception
49
+ break node
50
+ end
51
+ else
52
+ #
53
+ # found a node
54
+ #
55
+ num_working += 1
56
+ node.lock
57
+ to_workers.push(node)
58
+ end
58
59
  end
59
60
  end
60
61
 
@@ -67,18 +68,17 @@ module CompTree
67
68
  elsif not node.locked? and node.children_results
68
69
  #
69
70
  # Node is not computed, not locked, and its children are
70
- # computed; Ready to compute.
71
+ # computed; ready to compute.
71
72
  #
72
- node.lock
73
73
  node
74
74
  else
75
75
  #
76
76
  # locked or children not computed; recurse to children
77
77
  #
78
78
  node.each_child { |child|
79
- found = find_node(child) and (
79
+ if found = find_node(child)
80
80
  return found
81
- )
81
+ end
82
82
  }
83
83
  nil
84
84
  end
@@ -0,0 +1,55 @@
1
+
2
+ #
3
+ # CompTree -- Parallel Computation Tree.
4
+ #
5
+ # See README.rdoc.
6
+ #
7
+ module CompTree
8
+ VERSION = "1.0.0"
9
+
10
+ class << self
11
+ #
12
+ # :call-seq:
13
+ # build { |driver| ... }
14
+ #
15
+ # Build a new computation tree. A Driver instance is passed to the
16
+ # given block.
17
+ #
18
+ # Example:
19
+ # CompTree.build do |driver|
20
+ #
21
+ # # Define a function named 'area' taking these two arguments.
22
+ # driver.define(:area, :width, :height) { |width, height|
23
+ # width*height
24
+ # }
25
+ #
26
+ # # Define a function 'width' which takes a 'border' argument.
27
+ # driver.define(:width, :border) { |border|
28
+ # 7 + border
29
+ # }
30
+ #
31
+ # # Ditto for 'height'.
32
+ # driver.define(:height, :border) { |border|
33
+ # 5 + border
34
+ # }
35
+ #
36
+ # #
37
+ # # Define a constant function 'border'.
38
+ # driver.define(:border) {
39
+ # 2
40
+ # }
41
+ #
42
+ # # Compute the area using four parallel threads.
43
+ # puts driver.compute(:area, 4)
44
+ # # => 63
45
+ #
46
+ # # We've done this computation.
47
+ # puts((7 + 2)*(5 + 2))
48
+ # # => 63
49
+ # end
50
+ #
51
+ def build(opts = {})
52
+ yield Driver.new(opts)
53
+ end
54
+ end
55
+ end
@@ -1,26 +1,15 @@
1
1
 
2
- require 'comp_tree/algorithm'
3
- require 'comp_tree/node'
4
- require 'comp_tree/error'
5
-
6
2
  module CompTree
7
3
  #
8
4
  # Driver is the main interface to the computation tree. It is
9
5
  # responsible for defining nodes and running computations.
10
6
  #
11
7
  class Driver
12
- include Algorithm
13
-
14
8
  #
15
9
  # See CompTree.build
16
10
  #
17
- def initialize(opts = nil) #:nodoc:
18
- @node_class =
19
- if opts and opts[:node_class]
20
- opts[:node_class]
21
- else
22
- Node
23
- end
11
+ def initialize(opts = {}) #:nodoc:
12
+ @node_class = opts[:node_class] || Node
24
13
  @nodes = Hash.new
25
14
  end
26
15
 
@@ -49,16 +38,16 @@ module CompTree
49
38
  #
50
39
  def define(name, *child_names, &block)
51
40
  #
52
- # retrieve or create parent and children
41
+ # retrieve or create node and children
53
42
  #
54
43
 
55
- parent = @nodes.fetch(name) {
44
+ node = @nodes.fetch(name) {
56
45
  @nodes[name] = @node_class.new(name)
57
46
  }
58
- if parent.function
59
- raise RedefinitionError, "node `#{parent.name.inspect}' redefined"
47
+ if node.function
48
+ raise RedefinitionError.new(node.name)
60
49
  end
61
- parent.function = block
50
+ node.function = block
62
51
 
63
52
  children = child_names.map { |child_name|
64
53
  @nodes.fetch(child_name) {
@@ -69,12 +58,12 @@ module CompTree
69
58
  #
70
59
  # link
71
60
  #
72
- parent.children = children
61
+ node.children = children
73
62
  children.each { |child|
74
- child.parents << parent
63
+ child.parents << node
75
64
  }
76
65
 
77
- parent
66
+ node
78
67
  end
79
68
 
80
69
  #
@@ -106,35 +95,37 @@ module CompTree
106
95
  nil
107
96
  end
108
97
 
109
- #
110
- # :call-seq:
111
- # compute(name, threads)
112
- # compute(name, :threads => threads)
113
98
  #
114
99
  # _name_ -- unique node identifier (for example a symbol).
115
100
  #
116
- # _threads_ -- number of threads.
101
+ # _num_threads_ -- number of threads.
117
102
  #
118
- # Compute this node, returning its result.
103
+ # Compute the tree below _name_ and return the result.
119
104
  #
120
- # Any uncomputed children are computed first.
105
+ # If a node's computation raises an exception, the exception will
106
+ # be transferred to the caller of compute(). The tree will be
107
+ # left in a dirty state so that individual nodes may be examined.
108
+ # It is your responsibility to call reset() before attempting the
109
+ # computation again, otherwise the result will be undefined.
121
110
  #
122
- def compute(name, opts)
123
- threads = (opts.is_a?(Hash) ? opts[:threads] : opts).to_i
124
- unless threads > 0
125
- raise CompTree::ArgumentError,
126
- "number of threads must be greater than zero"
111
+ def compute(name, num_threads)
112
+ begin
113
+ num_threads = num_threads.to_int
114
+ rescue NoMethodError
115
+ raise TypeError, "can't convert #{num_threads.class} into Integer"
116
+ end
117
+ unless num_threads > 0
118
+ raise RangeError, "number of threads must be greater than zero"
127
119
  end
128
120
  root = @nodes.fetch(name) {
129
- raise CompTree::ArgumentError,
130
- "no such node named `#{name.inspect}'"
121
+ raise NoNodeError.new(name)
131
122
  }
132
123
  if root.computed
133
124
  root.result
134
- elsif threads == 1
135
- root.result = root.compute_now
125
+ elsif num_threads == 1
126
+ root.compute_now
136
127
  else
137
- compute_parallel(root, threads)
128
+ Algorithm.compute_parallel(root, num_threads)
138
129
  end
139
130
  end
140
131
  end
@@ -1,19 +1,53 @@
1
1
 
2
2
  module CompTree
3
+ #
3
4
  # Base class for CompTree errors.
4
- class Error < StandardError ; end
5
+ #
6
+ class Error < StandardError
7
+ def inspect #:nodoc:
8
+ "#<#{self.class.name}: #{message}>"
9
+ end
10
+ end
11
+
12
+ #
13
+ # Base class for node errors.
14
+ #
15
+ class NodeError < Error
16
+ attr_reader :node_name
17
+
18
+ def initialize(node_name) #:nodoc:
19
+ super()
20
+ @node_name = node_name
21
+ end
22
+ end
5
23
 
6
24
  #
7
- # Attempt to redefine a Node.
25
+ # An attempt was made to redefine a node.
8
26
  #
9
27
  # If you wish to only replace the function, set
10
28
  # driver.nodes[name].function = lambda { ... }
11
29
  #
12
- class RedefinitionError < Error ; end
30
+ class RedefinitionError < NodeError
31
+ def message #:nodoc:
32
+ "attempt to redefine node `#{node_name.inspect}'"
33
+ end
34
+ end
13
35
 
36
+ #
14
37
  # Encountered a node without a function during a computation.
15
- class NoFunctionError < Error ; end
38
+ #
39
+ class NoFunctionError < NodeError
40
+ def message #:nodoc:
41
+ "no function was defined for node `#{node_name.inspect}'"
42
+ end
43
+ end
16
44
 
17
- # Missing or malformed method arguments
18
- class ArgumentError < Error ; end
45
+ #
46
+ # Requested node does not exist.
47
+ #
48
+ class NoNodeError < NodeError
49
+ def message #:nodoc:
50
+ "no node named `#{node_name.inspect}'"
51
+ end
52
+ end
19
53
  end
@@ -1,24 +1,24 @@
1
1
 
2
- require 'thread'
3
-
4
2
  module CompTree
5
3
  #
6
4
  # Base class for nodes in the computation tree.
7
5
  #
8
6
  class Node
9
- attr_reader :name #:nodoc:
7
+ attr_reader :name
10
8
 
11
- attr_accessor :parents #:nodoc:
12
- attr_accessor :children #:nodoc:
13
- attr_accessor :function #:nodoc:
14
- attr_accessor :result #:nodoc:
15
- attr_accessor :computed #:nodoc:
16
- attr_accessor :lock_level #:nodoc:
9
+ attr_accessor(
10
+ :parents,
11
+ :children,
12
+ :function,
13
+ :result,
14
+ :computed,
15
+ :lock_level
16
+ )
17
17
 
18
18
  #
19
19
  # Create a node
20
20
  #
21
- def initialize(name) #:nodoc:
21
+ def initialize(name)
22
22
  @name = name
23
23
  @parents = []
24
24
  @children = []
@@ -29,7 +29,7 @@ module CompTree
29
29
  #
30
30
  # Reset the computation for this node.
31
31
  #
32
- def reset_self #:nodoc:
32
+ def reset_self
33
33
  @result = nil
34
34
  @computed = nil
35
35
  @lock_level = 0
@@ -39,27 +39,27 @@ module CompTree
39
39
  #
40
40
  # Reset the computation for this node and all children.
41
41
  #
42
- def reset #:nodoc:
42
+ def reset
43
43
  each_downward { |node|
44
44
  node.reset_self
45
45
  }
46
46
  end
47
47
 
48
- def each_downward(&block) #:nodoc:
48
+ def each_downward(&block)
49
49
  block.call(self)
50
50
  @children.each { |child|
51
51
  child.each_downward(&block)
52
52
  }
53
53
  end
54
54
 
55
- def each_upward(&block) #:nodoc:
55
+ def each_upward(&block)
56
56
  block.call(self)
57
57
  @parents.each { |parent|
58
58
  parent.each_upward(&block)
59
59
  }
60
60
  end
61
61
 
62
- def each_child #:nodoc:
62
+ def each_child
63
63
  @children.each { |child|
64
64
  yield(child)
65
65
  }
@@ -69,20 +69,26 @@ module CompTree
69
69
  # Force all children and self to be computed; no locking required.
70
70
  # Intended to be used outside of parallel computations.
71
71
  #
72
- def compute_now #:nodoc:
73
- unless @children_results
74
- @children_results = @children.map { |child|
75
- child.compute_now
76
- }
72
+ def compute_now
73
+ unless @computed
74
+ unless @children_results
75
+ @children_results = @children.map { |child|
76
+ child.compute_now
77
+ }
78
+ end
79
+ compute
80
+ if @computed.is_a? Exception
81
+ raise @computed
82
+ end
77
83
  end
78
- compute
84
+ @result
79
85
  end
80
86
 
81
87
  #
82
88
  # If all children have been computed, return their results;
83
89
  # otherwise return nil.
84
90
  #
85
- def children_results #:nodoc:
91
+ def children_results
86
92
  @children_results or (
87
93
  @children_results = @children.map { |child|
88
94
  unless child.computed
@@ -97,11 +103,10 @@ module CompTree
97
103
  # Compute this node; children must be computed and lock must be
98
104
  # already acquired.
99
105
  #
100
- def compute #:nodoc:
106
+ def compute
101
107
  begin
102
108
  unless @function
103
- raise NoFunctionError,
104
- "no function was defined for node `#{@name.inspect}'"
109
+ raise NoFunctionError.new(@name)
105
110
  end
106
111
  @result = @function.call(*@children_results)
107
112
  @computed = true
@@ -115,13 +120,13 @@ module CompTree
115
120
  @lock_level != 0
116
121
  end
117
122
 
118
- def lock #:nodoc:
123
+ def lock
119
124
  each_upward { |node|
120
125
  node.lock_level += 1
121
126
  }
122
127
  end
123
128
 
124
- def unlock #:nodoc:
129
+ def unlock
125
130
  each_upward { |node|
126
131
  node.lock_level -= 1
127
132
  }
@@ -0,0 +1 @@
1
+ require("comp_tree/queue/queue_" + (RUBY_VERSION < "1.9" ? "18" : "19"))
@@ -14,15 +14,15 @@ module CompTree
14
14
  def push(object)
15
15
  Thread.critical = true
16
16
  @queue.push object
17
- begin
18
- thread = @waiting.shift and thread.wakeup
19
- ensure
20
- Thread.critical = false
17
+ if thread = @waiting.shift
18
+ thread.wakeup
21
19
  end
20
+ ensure
21
+ Thread.critical = false
22
22
  end
23
23
 
24
24
  def pop
25
- while (Thread.critical = true; @queue.empty?)
25
+ while (Thread.critical = true ; @queue.empty?)
26
26
  @waiting.push Thread.current
27
27
  Thread.stop
28
28
  end
@@ -13,7 +13,9 @@ module CompTree
13
13
  def push(object)
14
14
  @mutex.synchronize {
15
15
  @queue.push object
16
- thread = @waiting.shift and thread.wakeup
16
+ if thread = @waiting.shift
17
+ thread.wakeup
18
+ end
17
19
  }
18
20
  end
19
21
 
data/lib/comp_tree.rb CHANGED
@@ -1,83 +1,30 @@
1
1
  #
2
2
  # Copyright (c) 2008, 2009 James M. Lawrence. All rights reserved.
3
3
  #
4
- # Permission is hereby granted, free of charge, to any person obtaining a copy
5
- # of this software and associated documentation files (the "Software"), to deal
6
- # in the Software without restriction, including without limitation the rights
7
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
- # copies of the Software, and to permit persons to whom the Software is
9
- # furnished to do so, subject to the following conditions:
4
+ # Permission is hereby granted, free of charge, to any person
5
+ # obtaining a copy of this software and associated documentation files
6
+ # (the "Software"), to deal in the Software without restriction,
7
+ # including without limitation the rights to use, copy, modify, merge,
8
+ # publish, distribute, sublicense, and/or sell copies of the Software,
9
+ # and to permit persons to whom the Software is furnished to do so,
10
+ # subject to the following conditions:
10
11
  #
11
- # The above copyright notice and this permission notice shall be included in
12
- # all copies or substantial portions of the Software.
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
13
14
  #
14
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
- # THE SOFTWARE.
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
19
+ # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20
+ # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # SOFTWARE.
21
23
  #
22
24
 
25
+ require 'comp_tree/error'
26
+ require 'comp_tree/queue/queue'
27
+ require 'comp_tree/node'
28
+ require 'comp_tree/algorithm'
23
29
  require 'comp_tree/driver'
24
-
25
- #
26
- # CompTree -- Parallel Computation Tree.
27
- #
28
- # See README.rdoc.
29
- #
30
- module CompTree
31
- COMP_TREE_VERSION = "0.7.6"
32
-
33
- class << self
34
- #
35
- # :call-seq:
36
- # build { |driver| ... }
37
- #
38
- # Build a new computation tree. A Driver instance is passed to the
39
- # given block.
40
- #
41
- # Options hash:
42
- #
43
- # <tt>:node_class</tt> -- CompTree::Node subclass from which nodes
44
- # are created.
45
- #
46
- # Example:
47
- # CompTree.build do |driver|
48
- #
49
- # # Define a function named 'area' taking these two arguments.
50
- # driver.define(:area, :width, :height) { |width, height|
51
- # width*height
52
- # }
53
- #
54
- # # Define a function 'width' which takes a 'border' argument.
55
- # driver.define(:width, :border) { |border|
56
- # 7 + border
57
- # }
58
- #
59
- # # Ditto for 'height'.
60
- # driver.define(:height, :border) { |border|
61
- # 5 + border
62
- # }
63
- #
64
- # #
65
- # # Define a constant function 'border'.
66
- # driver.define(:border) {
67
- # 2
68
- # }
69
- #
70
- # # Compute the area using four parallel threads.
71
- # puts driver.compute(:area, :threads => 4)
72
- # # => 63
73
- #
74
- # # We've done this computation.
75
- # puts((7 + 2)*(5 + 2))
76
- # # => 63
77
- # end
78
- #
79
- def build(opts = nil)
80
- yield Driver.new(opts)
81
- end
82
- end
83
- end
30
+ require 'comp_tree/comp_tree'
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/common'
1
+ require File.dirname(__FILE__) + '/comp_tree_test_base'
2
2
 
3
3
  class TestBasic < Test::Unit::TestCase
4
4
  def test_define
@@ -44,7 +44,7 @@ class TestBasic < Test::Unit::TestCase
44
44
  (1..20).each { |threads|
45
45
  CompTree.build do |driver|
46
46
  driver.define(:a) { 33 }
47
- assert_equal(33, driver.compute(:a, :threads => threads))
47
+ assert_equal(33, driver.compute(:a, threads))
48
48
  end
49
49
  }
50
50
  end
@@ -55,12 +55,14 @@ class TestBasic < Test::Unit::TestCase
55
55
  driver.define {
56
56
  }
57
57
  }
58
- assert_raise(CompTree::RedefinitionError) {
58
+ error = assert_raise(CompTree::RedefinitionError) {
59
59
  driver.define(:a) {
60
60
  }
61
61
  driver.define(:a) {
62
62
  }
63
63
  }
64
+ assert_equal "attempt to redefine node `:a'", error.message
65
+ assert_equal :a, error.node_name
64
66
  }
65
67
  end
66
68
 
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/common'
1
+ require File.dirname(__FILE__) + '/comp_tree_test_base'
2
2
 
3
3
  class TestCircular < Test::Unit::TestCase
4
4
  def test_circular