Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are there techniques to greatly improve C++ building time for 3D applications?

Tags:

There are many slim laptops who are just cheap and great to use. Programming has the advantage of being done in any place where there is silence and comfort, since concentrating for long hours is important factor to be able to do effective work.

I'm kinda old fashioned as I like my statically compiled C or C++, and those languages can be pretty long to compile on those power-constrainted laptops, especially C++11 and C++14.

I like to do 3D programming, and the libraries I use can be large and won't be forgiving: bullet physics, Ogre3D, SFML, not to mention the power hunger of modern IDEs.

There are several solutions to make building just faster:

  • Solution A: Don't use those large libraries, and come up with something lighter on your own to relieve the compiler. Write appropriate makefiles, don't use an IDE.

  • Solution B: Set up a building server elsewhere, have a makefile set up on an muscled machine, and automatically download the resulting exe. I don't think this is a casual solution, as you have to target your laptop's CPU.

  • Solution C: use the unofficial C++ module

  • ???

Any other suggestion ?

like image 671
jokoon Avatar asked Apr 09 '15 13:04

jokoon


People also ask

Why does building C++ take so long?

1) C++ grammar is more complex than C# or Java and takes more time to parse. 2) (More important) C++ compiler produces machine code and does all optimizations during compilation.


1 Answers

Compilation speed is something, that can be really boosted, if you know how to. It is always wise to think carefully about project's design (especially in case of large projects, consisted of multiple modules) and modify it, so compiler can produce output efficiently.

1. Precompiled headers.

Precompiled header is a normal header (.h file), that contains the most common declarations, typedefs and includes. During compilation, it is parsed only once - before any other source is compiled. During this process, compiler generates data of some internal (most likely, binary) format, Then, it uses this data to speed up code generation.

This is a sample:

#pragma once  #ifndef __Asx_Core_Prerequisites_H__ #define __Asx_Core_Prerequisites_H__  //Include common headers #include "BaseConfig.h" #include "Atomic.h" #include "Limits.h" #include "DebugDefs.h" #include "CommonApi.h" #include "Algorithms.h" #include "HashCode.h" #include "MemoryOverride.h" #include "Result.h" #include "ThreadBase.h" //Others...  namespace Asx {      //Forward declare common types     class String;     class UnicodeString;      //Declare global constants     enum : Enum     {         ID_Auto     = Limits<Enum>::Max_Value,         ID_None     = 0     };      enum : Size_t     {         Max_Size            = Limits<Size_t>::Max_Value,         Invalid_Position    = Limits<Size_t>::Max_Value     };      enum : Uint     {         Timeout_Infinite    = Limits<Uint>::Max_Value     };      //Other things...  }  #endif /* __Asx_Core_Prerequisites_H__ */ 

In project, when PCH is used, every source file usually contains #include to this file (I don't know about others, but in VC++ this actually a requirement - every source attached to project configured for using PCH, must start with: #include PrecompiledHedareName.h). Configuration of precompiled headers is very platform-dependent and beyond the scope of this answer.

Note one important matter: things, that are defined/included in PCH should be changed only when absolutely necessary - every chnge can cause recompilation of whole project (and other depended modules)!

More about PCH:

Wiki
GCC Doc
Microsoft Doc

2. Forward declarations.

When you don't need whole class definition, forward declare it to remove unnecessary dependencies in your code. This also implicates extensive use of pointers and references when possible. Example:

#include "BigDataType.h"  class Sample { protected:     BigDataType _data; }; 

Do you really need to store _data as value? Why not this way:

class BigDataType; //That's enough, #include not required  class Sample { protected:     BigDataType* _data; //So much better now }; 

This is especially profitable for large types.

3. Do not overuse templates.

Meta-programming is a very powerful tool in developer's toolbox. But don't try to use them, when they are not necessary.

They are great for things like traits, compile-time evaluation, static reflection and so on. But they introduce a lot of troubles:

  • Error messages - if you have ever seen errors caused by improper usage of std:: iterators or containers (especially the complex ones, like std::unordered_map), than you know what is this all about.
  • Readability - complex templates can be very hard to read/modify/maintain.
  • Quirks - many techniques, templates are used for, are not so well-known, so maintenance of such code can be even harder.
  • Compile time - the most important for us now:

Remember, if you define function as:

template <class Tx, class Ty> void sample(const Tx& xv, const Ty& yv) {     //body } 

it will be compiled for each exclusive combination of Tx and Ty. If such function is used often (and for many such combinations), it can really slow down compilation process. Now imagine, what will happen, if you start to overuse templating for whole classes...

4. Using PIMPL idiom.

This is a very useful technique, that allows us to:

  • hide implementation details
  • speed up code generation
  • easy updates, without breaking client code

How does it work? Consider class, that contain a lot of data (for example, representing person). It could look like this:

class Person { protected:     string name;     string surname;     Date birth_date;     Date registration_date;     string email_address;     //and so on... }; 

Our application evolves and we need to extend/change Person definition. We add some new fields, remove others... and everything crashes: size of Person changes, names of fields change... cataclysm. In particular, every client code, that depends on Person's definition needs to be changed/updated/fixed. Not good.

But we can do it the smart way - hide the details of Person:

class Person { protected:     class Details;     Details* details; }; 

Now, we do few nice things:

  • client cannot create code, that depends on how Person is defined
  • no recompilation needed as long as we don't modify public interface used by client code
  • we reduce the compilation time, because definitions of string and Date no longer need to be present (in previous version, we had to include appropriate headers for these types, that adds additional dependencies).

5. #pragma once directive.

Although it may give no speed boost, it is clearer and less error-prone. It is basically the same thing as using include guards:

#ifndef __Asx_Core_Prerequisites_H__ #define __Asx_Core_Prerequisites_H__  //Content  #endif /* __Asx_Core_Prerequisites_H__ */ 

It prevents from multiple parses of the same file. Although #pragma once is not standard (in fact, no pragma is - pragmas are reserved for compiler-specific directives), it is quite widely supported (examples: VC++, GCC, CLang, ICC) and can be used without worrying - compilers should ignore unknown pragmas (more or less silently).

6. Unnecessary dependencies elimination.

Very important point! When code is being refactored, dependencies often change. For example, if you decide to do some optimizations and use pointers/references instead of values (vide point 2 and 4 of this answer), some includes can become unnecessary. Consider:

#include "Time.h" #include "Day.h" #include "Month.h" #include "Timezone.h"  class Date { protected:     Time time;     Day day;     Month month;     Uint16 year;     Timezone tz;      //... }; 

This class has been changed to hide implementation details:

//These are no longer required! //#include "Time.h" //#include "Day.h" //#include "Month.h" //#include "Timezone.h"  class Date { protected:     class Details;     Details* details;      //... }; 

It is good to track such redundant includes, either using brain, built-in tools (like VS Dependency Visualizer) or external utilities (for example, GraphViz).

Visual Studio has also a very nice option - if you click with RMB on any file, you will see an option 'Generate Graph of include files' - it will generated a nice, readable graph, that can be easily analyzed and used to track unnecessary dependencies.

Sample graph, generated inside my String.h file:

Sample Graph

like image 83
Mateusz Grzejek Avatar answered Oct 13 '22 06:10

Mateusz Grzejek