-
Notifications
You must be signed in to change notification settings - Fork 704
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
Parallelise cabal build over modules #976
Comments
This will be a huge win if it can make effective use of all cores. I've had quite a few multi-minute builds of individual packages, where the newly added per-package parallelism only helps with dependencies during the very first build, but not at all during ongoing development. |
@bos The main obstacle here is reloading of interface files, which slows down the parallel compilation considerably compared to |
An e-mail from @dcoutts that describes the "compile server" idea in more detail:
|
Even though using |
@tibbe, I thought the point was that |
@bos I've heard the number 2 tossed around as well, but we should test and see. Doing parallelism at the module level should also expose many more opportunities for parallelism. The current parallel build system suffers quite a bit from lack of that (since there are lots of linear chains of package dependencies.) |
What about profiling builds? Due to the structure of the compilations (exactly the same things as in a normal compilaiton are built), I'd guess might easily be run in parallel, and we might get almost ~x2 time saved. |
@nh2 Parallel |
I am currently working on this. I got good results with ghc-parmake for compiling large libraries and am now making executables build in parallel. |
@nh2 Cool! BTW, I proposed this as a GSoC project for this summer. Maybe we can work together if my project gets accepted? |
I'm interested in the details. How large was the speedup? On how many cores? In my testing, the difference was negligible. |
The project I'm working on has a library with ~400 modules and 40 executables. I'm using an i7-2600K with 4 real (8 virtual) cores. For building the library only, I get:
I had to make minimal changes to ghc-parmake to get this to work, and thus got a 2x speedup almost for free :) As you can see, the speed-up is not as big as we can probably expect from Building the executables in parallel is independent from all this and will also probably be a small change. |
Nice to hear that it can give a noticeable speedup on large projects. I should try testing it some more.
Maybe if you don't integrate |
@23Skidoo I made a prototype at https://github.com/nh2/cabal/compare/build-executables-in-parallel. It would be nice if you could take a look.
Feedback appreciated. |
I have updated my branch to fix some minor bugs in my code. I can now build my project with The questions above still remain. |
@nh2 Thanks, I'll take look. |
Can't you just export them from
Yes, this doesn't work because of bootstrapping. You can do this, however:
Or maybe we should add a |
Good idea, but when we do the
If that would be enough to find out the version of |
I have another idea - since Cabal only supports building on GHC nowadays, you can use
|
We can make |
Nice, pushed that. |
Just rebased that. |
My GSoC 2013 project proposal has been accepted. |
Awesome! Let's give this build system another integer factor speedup! :) |
Do you mean with this: When we use |
Yes. The plan is to use an OS-level semaphore for this, as outlined above. |
Is the speed at which GHC can read interface files a bottleneck? How hard might it be to fix that? |
@treeowl I'm not sure if that is known. I've advertised in some other place that GHC, for the various parts of its build pipeline, should record CPU and wall time and be able to produce a report (e.g. "N seconds CPU/wall were spent on reading and decoding interface files). That way we could more easily pinpoint where bottlenecks are. Right now GHC does things time counting and reporting only for optimiser phases, not for any of the "more basic tech" bits. |
Hi All, I've written a prototype for a ghc feature to limit its parallelism with a semaphore, GNU make jobserver style. The idea being that cabal-install would pass https://gitlab.haskell.org/ghc/ghc/-/merge_requests/5176 I'd appreciate any comment on
to make this easy and useful to integrate into cabal |
Move the `keysQueue` implementation out of `Data.PQueue.Internals`. This allows that module to build in parallel with `Data.PQueue.Prio.Internals`. It looks like [Cabal can't yet make use of this](haskell/cabal#976), but work to make it do so is under way.
Move the `keysQueue` implementation out of `Data.PQueue.Internals`. This allows that module to build in parallel with `Data.PQueue.Prio.Internals`. It looks like [Cabal can't yet make use of this](haskell/cabal#976), but work to make it do so is under way.
fwiw, I was able to fix that for local development in our project by adding
to my This should be the default for interactive development, and I was kinda shocked that it isn’t! |
@Profpatsch: Great! What are your precise results? |
@Profpatsch thanks for the tip, it also sped up things for me! @Mikolaj , so I first added
thinking that will speed up local development / building of local code. It however had no effect, so I started searching through cabal issues on GH and found this issue. I applied the suggestion by @Profpatsch :
Our project consists of one package, which has one executable (24 modules), one library (124 modules), two test suites (one 8, another 29 modules). When building just library and exe ( Most of the speed up seems to be coming from compiling the library. I also wonder why this is not default, I guess it is relatively new thing? Here is project in case it is useful: I am pointing to PR because we are just switching from Stack to Cabal, so this is the PR that does it and contains the code I tested this upon: wasp-lang/wasp#471 |
Thank you for the data. Good points. I wonder how reporting warning and errors works with that option? E.g., may two warning texts be interspersed? I think that was in the past the blocker for cabal-level |
BTW, there is active work by some GHC hacker on how to allocat cores to cabal-level and GHC-level [Edit: to see what I mean, you'd need to wipe out the whole store directory and then measure the effect.] |
So, I guess, default GHC |
@Mikolaj thanks for explaining! I have to admit I don't know how errors are reported, haven't tried that out. So help me understand if I got this right: if cabal is building only one package, then GHC But I guess with the way I set it up now, that shouldn't be a problem, because GHC |
RAM also fills up quickly with -j |
I understand then that having GHC But it still sounds useful for local development of a package, be it library or executable. So maybe it makes sense for most people to have GHC |
Yes, I think GHC |
Cabal packages are a horror story anyway, e.g. they don’t work together with For local development I always want to use all available cores, for building libraries we use nixpkgs/nix, which knows how to forward the right amount of cores to its builds.
You can probably get more speedups if your project’s modules are split up in a reasonable way, i.e. there is no bottleneck module that all of compilation has to wait on. |
In order to see the module dependency graph, I use a script like this in our production code: # display a graph of all modules and how they depend on each other
mainserv-module-deps-with-filetype = self.writers.writeBash "mainserv-module-deps-with-filetype" ''
shopt -s globstar
filetype="$1"
${self.haskellPackages.graphmod}/bin/graphmod \
${/*silence warnings for missing external dependencies*/""} \
--quiet \
${/*applies some kind of import simplification*/""} \
--prune-edges \
${self.mainserv-root-directory}/src/**/*.hs \
| ${self.graphviz}/bin/dot \
${/*otherwise it’s a bit cramped*/""} \
-Gsize="20,20!" \
-T"$filetype"
''; Which uses the very good https://hackage.haskell.org/package/graphmod command. Then it’s just a matter of looking at the graph and noticing bottlenecks. |
@Profpatsch: yay, a great tool. And what are the speedups you are getting building a local project with GHC |
ghc-proposals/ghc-proposals#540 is in! 🚀 |
Updated summary by @ezyang. Previously, this ticket talked about all sorts of parallelism at many levels. Component-level parallelism was already addressed in #2623 (fixed by per-component builds), so all that remains is per-module parallelism. This is substantially more difficult, because right now we build by invoking
ghc --make
; achieving module parallelism would require teaching Cabal how to build usingghc -c
. But this too has a hazard: if you don't have enough cores/have a serial dependency graph,ghc -c
will be slower, because GHC spends more time reloading interface files. In #976 (comment) @dcoutts describes how to overcome this problem.There are several phases to the problem:
First building the GHC build server and parallelism infrastructure. This can be done completely independently of Cabal: imagine a program which has a command line identical to GHC, but is internally implemented by spinning up multiple GHC processes and farming out the compilation process. You can tell if this was worthwhile when you get scaling better than GHC's built-in
-j
and a traditional-c
setup.Next, we need to teach Cabal/cabal-install how to take advantage of this functionality. If you implemented your driver program with exactly the same command line flags as GHC, then this is as simple as just passing
-w $your_parallel_ghc_impl
. However, this is a problem doing it this way: cabal-install will attempt to spin up N parallel package/component builds, which each in turn will try to spin up M GHC build servers; this is bad; you want the total number of GHC build servers to equal the number of cores. So then you will need to setup some sort signalling mechanism to avoid too many build servers from running at once, OR have cabal new-build orchestrate the entire build down to the module level so it can plan parallelism (but you would probably have to rearchitect according to Rewrite Cabal in Shake #4174 before you can do this.)Now that the package-level parallel install has been implemented (see #440), the next logical step is to extend
cabal build
with support for building multiple modules, components and/or build variants (static/shared/profiling) in parallel. This functionality should be also integrated withcabal install
in such a way that we don't over- or underutilise the available cores.A prototype implementation of a parallel
cabal build
is already available as a standalone tool. It works by first extracting a module dependency graph with 'ghc -M' and then running multiple 'ghc -c' processes in parallel.Since the parallel install code uses the external setup method exclusively, integrating parallel
cabal build
with parallel install will require using IPC. A single coordinatingcabal install -j N
process will spawn a number ofsetup.exe build --semaphore=/path/to/semaphore
children, and each child will be building at most N modules simultaneously. An added benefit of this approach is that nothing special will have to be done to support custom setup scripts.An important issue is that compiling with
ghc -c
is slow compared toghc --make
because the interface files are not cached. One way to fix this is to implement a "build server" mode for GHC. Instead of repeatedly runningghc -c
, each build process will spawn at most N persistent ghcs and distribute the modules between them. Evan Laforge has done some work in this direction.Other issues:
cabal repl
patches).build-type: Simple
.The text was updated successfully, but these errors were encountered: