Skip to content

Commit 4f57562

Browse files
committed
llvm-reduce: Try to kill parallel workitems once we have a result.
The current reduction logic tries to reproduce what a serial reduction would produce, and just takes the first one that is still interesting. We still have to wait for all others to complete though, which at that point is just a waste. This helps speed things up with long running reducers, which I frequently have. e.g. for the added sleep test on my system, it took about 8 seconds before this change and about 4 after. https://reviews.llvm.org/D138953
1 parent d7bba07 commit 4f57562

File tree

6 files changed

+119
-23
lines changed

6 files changed

+119
-23
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/python
2+
3+
import time
4+
import sys
5+
6+
sleep_seconds = int(sys.argv[1])
7+
time.sleep(sleep_seconds)
8+
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
; REQUIRES: thread_support
2+
; RUN: llvm-reduce --process-poll-interval=1 -j 4 %s -o %t --delta-passes=instructions --test %python --test-arg %S/Inputs/sleep.py --test-arg 2
3+
; RUN: FileCheck %s < %t
4+
5+
; CHECK: define void @foo
6+
; CHECK-NEXT: ret void
7+
8+
define void @foo(ptr %ptr) {
9+
store i32 0, ptr %ptr
10+
store i32 1, ptr %ptr
11+
store i32 2, ptr %ptr
12+
store i32 3, ptr %ptr
13+
store i32 4, ptr %ptr
14+
store i32 5, ptr %ptr
15+
store i32 6, ptr %ptr
16+
store i32 7, ptr %ptr
17+
store i32 8, ptr %ptr
18+
store i32 9, ptr %ptr
19+
store i32 10, ptr %ptr
20+
store i32 11, ptr %ptr
21+
store i32 12, ptr %ptr
22+
store i32 13, ptr %ptr
23+
store i32 14, ptr %ptr
24+
store i32 15, ptr %ptr
25+
store i32 16, ptr %ptr
26+
store i32 17, ptr %ptr
27+
store i32 18, ptr %ptr
28+
store i32 19, ptr %ptr
29+
store i32 20, ptr %ptr
30+
store i32 21, ptr %ptr
31+
store i32 22, ptr %ptr
32+
store i32 23, ptr %ptr
33+
store i32 24, ptr %ptr
34+
store i32 25, ptr %ptr
35+
store i32 26, ptr %ptr
36+
store i32 27, ptr %ptr
37+
store i32 28, ptr %ptr
38+
store i32 29, ptr %ptr
39+
store i32 30, ptr %ptr
40+
store i32 31, ptr %ptr
41+
store i32 32, ptr %ptr
42+
store i32 33, ptr %ptr
43+
store i32 34, ptr %ptr
44+
store i32 35, ptr %ptr
45+
store i32 36, ptr %ptr
46+
store i32 37, ptr %ptr
47+
store i32 38, ptr %ptr
48+
store i32 39, ptr %ptr
49+
store i32 40, ptr %ptr
50+
ret void
51+
}
52+

llvm/tools/llvm-reduce/TestRunner.cpp

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818

1919
using namespace llvm;
2020

21+
extern cl::OptionCategory LLVMReduceOptions;
22+
static cl::opt<unsigned> PollInterval("process-poll-interval",
23+
cl::desc("child process wait polling"),
24+
cl::init(5), cl::Hidden,
25+
cl::cat(LLVMReduceOptions));
26+
2127
TestRunner::TestRunner(StringRef TestName,
2228
const std::vector<std::string> &TestArgs,
2329
std::unique_ptr<ReducerWorkItem> Program,
@@ -37,7 +43,7 @@ static constexpr std::array<std::optional<StringRef>, 3> NullRedirects;
3743

3844
/// Runs the interestingness test, passes file to be tested as first argument
3945
/// and other specified test arguments after that.
40-
int TestRunner::run(StringRef Filename) const {
46+
int TestRunner::run(StringRef Filename, const std::atomic<bool> &Killed) const {
4147
std::vector<StringRef> ProgramArgs;
4248
ProgramArgs.push_back(TestName);
4349

@@ -47,21 +53,39 @@ int TestRunner::run(StringRef Filename) const {
4753
ProgramArgs.push_back(Filename);
4854

4955
std::string ErrMsg;
56+
bool ExecutionFailed;
57+
sys::ProcessInfo PI =
58+
sys::ExecuteNoWait(TestName, ProgramArgs, /*Env=*/std::nullopt,
59+
Verbose ? DefaultRedirects : NullRedirects,
60+
/*MemoryLimit=*/0, &ErrMsg, &ExecutionFailed);
5061

51-
int Result =
52-
sys::ExecuteAndWait(TestName, ProgramArgs, /*Env=*/std::nullopt,
53-
Verbose ? DefaultRedirects : NullRedirects,
54-
/*SecondsToWait=*/0, /*MemoryLimit=*/0, &ErrMsg);
55-
56-
if (Result < 0) {
62+
if (ExecutionFailed) {
5763
Error E = make_error<StringError>("Error running interesting-ness test: " +
5864
ErrMsg,
5965
inconvertibleErrorCode());
6066
errs() << toString(std::move(E)) << '\n';
6167
exit(1);
6268
}
6369

64-
return !Result;
70+
// Poll every few seconds, taking a break to check if we should try to kill
71+
// the process. We're trying to early exit on long running parallel reductions
72+
// once we know they don't matter.
73+
std::optional<unsigned> SecondsToWait(PollInterval);
74+
bool Polling = true;
75+
sys::ProcessInfo WaitPI;
76+
77+
while (WaitPI.Pid == 0) { // Process has not changed state.
78+
WaitPI = sys::Wait(PI, SecondsToWait, &ErrMsg, nullptr, Polling);
79+
// TODO: This should probably be std::atomic_flag
80+
if (Killed) {
81+
// The current Program API does not have a way to directly kill, but we
82+
// can timeout after 0 seconds.
83+
SecondsToWait = 0;
84+
Polling = false;
85+
}
86+
}
87+
88+
return !WaitPI.ReturnCode;
6589
}
6690

6791
void TestRunner::setProgram(std::unique_ptr<ReducerWorkItem> P) {

llvm/tools/llvm-reduce/TestRunner.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class TestRunner {
3333

3434
/// Runs the interesting-ness test for the specified file
3535
/// @returns 0 if test was successful, 1 if otherwise
36-
int run(StringRef Filename) const;
36+
int run(StringRef Filename, const std::atomic<bool> &Killed) const;
3737

3838
/// Returns the most reduced version of the original testcase
3939
ReducerWorkItem &getProgram() const { return *Program; }

llvm/tools/llvm-reduce/deltas/Delta.cpp

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ void writeBitcode(ReducerWorkItem &M, raw_ostream &OutStream);
6565
void readBitcode(ReducerWorkItem &M, MemoryBufferRef Data, LLVMContext &Ctx,
6666
StringRef ToolName);
6767

68-
bool isReduced(ReducerWorkItem &M, const TestRunner &Test) {
68+
bool isReduced(ReducerWorkItem &M, const TestRunner &Test,
69+
const std::atomic<bool> &Killed) {
6970
const bool UseBitcode = Test.inputIsBitcode() || TmpFilesAsBitcode;
7071

7172
SmallString<128> CurrentFilepath;
@@ -96,7 +97,7 @@ bool isReduced(ReducerWorkItem &M, const TestRunner &Test) {
9697
}
9798

9899
// Current Chunks aren't interesting
99-
return Test.run(CurrentFilepath);
100+
return Test.run(CurrentFilepath, Killed);
100101
}
101102

102103
/// Splits Chunks in half and prints them.
@@ -138,7 +139,8 @@ CheckChunk(const Chunk &ChunkToCheckForUninterestingness,
138139
std::unique_ptr<ReducerWorkItem> Clone, const TestRunner &Test,
139140
ReductionFunc ExtractChunksFromModule,
140141
const DenseSet<Chunk> &UninterestingChunks,
141-
const std::vector<Chunk> &ChunksStillConsideredInteresting) {
142+
const std::vector<Chunk> &ChunksStillConsideredInteresting,
143+
const std::atomic<bool> &Killed) {
142144
// Take all of ChunksStillConsideredInteresting chunks, except those we've
143145
// already deemed uninteresting (UninterestingChunks) but didn't remove
144146
// from ChunksStillConsideredInteresting yet, and additionally ignore
@@ -178,7 +180,7 @@ CheckChunk(const Chunk &ChunkToCheckForUninterestingness,
178180
errs() << "\n";
179181
}
180182

181-
if (!isReduced(*Clone, Test)) {
183+
if (!isReduced(*Clone, Test, Killed)) {
182184
// Program became non-reduced, so this chunk appears to be interesting.
183185
if (Verbose)
184186
errs() << "\n";
@@ -191,7 +193,8 @@ static SmallString<0> ProcessChunkFromSerializedBitcode(
191193
Chunk &ChunkToCheckForUninterestingness, TestRunner &Test,
192194
ReductionFunc ExtractChunksFromModule, DenseSet<Chunk> &UninterestingChunks,
193195
std::vector<Chunk> &ChunksStillConsideredInteresting,
194-
SmallString<0> &OriginalBC, std::atomic<bool> &AnyReduced) {
196+
SmallString<0> &OriginalBC, std::atomic<bool> &AnyReduced,
197+
const std::atomic<bool> &Killed) {
195198
LLVMContext Ctx;
196199
auto CloneMMM = std::make_unique<ReducerWorkItem>();
197200
MemoryBufferRef Data(StringRef(OriginalBC), "<bc file>");
@@ -201,7 +204,7 @@ static SmallString<0> ProcessChunkFromSerializedBitcode(
201204
if (std::unique_ptr<ReducerWorkItem> ChunkResult =
202205
CheckChunk(ChunkToCheckForUninterestingness, std::move(CloneMMM),
203206
Test, ExtractChunksFromModule, UninterestingChunks,
204-
ChunksStillConsideredInteresting)) {
207+
ChunksStillConsideredInteresting, Killed)) {
205208
raw_svector_ostream BCOS(Result);
206209
writeBitcode(*ChunkResult, BCOS);
207210
// Communicate that the task reduced a chunk.
@@ -242,7 +245,7 @@ void llvm::runDeltaPass(TestRunner &Test, ReductionFunc ExtractChunksFromModule,
242245

243246
assert(!verifyReducerWorkItem(Test.getProgram(), &errs()) &&
244247
"input module is broken after counting chunks");
245-
assert(isReduced(Test.getProgram(), Test) &&
248+
assert(isReduced(Test.getProgram(), Test, std::atomic<bool>()) &&
246249
"input module no longer interesting after counting chunks");
247250

248251
#ifndef NDEBUG
@@ -290,6 +293,11 @@ void llvm::runDeltaPass(TestRunner &Test, ReductionFunc ExtractChunksFromModule,
290293
writeBitcode(Test.getProgram(), BCOS);
291294
}
292295

296+
// If doing parallel reduction, signal to running workitem threads that we
297+
// no longer care about their results. They should try to kill the reducer
298+
// workitem process and exit.
299+
std::atomic<bool> Killed = false;
300+
293301
SharedTaskQueue TaskQueue;
294302
for (auto I = ChunksStillConsideredInteresting.rbegin(),
295303
E = ChunksStillConsideredInteresting.rend();
@@ -316,11 +324,12 @@ void llvm::runDeltaPass(TestRunner &Test, ReductionFunc ExtractChunksFromModule,
316324
for (unsigned J = 0; J < NumInitialTasks; ++J) {
317325
TaskQueue.emplace_back(ChunkThreadPool.async(
318326
[J, I, &Test, &ExtractChunksFromModule, &UninterestingChunks,
319-
&ChunksStillConsideredInteresting, &OriginalBC, &AnyReduced]() {
327+
&ChunksStillConsideredInteresting, &OriginalBC, &AnyReduced,
328+
&Killed]() {
320329
return ProcessChunkFromSerializedBitcode(
321330
*(I + J), Test, ExtractChunksFromModule,
322331
UninterestingChunks, ChunksStillConsideredInteresting,
323-
OriginalBC, AnyReduced);
332+
OriginalBC, AnyReduced, Killed);
324333
}));
325334
}
326335

@@ -344,11 +353,11 @@ void llvm::runDeltaPass(TestRunner &Test, ReductionFunc ExtractChunksFromModule,
344353
TaskQueue.emplace_back(ChunkThreadPool.async(
345354
[&Test, &ExtractChunksFromModule, &UninterestingChunks,
346355
&ChunksStillConsideredInteresting, &OriginalBC,
347-
&ChunkToCheck, &AnyReduced]() {
356+
&ChunkToCheck, &AnyReduced, &Killed]() {
348357
return ProcessChunkFromSerializedBitcode(
349358
ChunkToCheck, Test, ExtractChunksFromModule,
350359
UninterestingChunks, ChunksStillConsideredInteresting,
351-
OriginalBC, AnyReduced);
360+
OriginalBC, AnyReduced, Killed);
352361
}));
353362
}
354363
continue;
@@ -361,6 +370,8 @@ void llvm::runDeltaPass(TestRunner &Test, ReductionFunc ExtractChunksFromModule,
361370
break;
362371
}
363372

373+
Killed = true;
374+
364375
// If we broke out of the loop, we still need to wait for everything to
365376
// avoid race access to the chunk set.
366377
//
@@ -375,7 +386,7 @@ void llvm::runDeltaPass(TestRunner &Test, ReductionFunc ExtractChunksFromModule,
375386
*I,
376387
cloneReducerWorkItem(Test.getProgram(), Test.getTargetMachine()),
377388
Test, ExtractChunksFromModule, UninterestingChunks,
378-
ChunksStillConsideredInteresting);
389+
ChunksStillConsideredInteresting, Killed);
379390
}
380391

381392
if (!Result)

llvm/tools/llvm-reduce/llvm-reduce.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ static cl::opt<int>
101101

102102
static codegen::RegisterCodeGenFlags CGF;
103103

104-
bool isReduced(ReducerWorkItem &M, const TestRunner &Test);
104+
bool isReduced(ReducerWorkItem &M, const TestRunner &Test,
105+
const std::atomic<bool> &Killed);
105106

106107
/// Turn off crash debugging features
107108
///
@@ -217,7 +218,7 @@ int main(int Argc, char **Argv) {
217218
// test, rather than evaluating the source IR directly. This is for the
218219
// convenience of lit tests; the stripped out comments may have broken the
219220
// interestingness checks.
220-
if (!isReduced(Tester.getProgram(), Tester)) {
221+
if (!isReduced(Tester.getProgram(), Tester, std::atomic<bool>())) {
221222
errs() << "\nInput isn't interesting! Verify interesting-ness test\n";
222223
return 1;
223224
}

0 commit comments

Comments
 (0)