Skip to content
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

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

Closed
nh2 opened this issue Jan 11, 2013 · 27 comments
Closed

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

nh2 opened this issue Jan 11, 2013 · 27 comments

Comments

@nh2
Copy link
Member

nh2 commented Jan 11, 2013

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
Copy link
Member

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
Copy link
Member Author

nh2 commented Jan 11, 2013

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

@23Skidoo
Copy link
Member

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

@nh2
Copy link
Member Author

nh2 commented Jan 11, 2013

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
Copy link
Member

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
Copy link
Member Author

nh2 commented Jan 12, 2013

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
Copy link
Member

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
Copy link
Member

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
Copy link
Member

Related: #1121

@nh2
Copy link
Member Author

nh2 commented Aug 5, 2013

@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
Copy link
Member Author

nh2 commented Aug 5, 2013

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

@nh2
Copy link
Member Author

nh2 commented Aug 5, 2013

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
Copy link
Member Author

nh2 commented Aug 5, 2013

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
Copy link
Contributor

dcoutts commented Aug 6, 2013

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
Copy link
Member Author

nh2 commented Aug 6, 2013

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
Copy link
Contributor

dcoutts commented Aug 6, 2013

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
Copy link
Contributor

dcoutts commented Aug 6, 2013

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
Copy link
Member Author

nh2 commented Aug 6, 2013

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
Copy link
Member Author

nh2 commented Aug 6, 2013

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

Can you have a look?

@tibbe
Copy link
Member

tibbe commented Aug 6, 2013

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
Copy link
Member Author

nh2 commented Aug 23, 2013

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

@tibbe
Copy link
Member

tibbe commented Aug 31, 2013

@dcoutts could you please have a look?

@nh2
Copy link
Member Author

nh2 commented Aug 31, 2013

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

@tibbe
Copy link
Member

tibbe commented Aug 31, 2013

@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
Copy link
Member Author

nh2 commented Sep 1, 2013

@tibbe Which platforms do we support?

@nh2
Copy link
Member Author

nh2 commented Nov 6, 2013

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

@nh2 nh2 closed this as completed Nov 6, 2013
@nh2
Copy link
Member Author

nh2 commented Jun 15, 2017

Note: Re-linking still happens every time when -dynamic is used: https://ghc.haskell.org/trac/ghc/ticket/13828

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants