Skip to content

Batch mode driver work (almost usable?) #14525

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Feb 16, 2018

Conversation

graydon
Copy link
Contributor

@graydon graydon commented Feb 10, 2018

This includes the remaining batch mode driver components from the prototype cleaned up to the point where I think they're credible to land.

It's very lightly fiddled-with; I wouldn't even say tested yet. But it's also disabled by default in its disabled state, appears not to perturb tests (at least on linux -- checking macos now). I just wanted to get all this in place so David has something he can play with (since it appears I might get pulled off again to do other things for another few days next week).

It's also missing, of course, support for writing OFMs to frontends, or randomizing batch partition, or probably a dozen forms of failure management I haven't covered yet.

@graydon
Copy link
Contributor Author

graydon commented Feb 10, 2018

@swift-ci please test

assert(&DerivedOutputMap == &other.DerivedOutputMap);
Inputs.append(other.Inputs.begin(),
other.Inputs.end());
types::forAllTypes([&](types::ID Type) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SmallSet has an iterator-based insert which will be equivalent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, as a sink; but it doesn't have an accessor to give you iterator(s) as a source!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, bah! Okay then.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That said, if you don't care about adding to an existing map, you could just do a copy-assignment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case we're doing a merge, the moral equivalent of +=, so I do care about the existing entries (if I'm understanding your comment correctly?)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that the operation is written as +=, but at the only place you use it it'll be empty or identical anyway, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, fair point. I'll modify it to assert that.

Copy link
Contributor

@jrose-apple jrose-apple left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not as intrusive as I feared! Some comments so far, but mostly this looks like it will work, while not getting in the way of non-batch mode.

@@ -166,6 +166,27 @@ class ToolChain {
std::unique_ptr<CommandOutput> output,
const OutputInfo &OI) const;

/// Return true iff the input \c Job \p A is an acceptable candidate for
/// batching together into a BatchJob, via a call to \c constructBathJob. This
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very relaxing typo: constructBathJob.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hah, fixed.

@@ -150,6 +151,150 @@ types::ID ToolChain::lookupTypeForExtension(StringRef Ext) const {
return types::lookupTypeForExtension(Ext);
}

// Return a _single_ TY_Swift InputAction, if one exists;
// if 0 or >1 such inputs exist, return nullptr.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doc comment?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure I guess? (do static functions in implementation files get docs extracted to anywhere useful?)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code completion and Quick Help will pick them up in Xcode.

return false;

// Check Jobs have same executable.
if (strcmp(A->getExecutable(), B->getExecutable()) != 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think CompileJobActions will ever be allowed to pick different executables, so maybe this can just be an assertion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

k, fixed.

/// This is true when each job independently satisfies \c jobIsBatchable, and the
/// jobs have identical executables, output types and environments (i.e. they are
/// identical aside from their inputs).
bool jobsAreBatchCombinable(const Job *A, const Job *B) const;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense to require that this be an equivalence relation, so as not to (supposedly) need quadratic checking at the use site.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, fair; I think it technically is one already, I was just describing it as a pairwise relation out of paranoia. I'll change the doc comment to reflect the reality.

if (strcmp(A->getExecutable(), B->getExecutable()) != 0)
return false;

// Check Jobs are making the same kind of output.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arguably they have to be making the same supplementary outputs too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, fixed.

if (Comp.ShowJobLifecycle)
llvm::outs() << "Batch job finished: "
<< LogJob(FinishedCmd) << "\n";
auto const *B = static_cast<const BatchJob *>(FinishedCmd);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realized Job needs a virtual destructor if BatchJob is going to contain a SmallVector.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops, fixed!

@@ -410,6 +449,7 @@ namespace driver {
SmallVector<const Job *, 16> Dependents;
if (Comp.getIncrementalBuildEnabled()) {
reloadAndRemarkDeps(FinishedCmd, ReturnCode, Dependents);
formBatchJobsAndAddPendingJobsToTaskQueue();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to go figure out how reloadAndRemarkDeps could schedule new jobs. Maybe there should be a comment around that. (My fault, not yours.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixing. Though .. there's something a little hinky here that I was kinda papering over and I'm still uncomfortable with: we should pick up the extra pending jobs and requeue them in the outer loop (runTaskQueueToCompletion); I'm going to take another look and see if I can figure out why were weren't (there's a single test that fails without one of these seemingly-redundant formBatchJobsAndAddPendingJobsToTaskQueue calls).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh gosh, ok, found the underlying issue here and it's both quite subtle and would have been pretty serious if I'd let it go.

(I put a couple comments on the reload path anyways, but it's not the culprit)

// with a CompileJobAction and JobContext that differ only slightly.

if (jobs.size() == 0)
return nullptr;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's 1, worth early-exiting with the single Job?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I don't think so; but I do think we should guard against getting to this path with any number of jobs < 2. I'll do a little refactoring in the caller and put an assert in here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eh actually this just moves ugliness in the logic elsewhere, I'm going to leave it as-is.

@graydon
Copy link
Contributor Author

graydon commented Feb 15, 2018

@swift-ci please test

@graydon
Copy link
Contributor Author

graydon commented Feb 15, 2018

@jrose-apple pushed update with review commentary addressed (I think!), with particular emphasis on 91aa7e4 which was actually quite a serious problem -- those seemingly-redundant calls to formBatchJobsAndAddPendingJobsToTaskQueue were not-very-thoroughly compensating for PendingExecution jobs being dropped on the floor in the outer loop -- but I believe I have set it right now. Added a bunch of commentary; let me know if it's not clear.

(The remaining changes are mostly tidying, commenting and splitting functions up as sensibly requested by @davidungar; again, I don't really know that the batching code works at the moment since there isn't frontend code quite ready to connect it to, and it's disabled by default anyways; but it seems about as logically reasonable as it was in its previous iteration which we did manage to test in prototype form, and it at least compiles?)

@swift-ci
Copy link
Contributor

Build failed
Swift Test OS X Platform
Git Sha - c93497e4677a681362c04b5b808616a6d5324c04

@@ -17,6 +17,7 @@
#ifndef SWIFT_DRIVER_COMPILATION_H
#define SWIFT_DRIVER_COMPILATION_H

#include "swift/Driver/Driver.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: this include seems to be unnecessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's needed to get the complete type for driver::OutputInfo.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I didn't realize that didn't have its own header. Carry on.

auto const &TC = Comp.getToolChain();
if (TC.jobsAreBatchCombinable(Comp, Cmd, CurrentBatch[0]))
return true;
return CurrentBatch.size() < TargetBatchSize;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like these conditions are backwards: you're doing an OR rather than an AND check here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good catch.

@@ -181,6 +193,8 @@ CommandOutput::dump() const {
llvm::errs() << '\n';
}

Job::~Job() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: You can write this as = default for the same effect.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, fixed.

ToolChain::jobsAreBatchCombinable(const Compilation &C,
const Job *A, const Job *B) const {
return (jobIsBatchable(C, A) &&
jobIsBatchable(C, B) &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid redundant checking, you could assert these conditions instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, fixed.

assert(&DerivedOutputMap == &other.DerivedOutputMap);
Inputs.append(other.Inputs.begin(),
other.Inputs.end());
types::forAllTypes([&](types::ID Type) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That said, if you don't care about adding to an existing map, you could just do a copy-assignment.

@graydon graydon force-pushed the batch-mode-driver-work branch from c93497e to 394c5a9 Compare February 15, 2018 23:19
@graydon graydon force-pushed the batch-mode-driver-work branch from 394c5a9 to 7686a5a Compare February 15, 2018 23:39
@graydon
Copy link
Contributor Author

graydon commented Feb 15, 2018

@swift-ci please test

@swift-ci
Copy link
Contributor

Build failed
Swift Test OS X Platform
Git Sha - c93497e4677a681362c04b5b808616a6d5324c04

@graydon
Copy link
Contributor Author

graydon commented Feb 15, 2018

@jrose-apple fixed issues discussed so far, squashed and rebased.

@swift-ci
Copy link
Contributor

Build failed
Swift Test Linux Platform
Git Sha - c93497e4677a681362c04b5b808616a6d5324c04

// there are, we need to continue trying to make progress on the
// TaskQueue before we start marking deferred jobs as skipped, below.
if (!PendingExecution.empty() && Result == 0) {
formBatchJobsAndAddPendingJobsToTaskQueue();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose if this still bugs you another option is to put it at the top of the loop.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before the call to TaskQueue::execute? I .. am pretty sure that wouldn't work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(It doesn't bug me enough to carry on fiddling it further; I think there are about 2 variants that work right and they're similarly confusing. One has two calls, the other needs more explanation, shrug)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it would also require changing the loop's controlling condition. This is fine.

@graydon graydon merged commit 15e8441 into swiftlang:master Feb 16, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants