Advertisement

Debugging cause of long compile times..

Started by March 22, 2001 11:39 AM
7 comments, last by Stoffel 23 years, 10 months ago
We have a fairly large library we''ve built here (1.8 Mb of source and header files), and compile times are getting extraordinarily long. I was wondering if there were any tools or methods to use to figure out what was taking the largest chunk of the compile and link time, maybe something horribly bad is going on (like functions being duplicated that shouldn''t be). Does anybody know how to go about debugging this? In a previous post: http://www.gamedev.net/community/forums/topic.asp?topic_id=43035 ...Gimp suggested that I use the option "/Y3" to display the time required for each compile step. This option doesn''t work for MSVC, so he must have been thinking of a different compiler. Anybody know a good way to do this?
Hmm... you''re not rebuilding the entire thing each time you compile, are you?
Advertisement
It has been my experience that header files cause most of the compile time increases. If you have Headers including headers - which in turn include headers, you have a lot of time being eaten up by the headers themselves and not the actual code.

If you can swing it, use precompiled headers. This will cost you a very slow 1st compile and tremendous speed increases in successive compiles (as long as the vast majority of headers are pretty constant with very little changes between compiles).
Project->Settings->C/C++->Precompiled Headers->...

OR - rework the headers to alleviate the #includes and place them in the source files instead. This is easier said than done though and involves a lot of work.



Regards,
Jumpster
Regards,JumpsterSemper Fi
after reducing headers as much as possable, you can also package as many common headers as you can into a single "common.h" that has just the includes, and checks a single #ifdef _COMMON. Putting stuff into a library that rarely changes also speeds things up during the link phase.

Compile times are the most annoying and slowest part of C/C++
For MSVC++, I''ve noticed several factors that appear to have a consistantly significant impact on compile times, such as the following:

1) Effective use of precompiled headers. In very large code bases where header processing takes up a significant part of the compilation time, the "automatic" precompiled header mode may not be your best bet. For small-to-medium size code bases, automatic works fine, but for large ones (on the order of 100 KLOC or more) you may want to look into using manually generated precompiled headers for header sets that do not change frequently. The automatic option seems to behave less than optimally when the number and/or size of the headers breaches a certain point.

2) Careful use of templates. MSVC++''s compilation time seems to be inordinately slow with regards to template expansion in release builds, so a template-heavy project can suffer very long compilation times. When templates are specified in header files, the actual generated classes are generally constructed by the compiler (and then optimized when in release build) for each source file that needs to expand the template. When the template is used with multiple types of parameters, this can result in multiple expansion and optimization steps, slowing things down noticably. Many large template classes have operations that are not specifically bound to the template parameters, so if you have a template-heavy project that is causing compiler slowdowns, you may want to look into factoring out methods that are not dependent on the template parameters into a non-templated base class, and have the template class derive from this base class. This kind of change reduces the size of many template classes significantly, which can reduce template expansion time and speed up compilation.

3) Controlled inlining. It is sometimes tempting to put "inline" (or even "__forceinline") in front of some functions that you think should be inlined but which compiler does not think should be. When you think inlining should be used for a particular function and the compiler does not, then in general, the compiler is right. There are caching and other generated code size issues that can significantly affect whether inlining a particular function call will be effective. In general, I recommend setting the release build inlining options to "Any suitable" so the compiler can choose what works best, and to only force the issue when the compiler chooses not to inline a particular method that you are absolutely certain (via evidence from a profiler) needs to be inlined for speed reasons. Unless the function is exceptionally small (i.e. a couple of lines of code at most) or you''re absolutely sure a non-inlined call would cause a big speed hit, you should leave the inlining keywords out of the question and let the compiler choose (provided that you use the "any suitable" option of course).

Just a few thoughts.
Thanks for the responses.

Sean: I''m not rebuilding fresh every time--my problem is that when I do , it takes a lot longer than it used to. In other words, it takes about 8x the amount of time to do a full rebuild than it did when the code what 2x smaller. In other words, I seem to have an exponential code size to build time curve, and was wondering if I could find out where that time is being spent.

Jumpster: We do have headers including headers. Will take some work to factor them out, but I already suspected it was a problem (have to convince my team).

Grib: good idea, but I don''t think separating out a sublibrary from this library will be possible. This is a bit of a monolithic package for all of our needs (which is part of why it has a monstrous scope). The team doesn''t want multiple libraries flying around, just one do-all. I know, probably not the best idea. Maybe I''m trying to polish a turd.

Chris: I turned off "automatic precompiled headers", and the compile time dropped by like 33%. I turned on messages, and it turns out that it was creating the same precompiled header program (at least that''s what the messages lead me to believe) for each source file. How is that smart?

We are using templates--it''s an STL-heavy project. However, none of our stuff is templated; just using STL containers everywhere.

I''m not sure of the scope of inlining, haven''t really looked through the project to see when/where/if it''s being used. Definitely something to check, though.

I think a good course of action would be to break the mass of header files up into something more reasonable. It would take some work, though, and since this isn''t a "bug", there''s not really the pressure to fix it.

Thanks for the responses, all.
Advertisement
Tell me about it! My library takes forever to build. It''s not even very big! The Release build DLL is only ... *waits for VC to stop compiling* ... *still waiting* ... 164 KB. There are probably less than 10 template functions in there, and five are in the pch. Oh, wait, my geometry template library is slowing things to a crawl. It''s not really necessary now that I''ve scrapped the GUI code, so luckily I can remove it. Maybe I''ll play around with the pch a bit, too. (But then again, if it becomes too fast then it''ll look like I haven''t done much - perhaps I''d better add in a string table and some static data to balance it out a bit. )

This is a very interesting topic, and it is great to hear from some professionals! BTW, does anyone have the Visual Studio.NET beta? I wonder if they''ve improved the compile times? Have they implemented the export keyword yet?

Good Luck,
- null_pointer
quote:

Stoffel: I turned off "automatic precompiled headers", and the compile time dropped by like 33%. I turned on messages, and it turns out that it was creating the same precompiled header program (at least that's what the messages lead me to believe) for each source file. How is that smart?




I'm not sure of the exact rules it uses, but the compiler can only reuse the precompiled header if all the #defines and other rules are EXACTLY the same for each source file. This means that if you include the header files in a different order it will probably have to regenerate the automatic precompiled header, or if you put #defines in your source files.
Unfortunately the compiler doesn't tell you exactly what is different for this source file, and so why it has regenerated the precompiled header.

quote:

I think a good course of action would be to break the mass of header files up into something more reasonable. It would take some work, though



Where I use to work we had quite *lively* discussions about the best way to organise header files.

Most programmers by instinct include just the headers they need for a particular source file, and have them in some sort of order at the top of the file. This means that:

1) Individual programmers have to spend time figuring out which header files their source files are dependent upon, and which header files those are dependent upon...This can waste time as people fish around trying to get their code to compile with the minimum of h file includes.

2) Different source files need different headers, which means you can't use precompiled headers efficiently.

3) It is possible to include absolutely MARVELLOUS bugs where different and mutually incompatible defines have been used in different parts of the code. eg there is a define in one header which affects how another header gets compiled...obviously you have to include them in the correct order otherwise you can get really screwy bugs, but not all programmers might be aware of this.


The other way of using headers is having a single header header for each area/library/dll of the program, which is included in each source file. eg I always have a header called Unity.h for the main game logic files, which looks something like this:

    #ifndef _MEMORY_MANAGER_#define _MEMORY_MANAGER_#include <MemoryManager.h>#endif #ifndef _GAME_MATHS_H_#define _GAME_MATHS_H_#include <GameMaths.h>#endif #ifndef _PHYSICS_H_#define _PHYSICS_H_    #include <Physics.h>#endif#ifndef _GRAPHICS_INTERFACE_H#define _GRAPHICS_INTERFACE_H_    #include <GraphicsInterface.h>#endif....  


(Though obviously without special formatting for gamedev...

I then include just Unity.h in each of the game logic files.

This has the following advantages:

1) I never need to worry about which header files i need to include on a source project, and also the top of my source files are relatively clean, without perhaps 100 lines devoted to includes.

2) I never have to worry about getting the header files in the right order. I just include Unity.h and everything works.

3) Precompiled headers work fantastically. At work I had a project that had approx 120 files and I could do a full clean rebuild and link in about 45 seconds, without precompiled headers it took about three minutes.

It does have the downside that I have to do rebuilds more frequently than including just the headers each source file needs.
However if you enable minimal rebuilds the compiler is smart enough not to rebuild things that don't need changing.
So if I just change a function of a class in its header file, the compiler will know to rebuild just a couple of files and skip the rest of the source files (the message you will see is 'Skipping, no changes detected')

So, er yeah, I really recommend sitting down, finding out what defines or includes are making your compiler regenerate the precompiled header file and stopping it from happening, and then turn on 'minimal rebuilds' ( i think...) to allow the compiler to skip files that haven't got any relevant changes.

btw You would have thought that using 'header header' files would be disastrasly slow on GNU compilers, which don't have support for precompiled headers (pls correct me if I'm wrong there). However we were quite surprised when changing from including individual headers to using header header files, speeded up compile times on our PS2 project which used SN Systems GNU based compiler.....

cheers dan


btw putting the #ifndef _HEADER_FILE_, include outside the .h file, can give you a good speedup as the compiler doesn't have to open the .h file to do this check.




Game production:
Good, quick, cheap: Choose two.

Edited by - danack on March 23, 2001 1:47:24 PM
Game production:Good, quick, cheap: Choose two.
This is a very useful thread, and interesting. I''ve noticed that on my current project, where different objects'' internal implementations change on a daily basis as I modify and add features and abilities, compile times are getting very annoyingly long.

It''s my pet peeve about C++, really: other modules that want to talk to a game object just need to know about its public methods/variables, not its internals, but in C++ you have your internals out in the header file, and so a simple change triggers a massive set of recompilation of various source files that include the header.

When it gets bad enough, it''s probably going to force me to sit down during an afternoon, and eliminate as many dependancies as possible.

- Remnant
- (Steve Schmitt)
- Remnant- (Steve Schmitt)

This topic is closed to new replies.

Advertisement