Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Can re-linking on cabal build be avoided? #1177

Closed
nh2 opened this Issue · 26 comments

5 participants

@nh2

When I build my project repeatedly, I always get

Linking dist/build/ex1/ex1 ...
Linking dist/build/ex2/ex2 ...

even though no code has changed, and it takes significant time.

Is it possible to detect that no input files have changed so that re-linking can be skipped?

This would speed up no-op builds a lot (e.g. factor 3 for 3 executables, and it scales up) and be very useful for integration into editors where a quick rebuild response is helpful.

@23Skidoo
Collaborator

What's the structure of your project? I guess that you have a library defined in the same .cabal file that both ex-1 and ex-2 depend on, otherwise relinking shouldn't happen.

@nh2

Yes, exactly. Why does (should?) it happen in this case?

@23Skidoo
Collaborator

My hypothesis is that the library is reinstalled into the in-place package db each time, which changes the timestamp.

@nh2

Yes, that happens:

Building mylib-0.1.0.0...
Preprocessing library mylib-0.1.0.0...
In-place registering mylib-0.1.0.0...
Preprocessing executable 'ex1' for mylib-0.1.0.0...
Linking dist/build/ex1/ex1 ...

Do we have to treat the library as new if it hasn't changed?

@23Skidoo
Collaborator

Do we have to treat the library as new if it hasn't changed?

Cabal is dumb in this regard: there is no mechanism to tell whether the library hasn't changed and doesn't need to be reinstalled. This hurts sandbox add-source too - all deps are reinstalled on each cabal build.

@nh2

So you would say it is a lacking feature that would be useful in other places as well?

In ~/.ghc, we have checksums for installed packages. Can we not use them for this as well?

@23Skidoo
Collaborator

So you would say it is a lacking feature that would be useful in other places as well?

It'd be useful for sandbox add-source, yes.

In ~/.ghc, we have checksums for installed packages. Can we not use them for this as well?

Possibly - need to look closer at the implementation. Perhaps this can be done as an install flag.

@simonmar
Owner

GHC does check the timestamps on the libraries it is linking against when deciding whether to relink. So the best solution would be for Cabal to check whether it is installing a library file that is identical to the existing file, and not touch the file if so.

@23Skidoo
Collaborator

Related: #1121

@nh2
nh2 commented

@simonmar You are right, I tried that, ghc itself avoids it and that is ~10 times faster than actual linking. Unfortunately, ar writes a out a different invocation because it requires a time stamp by default. However, it looks like one can make it deterministic by using ar -D.

I will have a look at that.

@nh2
nh2 commented

Cabal also calls ghc twice for linking: Once with --no-link, and once with -o [binary]. Does anyone know why?

@nh2
nh2 commented

Done!

The change is quite small and works incredibly well for me. For my project with 40 executables, my no-op cabal build time went down from 96 seconds to 16 seconds (and using my build-executables-in-parallel branch, it finishes in 9 seconds, but this change is independent of it).

The link time is now dominated by the two ghc invocations I mentioned above.

So the remaining points are:

  1. Why the two ghc invocations? They are now the time bottleneck.
  2. One of my commits removes code that deleted the .a files before creating them. I cannot see what that was meant for originally. Do you know why that was done?
  3. As mentioned in this comment, the ar -D option only exists since 2009 (binutils 2.20). Do we maintain backwards compatibility with older versions? If yes, I can put in a switch for that.
  4. As mentioned in the comment next to it, ar -D might not work on OS X. I cannot currently check that. Does somebody have a Mac?

And independent of my change:

  1. How does GHC manage to avoid using ld entirely? One feature of ld is that it looks up certain libraries for you by name (like -lmath though that's a dynamic linking example), so is it possible that ghc ever does not notice an update of e.g. some system library or does it have full knowledge about all object files that go into the binary, ever?
@nh2
nh2 commented

For 4: With help of merijn from #haskell, I could find out that OS X ar does not have the -D option (none of the BSDs seem to have).

@dcoutts
Collaborator

This is great, but it needs to be checked individually on each platform sadly. I remember the pain I went through to work out which ar flags we have to use on each platform. It's not ok to just assume. (You've mentioned BSD, there's also Solaris, whatever older binutils mingw uses on Windows etc)

So my suggestion is that we set flags to include -D only for the specific platforms we can confirm it works on, and leave the _ -> ... other cases as they are now. We will have to check it on Windows anyway because of that copy/rename stuff (see the detailed code comment).

@nh2
nh2 commented

This is great, but it needs to be checked individually on each platform sadly.

I'm working on alternative solution: The ar format is precisely specified and the time stamp is always in the same place at byte 24 of a file entry. We can set it to 0 ourselves to get what -D does.

@dcoutts
Collaborator

As for the -no-link vs -o. It's because we have to build .c files after building the .hs files because FFI foreign exports create .h files that the .c files may need. So we have to compile .hs, then .c and then link.

Is there really a measurable performance difference there? I'd be surprised if there's that much extra startup overhead. Oh, hmm, perhaps if we're still calling ghc in --make mode for the link, when we could just tell ghc to do the link directly, but to do that we need to know all the .o files and currently people often don't list all the other-modules for exes.

@dcoutts
Collaborator

I'm working on alternative solution:

Wait a moment! I already have code to generate Ar format files, and yes that bit is easy. The problem is you still need to use the "real" ar program because it's the symbol index that is the tricky bit, and that is sadly different on each platform, you don't want to write code to generate that. So you'd still need to run ranlib (which is basically ar) to generate the index, and it may go and insert timestamps again...

@nh2
nh2 commented

Is there really a measurable performance difference there? I'd be surprised if there's that much extra startup overhead

I wouldn't call it "overhead" - in a no-op build, the two ghc invocations are the only things that take time, so using only one saves me half the time.

Your argument for why this is done makes sense. Although I think the two links are done unconditional of that - I don't think my executables generate any .c files. Is that right?

I already have code to generate Ar format files

I'm not suggesting writing the .a files with Haskell. I mean generating them with ar as is, and then wiping out all timestamps.

@nh2
nh2 commented

@dcoutts Ok, I've implemented that, and updated #1410.

Can you have a look?

@tibbe
Owner

I'd expect to see a performance difference. Re-linking test suite executables that haven't changed takes a considerable amount of time when building e.g. containers (which has many test suites, one for each data structure).

@nh2

Any update on this? We've been running it for a few weeks now and it works very well.

@tibbe
Owner

@dcoutts could you please have a look?

@nh2

This change is very non-invasive, so it would be great if it could make it into 1.18.

@tibbe
Owner

@nh2 I think @dcoutts is worried about how this will work on different platforms. Linkers are finicky beasts and we don't want to rush something and end up with a broken 1.18.

@nh2
nh2 commented

@tibbe Which platforms do we support?

@nh2
nh2 commented

Great that this is done. Thanks @liyang and @tibbe!

@nh2 nh2 closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.