build-graph 1.0.3 → 1.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +9 -6
- data/README.md +32 -8
- data/build-graph.gemspec +2 -2
- data/lib/build/graph.rb +1 -1
- data/lib/build/graph/call_stack.rb +51 -0
- data/lib/build/graph/edge.rb +1 -2
- data/lib/build/graph/task.rb +23 -3
- data/lib/build/graph/version.rb +1 -1
- data/lib/build/graph/walker.rb +47 -18
- data/spec/build/graph/build_test.rb +3 -2
- data/spec/build/graph/call_stack_spec.rb +42 -0
- data/{lib/build/graph/error.rb → spec/build/graph/edge_spec.rb} +15 -14
- data/spec/build/graph/graph_spec.rb +33 -27
- data/spec/build/graph/process_graph.rb +10 -6
- data/spec/build/graph/program/Benchmark.cpp +3 -3
- data/spec/build/graph/program/DictionarySort.h +58 -58
- data/spec/build/graph/program/ParallelMergeSort.h +53 -53
- data/spec/build/graph/program/main.cpp +21 -21
- data/spec/build/graph/walker_spec.rb +4 -6
- metadata +12 -8
@@ -0,0 +1,42 @@
|
|
1
|
+
#!/usr/bin/env rspec
|
2
|
+
# Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
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:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
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.
|
21
|
+
|
22
|
+
require 'build/graph/call_stack'
|
23
|
+
|
24
|
+
module Build::Graph::CallStackSpec
|
25
|
+
describe Build::Graph::CallStack do
|
26
|
+
it "should merge state" do
|
27
|
+
outer_state = nil
|
28
|
+
inner_state = nil
|
29
|
+
|
30
|
+
subject.with(x: 10) do
|
31
|
+
outer_state = subject.last
|
32
|
+
|
33
|
+
subject.with(x: 20, y: 30) do
|
34
|
+
inner_state = subject.last
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
expect(outer_state).to include(:x)
|
39
|
+
expect(inner_state).to include(:x, :y)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
#!/usr/bin/env rspec
|
2
|
+
|
3
|
+
# Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
4
|
#
|
3
5
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
6
|
# of this software and associated documentation files (the "Software"), to deal
|
@@ -18,21 +20,20 @@
|
|
18
20
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
21
|
# THE SOFTWARE.
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
23
|
+
require_relative 'process_graph'
|
24
|
+
|
25
|
+
RSpec.describe Build::Graph::Edge do
|
26
|
+
let(:failed_task) {double(:failed? => true)}
|
25
27
|
|
26
|
-
|
27
|
-
|
28
|
-
super "Command #{command.inspect} failed with exit status #{status}!"
|
28
|
+
it "should fail if failed task is added" do
|
29
|
+
subject.traverse(failed_task)
|
29
30
|
|
30
|
-
|
31
|
-
|
32
|
-
end
|
31
|
+
expect(subject.failed?).to be_truthy
|
32
|
+
end
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
|
34
|
+
it "should fail if failed task is skipped" do
|
35
|
+
subject.skip!(failed_task)
|
36
|
+
|
37
|
+
expect(subject.failed?).to be_truthy
|
37
38
|
end
|
38
39
|
end
|
@@ -25,13 +25,16 @@ module Build::Graph::GraphSpec
|
|
25
25
|
include ProcessGraph
|
26
26
|
|
27
27
|
describe Build::Graph do
|
28
|
+
let(:group) {Process::Group.new}
|
29
|
+
|
30
|
+
let(:logger) {Logger.new($stderr).tap{|logger| logger.level = Logger::DEBUG}}
|
31
|
+
|
28
32
|
it "shouldn't update mtime" do
|
29
33
|
test_glob = Glob.new(__dir__, "*.rb")
|
30
34
|
listing_output = Paths.directory(__dir__, ["listing.txt"])
|
31
35
|
|
32
36
|
FileUtils.rm_f listing_output.to_a
|
33
37
|
|
34
|
-
group = Process::Group.new
|
35
38
|
walker = Walker.for(ProcessTask, group)
|
36
39
|
|
37
40
|
top = ProcessNode.top do
|
@@ -40,13 +43,15 @@ module Build::Graph::GraphSpec
|
|
40
43
|
end
|
41
44
|
end
|
42
45
|
|
43
|
-
|
44
|
-
|
46
|
+
group.wait do
|
47
|
+
walker.update(top)
|
48
|
+
end
|
45
49
|
|
46
50
|
first_modified_time = listing_output.first.modified_time
|
47
51
|
|
48
|
-
|
49
|
-
|
52
|
+
group.wait do
|
53
|
+
walker.update(top)
|
54
|
+
end
|
50
55
|
|
51
56
|
# The output file shouldn't have been changed because already exists and the input files haven't changed either:
|
52
57
|
second_modified_time = listing_output.first.modified_time
|
@@ -60,8 +65,9 @@ module Build::Graph::GraphSpec
|
|
60
65
|
# The granularity of modification times isn't that great, so we use >= below.
|
61
66
|
# sleep 1
|
62
67
|
|
63
|
-
|
64
|
-
|
68
|
+
group.wait do
|
69
|
+
walker.update(top)
|
70
|
+
end
|
65
71
|
|
66
72
|
expect(listing_output.first.modified_time).to be >= first_modified_time
|
67
73
|
|
@@ -73,7 +79,6 @@ module Build::Graph::GraphSpec
|
|
73
79
|
code_glob = Glob.new(program_root, "*.cpp")
|
74
80
|
program_path = Path.join(program_root, "dictionary-sort")
|
75
81
|
|
76
|
-
group = Process::Group.new
|
77
82
|
walker = Walker.for(ProcessTask, group)
|
78
83
|
|
79
84
|
#FileUtils.touch(code_glob.first)
|
@@ -110,8 +115,9 @@ module Build::Graph::GraphSpec
|
|
110
115
|
end
|
111
116
|
end
|
112
117
|
|
113
|
-
|
114
|
-
|
118
|
+
group.wait do
|
119
|
+
walker.update(top)
|
120
|
+
end
|
115
121
|
|
116
122
|
expect(program_path).to be_exist
|
117
123
|
expect(code_glob.first.modified_time).to be <= program_path.modified_time
|
@@ -122,48 +128,48 @@ module Build::Graph::GraphSpec
|
|
122
128
|
files = Glob.new(program_root, "*.cpp")
|
123
129
|
destination = Path.new(__dir__) + "tmp"
|
124
130
|
|
125
|
-
group = Process::Group.new
|
126
131
|
walker = Walker.for(ProcessTask, group)
|
127
132
|
|
128
|
-
#FileUtils.touch(code_glob.first)
|
129
|
-
|
130
133
|
top = ProcessNode.top files do
|
131
|
-
|
134
|
+
mkpath destination
|
132
135
|
|
133
136
|
inputs.each do |source_path|
|
134
137
|
destination_path = source_path.rebase(destination)
|
135
138
|
|
136
139
|
process source_path, destination_path do
|
137
|
-
|
140
|
+
$stderr.puts "Copying #{inputs.first} -> #{outputs.first}"
|
141
|
+
install inputs.first, outputs.first
|
138
142
|
end
|
139
143
|
end
|
140
144
|
end
|
141
145
|
|
142
|
-
|
143
|
-
|
146
|
+
mutex = Mutex.new
|
147
|
+
files_deleted = false
|
144
148
|
|
145
149
|
thread = Thread.new do
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
destination.glob("*.cpp").
|
150
|
+
sleep 1
|
151
|
+
|
152
|
+
mutex.synchronize do
|
153
|
+
destination.glob("*.cpp").delete
|
150
154
|
|
151
|
-
|
155
|
+
files_deleted = true
|
152
156
|
end
|
153
157
|
end
|
154
158
|
|
155
159
|
walker.run do
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
+
mutex.synchronize do
|
161
|
+
group.wait do
|
162
|
+
walker.update(top)
|
163
|
+
end
|
164
|
+
end
|
160
165
|
|
161
|
-
break if
|
166
|
+
break if files_deleted
|
162
167
|
end
|
163
168
|
|
164
169
|
thread.join
|
165
170
|
|
166
171
|
expect(destination).to be_exist
|
172
|
+
# This line failed, may still be a race condition:
|
167
173
|
expect(destination.glob("*.cpp").count).to be == 2
|
168
174
|
|
169
175
|
destination.delete
|
@@ -58,12 +58,16 @@ module ProcessGraph
|
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
|
-
def
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
61
|
+
def mkpath(*args)
|
62
|
+
return unless wet?
|
63
|
+
|
64
|
+
FileUtils.mkpath(*args)
|
65
|
+
end
|
66
|
+
|
67
|
+
def install(*args)
|
68
|
+
return unless wet?
|
69
|
+
|
70
|
+
FileUtils.install(*args)
|
67
71
|
end
|
68
72
|
|
69
73
|
# This function is called to finish the invocation of the task within the graph.
|
@@ -18,16 +18,16 @@ namespace Benchmark
|
|
18
18
|
gettimeofday (&t, (struct timezone*)0);
|
19
19
|
return ((TimeT)t.tv_sec) + ((TimeT)t.tv_usec / 1000000.0);
|
20
20
|
}
|
21
|
-
|
21
|
+
|
22
22
|
WallTime::WallTime () {
|
23
23
|
this->reset();
|
24
24
|
}
|
25
|
-
|
25
|
+
|
26
26
|
void WallTime::reset () {
|
27
27
|
this->_last = system_time();
|
28
28
|
this->_total = 0.0;
|
29
29
|
}
|
30
|
-
|
30
|
+
|
31
31
|
TimeT WallTime::total () const {
|
32
32
|
TimeT current = system_time();
|
33
33
|
this->_total += current - this->_last;
|
@@ -28,20 +28,20 @@ namespace DictionarySort
|
|
28
28
|
//const int SORT_MODE = -1;
|
29
29
|
// Use ParallelMergeSort with 2^n threads
|
30
30
|
const int SORT_MODE = 3; // = n
|
31
|
-
|
31
|
+
|
32
32
|
typedef std::uint64_t IndexT;
|
33
|
-
|
33
|
+
|
34
34
|
template <typename CharT, typename MapT>
|
35
35
|
class Dictionary {
|
36
36
|
public:
|
37
37
|
typedef std::vector<CharT> WordT;
|
38
38
|
typedef std::vector<WordT> WordsT;
|
39
39
|
typedef std::vector<IndexT> OrderT;
|
40
|
-
|
40
|
+
|
41
41
|
static const int ORDERED_LT = -1;
|
42
42
|
static const int ORDERED_EQ = 0;
|
43
43
|
static const int ORDERED_GT = 1;
|
44
|
-
|
44
|
+
|
45
45
|
// Compare two order vectors to determine the relative nature of lhs compared to rhs.
|
46
46
|
// We assume that lhs and rhs have at least one element each.
|
47
47
|
// ORDERED_LT => (lhs < rhs)
|
@@ -50,155 +50,155 @@ namespace DictionarySort
|
|
50
50
|
static int compare(const OrderT & lhs, const OrderT & rhs)
|
51
51
|
{
|
52
52
|
std::size_t offset = 0;
|
53
|
-
|
53
|
+
|
54
54
|
while (offset < lhs.size() && offset < rhs.size()) {
|
55
55
|
if (lhs[offset] < rhs[offset])
|
56
56
|
return ORDERED_LT;
|
57
57
|
else if (lhs[offset] > rhs[offset])
|
58
58
|
return ORDERED_GT;
|
59
|
-
|
59
|
+
|
60
60
|
offset += 1;
|
61
61
|
}
|
62
|
-
|
62
|
+
|
63
63
|
if (lhs.size() == rhs.size())
|
64
64
|
return ORDERED_EQ;
|
65
|
-
|
66
|
-
// lhs was longer,
|
65
|
+
|
66
|
+
// lhs was longer,
|
67
67
|
if (offset < lhs.size())
|
68
68
|
return ORDERED_GT;
|
69
|
-
|
69
|
+
|
70
70
|
return ORDERED_LT;
|
71
71
|
}
|
72
|
-
|
72
|
+
|
73
73
|
static int compare(Dictionary * dictionary, const WordT & lhs, const WordT & rhs) {
|
74
74
|
std::size_t offset = 0;
|
75
|
-
|
75
|
+
|
76
76
|
while (offset < lhs.size() && offset < rhs.size()) {
|
77
77
|
IndexT left_order = dictionary->_characterOrder[lhs[offset]];
|
78
78
|
IndexT right_order = dictionary->_characterOrder[rhs[offset]];
|
79
|
-
|
79
|
+
|
80
80
|
if (left_order < right_order)
|
81
81
|
return ORDERED_LT;
|
82
82
|
else if (left_order > right_order)
|
83
83
|
return ORDERED_GT;
|
84
|
-
|
84
|
+
|
85
85
|
offset += 1;
|
86
86
|
}
|
87
|
-
|
87
|
+
|
88
88
|
if (lhs.size() == rhs.size())
|
89
89
|
return ORDERED_EQ;
|
90
|
-
|
90
|
+
|
91
91
|
if (offset < lhs.size())
|
92
92
|
return ORDERED_GT;
|
93
|
-
|
93
|
+
|
94
94
|
return ORDERED_LT;
|
95
95
|
}
|
96
|
-
|
96
|
+
|
97
97
|
private:
|
98
98
|
WordT _alphabet;
|
99
|
-
|
99
|
+
|
100
100
|
MapT _characterOrder;
|
101
101
|
//int _characterOrder[256];
|
102
102
|
//std::map<CharT, IndexT> _characterOrder;
|
103
|
-
|
103
|
+
|
104
104
|
IndexT width;
|
105
105
|
IndexT characters_per_segment;
|
106
|
-
|
106
|
+
|
107
107
|
// This is a light weight wrapper over WordT which caches its OrderT, an integer representation of position based on the given dictionary.
|
108
108
|
struct OrderedWord {
|
109
109
|
WordT word;
|
110
|
-
|
110
|
+
|
111
111
|
// We can generate this as part of the sorting process. Because the sort is parallel, generation of word order (which is relatively expensive) is distributed across multiple processors.
|
112
112
|
mutable OrderT order;
|
113
|
-
|
113
|
+
|
114
114
|
// The first time this function is called, it must be guaranteed from a single thread.
|
115
115
|
// After that, it can be called from multiple threads at the same time.
|
116
116
|
// The parallel merge sort algorithm guarantees this.
|
117
117
|
const OrderT & fetch_order(Dictionary * dictionary) const {
|
118
118
|
if (order.size() == 0 && word.size() > 0)
|
119
119
|
order = dictionary->sum(word);
|
120
|
-
|
120
|
+
|
121
121
|
return order;
|
122
122
|
}
|
123
123
|
};
|
124
|
-
|
124
|
+
|
125
125
|
struct CompareWordsAscending {
|
126
126
|
Dictionary * dictionary;
|
127
|
-
|
127
|
+
|
128
128
|
CompareWordsAscending (Dictionary * _dictionary)
|
129
129
|
: dictionary(_dictionary)
|
130
130
|
{
|
131
131
|
}
|
132
|
-
|
132
|
+
|
133
133
|
bool operator()(const WordT * a, const WordT * b) const {
|
134
134
|
return compare(dictionary, a, b) == ORDERED_LT;
|
135
135
|
}
|
136
|
-
|
136
|
+
|
137
137
|
bool operator()(const OrderedWord * a, const OrderedWord * b) const {
|
138
138
|
return compare(a->fetch_order(dictionary), b->fetch_order(dictionary)) == ORDERED_LT;
|
139
139
|
}
|
140
140
|
};
|
141
|
-
|
141
|
+
|
142
142
|
struct UnorderedWord {
|
143
143
|
WordT word;
|
144
144
|
Dictionary * dictionary;
|
145
|
-
|
145
|
+
|
146
146
|
UnorderedWord(const WordT & _word, Dictionary * _dictionary)
|
147
147
|
: word(_word), dictionary(_dictionary) {
|
148
|
-
|
148
|
+
|
149
149
|
}
|
150
|
-
|
150
|
+
|
151
151
|
bool operator<(const UnorderedWord & other) const {
|
152
152
|
return compare(dictionary, *this, other) == ORDERED_LT;
|
153
153
|
}
|
154
154
|
};
|
155
|
-
|
155
|
+
|
156
156
|
public:
|
157
157
|
Dictionary(WordT alphabet)
|
158
158
|
: _alphabet(alphabet)
|
159
159
|
{
|
160
160
|
IndexT index = 1;
|
161
|
-
|
161
|
+
|
162
162
|
// Build up the character order map
|
163
163
|
for (typename WordT::iterator i = _alphabet.begin(); i != _alphabet.end(); ++i) {
|
164
164
|
_characterOrder[*i] = index;
|
165
165
|
index += 1;
|
166
166
|
}
|
167
|
-
|
167
|
+
|
168
168
|
width = std::ceil(std::log(alphabet.size()) / std::log(2));
|
169
|
-
|
169
|
+
|
170
170
|
// Naturally floor the result by integer division/truncation.
|
171
171
|
characters_per_segment = (sizeof(IndexT) * 8) / width;
|
172
172
|
}
|
173
|
-
|
173
|
+
|
174
174
|
OrderT sum(const WordT & word) {
|
175
175
|
OrderT order;
|
176
176
|
std::size_t index = 0;
|
177
|
-
|
177
|
+
|
178
178
|
while (index < word.size()) {
|
179
179
|
IndexT count = characters_per_segment;
|
180
180
|
IndexT sum = 0;
|
181
|
-
|
181
|
+
|
182
182
|
while (index < word.size()) {
|
183
183
|
count -= 1;
|
184
|
-
|
184
|
+
|
185
185
|
sum <<= width;
|
186
186
|
sum += _characterOrder[word[index]];
|
187
|
-
|
187
|
+
|
188
188
|
index += 1;
|
189
|
-
|
189
|
+
|
190
190
|
if (count == 0)
|
191
191
|
break;
|
192
192
|
}
|
193
|
-
|
193
|
+
|
194
194
|
// Shift along any remaining count, since we are ordering using the left most significant character.
|
195
195
|
sum <<= (count * width);
|
196
196
|
order.push_back(sum);
|
197
197
|
}
|
198
|
-
|
198
|
+
|
199
199
|
return order;
|
200
200
|
}
|
201
|
-
|
201
|
+
|
202
202
|
// The words will be sorted in-place.
|
203
203
|
template <typename ToSortT>
|
204
204
|
void sort (ToSortT & words, int mode = 2)
|
@@ -210,7 +210,7 @@ namespace DictionarySort
|
|
210
210
|
if (mode == -1) {
|
211
211
|
// Sort the words using built-in sorting algorithm, for comparison:
|
212
212
|
std::sort(words.begin(), words.end(), comparator);
|
213
|
-
} else {
|
213
|
+
} else {
|
214
214
|
ParallelMergeSort::sort(words, comparator, std::size_t(mode));
|
215
215
|
}
|
216
216
|
|
@@ -221,7 +221,7 @@ namespace DictionarySort
|
|
221
221
|
std::cerr << " * Processor sort time: " << sample.processor_time_total << std::endl;
|
222
222
|
std::cerr << " * Approximate processor usage: " << sample.approximate_processor_usage() << std::endl;
|
223
223
|
}
|
224
|
-
|
224
|
+
|
225
225
|
// This function can be slow due to the large amount of memory required for large datasets.
|
226
226
|
uint64_t sort(const WordsT & input, WordsT & output)
|
227
227
|
{
|
@@ -229,41 +229,41 @@ namespace DictionarySort
|
|
229
229
|
|
230
230
|
// Allocate all words in one go:
|
231
231
|
OrderedWord * allocation = new OrderedWord[input.size()];
|
232
|
-
|
232
|
+
|
233
233
|
// Copy pointers to intermediate list which will be used for sorting:
|
234
234
|
OrderedWordsT words(input.size());
|
235
|
-
|
235
|
+
|
236
236
|
// Calculate order vector for each word in preparation for sort.
|
237
237
|
for (std::size_t i = 0; i < input.size(); i += 1) {
|
238
238
|
words[i] = &allocation[i];
|
239
|
-
|
239
|
+
|
240
240
|
words[i]->word = input[i];
|
241
|
-
|
241
|
+
|
242
242
|
// We can force generation of the order cache, but performance may be reduced by about 10%.
|
243
243
|
//words[i]->fetch_order(this);
|
244
244
|
}
|
245
|
-
|
245
|
+
|
246
246
|
// Change the mode from -1 for std::sort, to 0..n for ParallelMergeSort where 2^n is the number of threads to use.
|
247
247
|
sort(words, SORT_MODE);
|
248
|
-
|
248
|
+
|
249
249
|
// Prepare container for sorted output:
|
250
250
|
output.reserve(input.size());
|
251
251
|
output.resize(0);
|
252
|
-
|
252
|
+
|
253
253
|
uint64_t checksum = 1, offset = 1;
|
254
254
|
// Copy sorted words to output vector.
|
255
255
|
for (typename OrderedWordsT::iterator i = words.begin(); i != words.end(); ++i) {
|
256
256
|
output.push_back((*i)->word);
|
257
|
-
|
257
|
+
|
258
258
|
// Compute a very simple checksum for verifying sorted order.
|
259
259
|
const OrderT & order = (*i)->fetch_order(this);
|
260
260
|
for (typename OrderT::const_iterator j = order.begin(); j != order.end(); ++j) {
|
261
261
|
checksum ^= *j + (offset++ % checksum);
|
262
|
-
}
|
262
|
+
}
|
263
263
|
}
|
264
|
-
|
264
|
+
|
265
265
|
delete[] allocation;
|
266
|
-
|
266
|
+
|
267
267
|
return checksum;
|
268
268
|
}
|
269
269
|
};
|