-
Notifications
You must be signed in to change notification settings - Fork 381
Description
Presently very little of GWT's compilation can be parallelized at all - the AST is a giant global structure that is modified through optimizations/etc one step at a time. Once "precompilation" is complete and permutations are to be built, the structure is cloned for each worker, so those can proceed in parallel.
There may be some opportunities for splitting up work across threads where we know that changes will only be made to "local" sections of the graph and not have a wider impact and break or cause nondeterminism in other operations.
First, the JDT org.eclipse.jdt.internal.compiler.Compiler has a useSingleThread
that defaults to true. This is turned to false automatically (unless explicitly disabled) when JDT is run as a Java main, or through the batch/incremental builders that JDT offers as part of its API. It seems likely we could exploring turning that off.
Next, it might be possible to change some of the optimization/normalization passes to run concurrently. For a single pass, individual methods must only be processed once at a time, but as long as a given pass doesn't modify globally available data, there should be no races from this kind of change. It could even be possible to allow multiple passes to run concurrently with each other, as long as they only read globally, and are running serially within a given method.
An approach for this could start with offering a JChangeTrackingVisitor
subtype (or wrapper) that guards against any changes to method signatures or other global changes, and only performs local expression/statement changes. Iterating through methods or types then could spread work to a common set of threads (default ForkJoinPool?). Then, optimizations that should be able to support these limitations could be updated to support this, and the entire optimization pass would block on all work being completed (again, possibly a feature of the subclass/wrapper).
A quick survey of passes and how they would handle this:
- DeadCodeElimination, MethodCallTightener, Finalizer, MethodCallSpecializer, and SameParameterValueOptimizer appear to be safe to be parallelized in this way
- TypeTightener, MakeCallsStatic, Pruner appear to all modify global state, so would be unsafe to run on more than one ast node at a time
- MethodInliner is probably unsafe as written, since the contents of other methods are read - guarantees would need to be made that no method's contents can be read while it is itself being processed.
- EnumOrdinalizer technically appears to replace the declaration of fields, but this might not actually impact what readers of those fields see. Other, unrelated methods are also rewritten (usages of a given enum literal rewritten to just be an ordinal), so this could be unsafe to perform while other passes are running.
The level of concurrency to use must be guided by how many workers are running already vs what level of concurrency was configured by the user. All of the above passes occur in the "optimization loop" of permutation compilation - if a project has 10 permutations and 5 workers configured, it likely does not want any additional concurrency added. On the other hand, if there are only 2 permutations (presently the default for production, assuming a single locale), it might make sense to split work up to run concurrently.
Precompilation may also benefit from this idea, but those passes are only run once, so likely would see less of an impact.
For now, this is just a research topic - it may end up that making these changes introduces other indirection that could slow down single-threaded compilation (which is to say compiling many permutations each in its own worker), so care must be taken to not impact existing use cases.