comp_tree 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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)