I haven’t been following this thread closely so this may not be relevant, but I just wanted to point out that we have the start of a spec for process management. Might be some overlap.
https://github.com/gradle/gradle/blob/master/design-docs/process-management.md On 6 July 2014 at 6:50:15 am, Daz DeBoer (darrell.deb...@gradleware.com) wrote: I'm pretty sure we don't want to expose a raw ExecutorService. We are pretty careful about exposing Gradle APIs that we have control over, rather than APIs that we don't have control over. One of the benefits of providing a specific API for parallel external processes is that Gradle could automatically buffer the input/output, provide progress reporting, etc. I haven't put a lot of thought into what our Gradle-wide parallel mechanism will look like, but if we start with an AsyncExecAction (or similar) we can later change AsyncExecAction to wrap the new API. If you want to create a completely separate service for this (instead of extending ExecActionFactory) then that would be fine, too. Daz On Sat, Jul 5, 2014 at 1:20 PM, Daniel Lacasse <daniel.lacass...@gmail.com> wrote: I was thinking about this Daz. Would it be more flexible, extensible and powerful if we would just inject a singleton whenever someone request it of a ExecutorService that is configured according to the parallel-threads count? Creating an AsyncExecAction is more code to maintain and support as opposed to letting the developers choose to just wrap an normal ExecAction into a Callable and submitting it to the ExecutorService. The pro is we get closer to option #2 and #3 for a Gradle-wide parallel mechanism. The con is do we really want to expose the actual ExecutorService? I realise that we should explore this a bit more. What are your thoughts on this? --- Daniel On Thursday, July 3, 2014 11:09 PM, Daz DeBoer <darrell.deb...@gradleware.com> wrote: On Wed, Jul 2, 2014 at 7:36 PM, Daniel Lacasse <daniel.lacass...@gmail.com> wrote: Sounds like a better plan as it would add more building blocks to gradle. Correct me if I'm wrong. They would probably be a global ExecutorService to avoid recreating it at ever task and share the threads between everyone who use it. Yes. ExecActionFactory is currently implemented by DefaultFileOperations, which is created per-project. So if you just make the change there you could have an ExecutorService per project. We could further improve this if we like by adding something to BuildScopeServices that is shared by all project instances. For now, I'd keep the implementation of ExecActionFactory.createAsyncAction nice and simple, because Adam is sure to have some ideas on how this could be better structured, when he's back from holidays. The problem I see - which could be a temporary thing until option #3 is implemented - is parallel-threads will be the number of parallel task executed at the same time as the number of compiler fork. For someone using the parallel flags, it may be non-intuitive. It basically boils down to how do we share the parallel-threads number between parallel task execution and number of compiler fork? In my mind parallel-threads is the maximum number of threads that Gradle should be allowed to create for his parallel business. Yes, we'd be overloading the 'parallel-threads' setting, so that you'd have both N projects being built in parallel, as well as N files being compiled in parallel. But the end goal is to use a single setting to control all parallelism, and this is a step in that direction. Thanks again Daz and I will move in the direction you are suggesting. --- Daniel On Wednesday, July 2, 2014 09:34 AM, Daz DeBoer < darrell.deb...@gradleware.com> wrote: On Mon, Jun 30, 2014 at 7:24 PM, Daniel Lacasse <daniel.lacass...@gmail.com> wrote: I did some research on how to implement option #1 and here is the implementation: In org.gradle.nativebinaries.language.c.tasks.AbstractNativeCompilerTask: Add option int maximumNumberOfCompilerFork = 1 1 is to prevent any side effect. It will mainly behave just like it was previously. I'm open to suggestion on the chosen name. No @Input on the new option In org.gradle.nativebinaries.toolchain.internal.NativeCompileSpec: set/getMaximumNumberOfCompilerFork In org.gradle.nativebinaries.language.internal.AbstractNativeCompilerSpec: set/getMaximumNumberOfCompilerFork implementation In org.gradle.nativebinaries.toolchain.internal.{gcc|msvcpp}.NativeCompilerSpec: Add modification to method WorkResult execute(T spec) Use a ThreadPoolExecutor with maximumPoolSize set to maximumNumberOfCompileFork and corePoolSize set to 1 Submit anonymous Callable<WorkResult> class to the ThreadPoolExecutor Use the get method on each Future returned by Submit to join all the work together. This cover pretty much the option #1 for parallel compilation in the native extension. The 2 open issues would be: Is maximumNumberOfCompilerFork a good choice for the option name? If not, what would be the appropriate name? Let's avoid adding a configuration option for this at this stage. What we really want is to make this configurable at a much higher level, and auto-detect the optimal setting. For now, let's use the value of 'parallel-threads' for this setting. Even though this is a bit coarse grained, it takes us in the right direction. So instead of passing the value from Task->CompileSpec->NativeCompiler, we would access the value from StartParameter#getParallelThreadCount. I think the easiest way to do this would be to add a method to ExecActionFactory that provided an AsyncExecAction, which would be similar to ExecAction, but something like: public interface AsyncExecAction extends ExecSpec { Future<ExecResult> submit(); } You could then modify org.gradle.nativebinaries.toolchain.internal.CommandLineTool to use async actions, and to present some sort of submit/collect API. This would mean that the parallel functionality would be available everywhere that CommandLineTool is used, which means both VisualCpp and Gcc based tool chains. How does this sound? While it may seem a bit more involved, the design will be largely the same. You'll still need a ThreadPoolExecuter and code to submit/collect, but this design takes your suggested solution and moves most of the code out of the NativeCompiler implementations into the CommandLineTool and ExecActionFactory. The nice thing about this is that it makes this feature more generally available, and makes is simpler for us to have a global setting for parallelisation. (The funny thing is that this is looking more like Option #2!) So native compilation would happen in parallel whenever --parallel and/or --parallel-threads is used. As Adam suggested, we should really separate these 2 options so that --parallel is a separate option to enable parallel project execution, while parallel-threads is a general purpose setting that defaults to a reasonable value. (Currently StartParameter#getParallelThreadCount defaults to zero if neither --parallel or --parallel-threads is specified). How can we test this new feature? The only way I can think of right now is to compile 2 file with maximumNumberOfCompileFork set to 1 and a second time with the option set to 2 and compare the execution time. This heavily depend on timing which will probably be non deterministic. No, timing-sensitive tests are not the way to go. We could certainly unit test the AsyncExecAction stuff, and also integration test with a few 'sleep 1 second' actions and ensure that they don't take much more than 1 second in total. I guess we could integration test parallel native compilation by using a 'sleep exe' in place of the actual compiler... I'll think about this a little more. Thanks for pushing to get this done. You're right that we really need parallel compilation to be a serious C++ build tool. Daz On Sun, Jun 29, 2014 at 11:02 PM, Adam Murdoch <adam.murd...@gradleware.com> wrote: On 30 Jun 2014, at 12:21 pm, Daniel Lacasse < daniel.lacass...@gmail.com> wrote: Thanks Adam for your insight. I agree that option #2 would probably be a really good start. Unfortunately, for someone who is pretty new to the code base, I would rather start with #1 by exposing n on the compile task, set it to one by default and have the logic there to fork compiler tag. I would stress that it's a temporary fix and it will be stream line later with #2 and #3. It would also be much easier for me to contribute #1 in a reasonable time, say for 2.1, and take more time to implement a full solution. How does that sound? Sounds good to me. If you strongly prefer #2 as a start point, could you point me to a couple place where changes should be applied so I start looking into it? On Sun, Jun 29, 2014 at 9:25 PM, Adam Murdoch <adam.murd...@gradleware.com> wrote: On 30 Jun 2014, at 1:51 am, Daniel Lacasse < daniel.lacass...@gmail.com> wrote: I have been using the native extension for Gradle inside an actual project for a couple months. The biggest limitation I'm seeing is the inflexibility of the compile task when compared to other build tool in the industry. The --parallel switch works great for concurrent execution of tasks. When it comes to the compile task, all files are compiled one after the other. The native compiler are quite slow especially when it comes to compiling C++ templates. As a comparison, my dev box is mostly idle while compiling with Gradle as oppose to a fairly important load when compiling Visual Studio. The main use case of this feature is the speed up of the compilation process for the native extension. To highlight how this feature is important, I will point out that some company where I previously worked at use system like Incredibuild to perform parallel distributed compilation. Even with such system, the compilation was still pretty time consuming. In it's present form, Gradle is not suitable in term of speed for those scenario. I talked to a couple Gradleware engineers during Gradle Summit 2014 and some insane features are planed to address this problem. Unfortunately, a quicker solution is needed in order to speed up the adoption of Gradle as a native build tool. I also want this feature to be in accordance to the long term Gradle road map. I would like to start the discussion for contributing this into Gradle. That would be great. From my limiting knowledge of Gradle here are a couple open issues I have. I hope some brilliant minds from Gradleware can share there wisdom on where to move forward with this feature. Open issues - What is the current road map for such feature. I think there are 3 potential steps we could take: 1. The compilation tasks do something specific, where they fork n concurrent compilations, and n is just some setting on the compilation tasks. 2. Then, we introduce some general service that tasks can use for coordinating concurrent work. This would be integrated with —parallel-threads. The compilation tasks, the test tasks, and the task executor would all use this service to ensure that an appropriate amount of concurrent work happens at any given time. This would be a public service that any task implementation could use. 3. Then later, we add more capabilities to this so that the work can treated more like tasks - with their own up-to-date checks, dependencies, and so on. We could start with #1 and later extract #2, or we could jump straight to #2. In some ways, it might be nice to start with #2. - This could also be used in any language which require compilation such as Java. - Allowing custom implementation of this feature could allow a company to plug Gradle in there current Incredibuild infrastructure or any other distributed framework. - How this feature fits with the --parallel flag? For every build, there should be two settings that you can tweak: 1. The maximum amount of parallel work that can be performed by Gradle. 2. Whether or not tasks should be executed in parallel. That is, Gradle will be able to do stuff in parallel even if the tasks aren’t executed concurrently. It already does this with test execution. I would change —parallel-threads to control the maximum work but to not enable parallel task execution. It would default to some ‘reasonable’ value - the number of cores, say, at least to start with. - How the number of parallel compilation unit will be configure aka number of files that can be compiled in parallel? As above. - Should this feature be always on by default or have a toggle flag? Always on, I think. Why would you turn it off? -- Adam Murdoch Gradle Co-founder http://www.gradle.org CTO Gradleware Inc. - Gradle Training, Support, Consulting http://www.gradleware.com -- Daniel -- Adam Murdoch Gradle Co-founder http://www.gradle.org CTO Gradleware Inc. - Gradle Training, Support, Consulting http://www.gradleware.com -- Daniel -- Darrell (Daz) DeBoer Principal Software Engineer, Gradleware Join us for Gradle Summit 2014, June 12th and 13th in Santa Clara, CA: http://www.gradlesummit.com -- Darrell (Daz) DeBoer Principal Software Engineer, Gradleware Join us for Gradle Summit 2014, June 12th and 13th in Santa Clara, CA: http://www.gradlesummit.com -- Darrell (Daz) DeBoer Principal Software Engineer, Gradleware Join us for Gradle Summit 2014, June 12th and 13th in Santa Clara, CA: http://www.gradlesummit.com — Luke Daley http://www.gradleware.com