Support for parallel target generation#863
Support for parallel target generation#863asifmohd wants to merge 15 commits intoyonaskolb:masterfrom
Conversation
There was a problem hiding this comment.
This is the part where I am blocking the main queue, so that the generateCommand gets time to generate all the targets, write the xcodeproj and then finish
There was a problem hiding this comment.
I am not sure about this assumption, please help review, we can probably get rid of this if check as well.
But this helps me parallelise a few targets which just depend on an existing Xcode project reference
There was a problem hiding this comment.
This while loops helps us parallelize more and more targets in each loop, starting with targets, which have no dependencies at all
There was a problem hiding this comment.
generatedTargetNames.contains(dependency.reference), this is how we see if a target is ready to be generated
By checking each dependency of that target, and checking if it's already been generated, if so, then the target is ready to be generated, otherwise it stays in the pendingTargets array
There was a problem hiding this comment.
Because I couldn't find a way to make the DispatchQueue.global triggers follow a try pattern, I had to fall back to this, where i keep a track of all errors and then throw them once the completion block gets called
b94ee28 to
97b8eea
Compare
There was a problem hiding this comment.
Moved to Target.swift as an extension on Target
|
We have a project.yml file that has 276 targets, where 275 depend on a single other target. This change resulted in no speed up or slow down for us 😕. |
I should be able to do something about this, basically I'll have to modify the parallelization logic to sort by the number of dependencies a target has, and then pick the one with the least dependencies and so on, so that more and more targets can be parallelized in each cycle. Right now I just pick the last target from the array, which is probably why you are not seeing an improvement. Will try to make this change this coming weekend 🙂 |
|
@keith, would you mind testing this on the Lyft codebase? |
@brentleyjones with my most recent commit, ideally the dependency resolution logic should pick up the target with no dependencies, then targets with just 1 dependencies, and so on. |
|
There is a lot of scope in improving the dependency resolution of targets, like we could probably build functionality, which would parse all the targets, and find the most commonly depended target, build that and it's dependencies first, so that rest of the targets can be parallelized. Kinda like our own build system for generating targets. |
|
@asifmohd It's now about 10% slower than master for us. And when we make it so we instead have 275 independent targets we get into a deadlock and it never returns. |
|
I tested this and also hit the deadlock, here's the main thread in that case: |
3180eae to
48d2153
Compare
|
Thanks for testing this out everyone 🙏 |
48d2153 to
263c551
Compare
ac7201c to
5370e9a
Compare
to unblock the thread calling this function, which is generateXcodeProject This was more evident in unit tests, without this the performanceTests would end up being blocked
The alternative ie passing the completion handlers forward would have required too many changes in all uses of these helpers, around 80 when i checked
for atomic operations on global variables Not using this causes various SIGABRT signals to be triggered because of multiple threads accessing and mutating the same global variable
and use the same helper function, to split targets into two arrays one with no dependencies and one with dependencies
so that successive calls can reuse the cached data from a previous job Without this, because of the parallelization, duplicate groups/file references get added to the pbxproj
ie keep checking in a while loop, to see if there are targets which can now be generated in a parallel manner, once the previous cycle has lead to building a few targets There is a fallback check to see if there are no targets to be generated in this cycle, then just remove the last target from the pending target list and process that target So in the worst case, the while loop will fallback to generating targets sequentially and in the best cases, atleast >2 targets will be generated parallely in the current while loop
We are now generating each target in parallel as much as possible We are not reusing the generateTargetGroups dispatchGroup for target generation instead we are creating a new dispatch group to isolate the functionality related to generating targets in parallel.
5370e9a to
3258e01
Compare
Background
Glob.exploreDirectoriessection.Overview of changes to code in this PR
getSourceMatchesin SourceGenerator.swift, be able to run asynchronously in multiple threads for each targetGCDto dispatch to global queues, with QOS asuserInitiated, along with heavy use ofDispatchGroups, to block queue execution when successive code depend on a result from an async completion handler being executed.Writing projectphase. Instead of blocking the main queue, we could probably show progress to the user on how many targets have been generated in a separate PR.GenerateCommand.swiftis a synchronous function, if we return early, the xcodegen command exits as well, so we have to make the main queue wait so that the command thinks, that processing is still going on.rootGroupsin SourceGenerator.swift and more, be thread safe, ie reading and writing happens using a serial queue.getAllDependenciesPlusTransitiveNeedingEmbeddingfrom SourceGenerator.swift toTarget.swift, so that the code in SourceGenerator.swift reduces a bit, and to be able to reuse this function to figure out the order of generating targets, by reusing and calling this function on each target itself, ex:target.getAllDependencies(usingProject:)shouldEmbedDependenciesas well and marking it as a public functionOverview of changes related to tests in this PR
generateXcodeProjectinPBXProjGeneratorTests.swifthave been modified to convert the completion handler based functions to be serial functions, which return a single value instead of accepting and calling the completion handlersResults