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/lib/comp_tree.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2008, 2009 James M. Lawrence. All rights reserved.
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
- require 'comp_tree/comp_tree'
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
@@ -3,40 +3,35 @@ module CompTree
3
3
  module Algorithm
4
4
  module_function
5
5
 
6
- def compute_parallel(root, num_threads)
6
+ def compute_parallel(root, max_threads)
7
+ workers = []
7
8
  from_workers = Queue.new
8
9
  to_workers = Queue.new
9
10
 
10
- workers = (1..num_threads).map {
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
- num_threads.times { to_workers.push(nil) }
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
- raise node.computed
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 worker_loop(from_workers, to_workers)
29
- while node = to_workers.pop
30
- node.compute
31
- from_workers.push(node)
32
- end
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, num_threads, from_workers, to_workers)
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 == num_threads or !(node = find_node(root))
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
- break node
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
- if found = find_node(child)
78
- return found
79
- end
76
+ found = find_node(child) and return found
80
77
  }
81
78
  nil
82
79
  end
@@ -38,30 +38,24 @@ module CompTree
38
38
  #
39
39
  def define(name, *child_names, &block)
40
40
  #
41
- # retrieve or create node and children
41
+ # retrieve or create node
42
42
  #
43
-
44
- node = @nodes.fetch(name) {
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.fetch(child_name) {
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
- # _num_threads_ -- number of threads.
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, num_threads)
106
+ def compute(name, max_threads)
112
107
  begin
113
- num_threads = num_threads.to_int
108
+ max_threads = max_threads.to_int
114
109
  rescue NoMethodError
115
- raise TypeError, "can't convert #{num_threads.class} into Integer"
110
+ raise TypeError, "can't convert #{max_threads.class} into Integer"
116
111
  end
117
- unless num_threads > 0
118
- raise RangeError, "number of threads must be greater than zero"
112
+ if max_threads < 0
113
+ raise RangeError, "number of threads must be nonnegative"
119
114
  end
120
- root = @nodes.fetch(name) {
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 num_threads == 1
118
+ elsif max_threads == 1
126
119
  root.compute_now
127
120
  else
128
- Algorithm.compute_parallel(root, num_threads)
121
+ Algorithm.compute_parallel(root, max_threads)
129
122
  end
130
123
  end
131
124
  end
@@ -14,9 +14,7 @@ module CompTree
14
14
  def push(object)
15
15
  Thread.critical = true
16
16
  @queue.push object
17
- if thread = @waiting.shift
18
- thread.wakeup
19
- end
17
+ thread = @waiting.shift and thread.wakeup
20
18
  ensure
21
19
  Thread.critical = false
22
20
  end
@@ -13,9 +13,7 @@ module CompTree
13
13
  def push(object)
14
14
  @mutex.synchronize {
15
15
  @queue.push object
16
- if thread = @waiting.shift
17
- thread.wakeup
18
- end
16
+ thread = @waiting.shift and thread.wakeup
19
17
  }
20
18
  end
21
19
 
@@ -0,0 +1,3 @@
1
+ module CompTree
2
+ VERSION = "1.1.0"
3
+ 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 TestBasic < Test::Unit::TestCase
3
+ class BasicTest < CompTreeTest
4
4
  def test_define
5
- (1..20).each { |threads|
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
- (1..3).each { |n|
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
- (1..20).each { |threads|
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
- assert_raise(ArgumentError) {
54
+ assert_raises(ArgumentError) {
55
55
  driver.define {
56
56
  }
57
57
  }
58
- error = assert_raise(CompTree::RedefinitionError) {
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
- assert_raise(test_error) {
93
- driver.compute(:area, 6)
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
- (1..3).each { |threads|
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
- assert_raise(CompTree::RedefinitionError) {
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
- (1..20).each { |threads|
156
+ (0..20).each { |threads|
154
157
  CompTree.build { |driver|
155
158
  driver.define(:area, :width, :height, :offset) {
156
159
  |width, height, offset|
@@ -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 TestCircular < Test::Unit::TestCase
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|
@@ -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 'test/unit'
4
+ require 'minitest/unit'
5
+ require 'minitest/autorun' unless defined? Rake
5
6
  require 'comp_tree'
6
7
 
7
- module TestBase
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
- (1..10).each { |threads|
25
+ (0..10).each { |threads|
28
26
  yield threads
29
27
  }
30
28
  end
@@ -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 TestException < Test::Unit::TestCase
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
- (1..20).each { |num_threads|
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
- (1..20).each { |num_threads|
78
+ (0..20).each { |num_threads|
80
79
  CompTree.build do |driver|
81
80
  driver.define(:root) { }
82
- assert_nothing_raised {
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
- (1..20).each { |num_threads|
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 TestFlood < Test::Unit::TestCase
3
+ class FloodTest < CompTreeTest
4
4
  def test_thread_flood
5
- (1..200).each { |num_threads|
5
+ (0..200).each { |num_threads|
6
6
  CompTree.build { |driver|
7
7
  noop = lambda { |*args| true }
8
8
  driver.define(:a, :b, &noop)