Live data from Hacker News

C++ Headers are Expensive

virtuallyrandom.com

11–20 of 115 posts

Re: C++ Headers are Expensive

#11
post #4

I recommend three things for wrangling compile times in C++: precompiled headers, using forward headers when possible (e.g. ios_fwd and friends), and implementing an aggressive compiler firewall strategy when not. The compiler firewall strategy works fairly well in C++11 and even better in C++14. Create a public interface with minimal dependencies, and encapsulate the details for this interface in a pImpl (pointer to…

pImpl pattern is great for those who don’t care about performance but it’s inappropriate for most header libraries. You wouldn’t want a library that hides the implementation of std::vector for example. With a visible implementation the compiler compile e.g. operator[] down one x86 instruction. With a pImpl pattern it will be an indirect function call in all likelihood that will be hundreds of times slower. It can mak…

This depends on what you are building. Don't commit the sin of early optimization.

Does a client of the framework you are writing -- which is probably using STL internally -- need a single instruction operation for adding a value for a call that you make less than 0.001% of the time?

Optimization is about end results. Apply the Pareto Principle, and don't forget that your users also need to compile your code in a reasonable amount of time.

Re: C++ Headers are Expensive

#12
post #4

I recommend three things for wrangling compile times in C++: precompiled headers, using forward headers when possible (e.g. ios_fwd and friends), and implementing an aggressive compiler firewall strategy when not. The compiler firewall strategy works fairly well in C++11 and even better in C++14. Create a public interface with minimal dependencies, and encapsulate the details for this interface in a pImpl (pointer to…

One hickup is that with unique_ptr you now have a rule of 5 thing, you need to declare a destructor which means you need to declar the copy/move constructor and assignment too. Not usually a big deal, but is extra code.

Re: C++ Headers are Expensive

#13
post #4

I recommend three things for wrangling compile times in C++: precompiled headers, using forward headers when possible (e.g. ios_fwd and friends), and implementing an aggressive compiler firewall strategy when not. The compiler firewall strategy works fairly well in C++11 and even better in C++14. Create a public interface with minimal dependencies, and encapsulate the details for this interface in a pImpl (pointer to…

One hickup is that with unique_ptr you now have a rule of 5 thing, you need to declare a destructor which means you need to declar the copy/move constructor and assignment too. Not usually a big deal, but is extra code.

This is true. Fortunately, these do not need to be inlined, which can still free client code of compile time overhead.

It's a tradeoff between compile time and complexity.

Re: C++ Headers are Expensive

#14
In the Zig stage1 compiler (written in C++), I tried to limit all the C++ headers to as few files as possible. Not counting vendored dependencies, the compiler builds in 24 seconds using a single core on my laptop. It's because of tricks like this:

    /*
     * The point of this file is to contain all the LLVM C++ API interaction so that:
     * 1. The compile time of other files is kept under control.
     * 2. Provide a C interface to the LLVM functions we need for self-hosting purposes.
     * 3. Prevent C++ from infecting the rest of the project.
     */


    // copied from include/llvm/ADT/Triple.h

    enum ZigLLVM_ArchType {
        ZigLLVM_UnknownArch,
    
        ZigLLVM_arm,            // ARM (little endian): arm, armv.*, xscale
        ZigLLVM_armeb,          // ARM (big endian): armeb
        ZigLLVM_aarch64,        // AArch64 (little endian): aarch64
    ...
and then in the .cpp file:

    static_assert((Triple::ArchType)ZigLLVM_UnknownArch == Triple::UnknownArch, "");
    static_assert((Triple::ArchType)ZigLLVM_arm == Triple::arm, "");
    static_assert((Triple::ArchType)ZigLLVM_armeb == Triple::armeb, "");
    static_assert((Triple::ArchType)ZigLLVM_aarch64 == Triple::aarch64, "");
    static_assert((Triple::ArchType)ZigLLVM_aarch64_be == Triple::aarch64_be, "");
    static_assert((Triple::ArchType)ZigLLVM_arc == Triple::arc, "");
    ...
I found it more convenient to redefine the enum and then static assert all the values are the same, which has to be updated with every LLVM upgrade, than to use the actual enum, which would include a bunch of other C++ headers.

The file that has to use C++ headers takes about 3x as long to compile than Zig's ir.cpp file which is nearing 30,000 lines of code, but only depends on C-style header files.

Re: C++ Headers are Expensive

#15
> The test was done with the source code and includes on a regular hard drive, not an SSD.

In my opinion, this makes any conclusion dubious. If you really care about compile times in C++, step 0 is to make sure you have an adequate machine (at least quadcore CPU/ lot of RAM/SSD). If the choice is between spending programmer time trying to optimize compile times, versus spending a couple hundred dollars for an SSD, 99% of the time, spending money on an SSD will be the correct solution.

Re: C++ Headers are Expensive

#16

Earlier quoted context omitted.

pImpl pattern is great for those who don’t care about performance but it’s inappropriate for most header libraries. You wouldn’t want a library that hides the implementation of std::vector for example. With a visible implementation the compiler compile e.g. operator[] down one x86 instruction. With a pImpl pattern it will be an indirect function call in all likelihood that will be hundreds of times slower. It can mak…

This depends on what you are building. Don't commit the sin of early optimization. Does a client of the framework you are writing -- which is probably using STL internally -- need a single instruction operation for adding a value for a call that you make less than 0.001% of the time? Optimization is about end results. Apply the Pareto Principle, and don't forget that your users also need to compile your code in a rea…

That only makes sense if you are planning to offer two implementations of your library. Which I of course urge you to not do. This article is about the STL headers. The reason std::sort beats the pants off all other languages’ sort routines is because the iterators of every collection, all specializations of swap, and the comparator can all be visible to the compiler. If they weren’t, it would be a lot slower.

Premature optimization is not really a thing but foreclosing future avenues of optimization definitely can be.

Re: C++ Headers are Expensive

#17
post #3
post #2

Isn't this the reason precompiled headers are a thing?

As far as I understand it's also one of the reasons modules are a thing... or at least people want them to be. Precompiled headers are a pretty ugly solution and the way they've been implemented in the past could be really nasty. (IIRC in old GCC versions it would copy some internal state to disk, then later load it from disk and manually adjust pointers!)

This isn't uncommon, especially for file formats which are meant for internal consumption. Of course, they end up being huge cans of worms in terms of security, stability and maintainability going forward.

Basically, instead of defining a real serialization format (and thus having to write serializer/deserializer code), it's way easier to just `fwrite` out your internal structs to disk, one after another, and write some much simpler walker code to walk through any pointed fields appropriately. At some point though this becomes technical debt which needs to be repaid in the form of a total serialization rewrite.

Blender, the popular open-source 3D modelling tool, uses a format like this for their .blend files, and it is really gross. IIRC a few releases back they started working to improve the format to be a little less dependent on low-level internal details, but now they have the nightmare of backwards compatibility to deal with.

The basic problem is that C/C++ have no mechanism for native serialization, unlike e.g. Java, Python, or any number of other languages, so you're either stuck `fwrite`ing structs or reinventing the wheel.

Re: C++ Headers are Expensive

#18

Earlier quoted context omitted.

This depends on what you are building. Don't commit the sin of early optimization. Does a client of the framework you are writing -- which is probably using STL internally -- need a single instruction operation for adding a value for a call that you make less than 0.001% of the time? Optimization is about end results. Apply the Pareto Principle, and don't forget that your users also need to compile your code in a rea…

That only makes sense if you are planning to offer two implementations of your library. Which I of course urge you to not do. This article is about the STL headers. The reason std::sort beats the pants off all other languages’ sort routines is because the iterators of every collection, all specializations of swap, and the comparator can all be visible to the compiler. If they weren’t, it would be a lot slower. Premat…

> Premature optimization is not really a thing

Okay. I'm going to stop this thread right there and take some opportunity to provide some mentoring. I hope you accept this, as it will help in your career.

Read this paper. It is a classic.

https://pic.plover.com/knuth-GOTO.pdf

Re: C++ Headers are Expensive

#19

In the Zig stage1 compiler (written in C++), I tried to limit all the C++ headers to as few files as possible. Not counting vendored dependencies, the compiler builds in 24 seconds using a single core on my laptop. It's because of tricks like this: /* * The point of this file is to contain all the LLVM C++ API interaction so that: * 1. The compile time of other files is kept under control. * 2. Provide a C interface…

What a world... Thanks for working on Zig, can't wait to see what comes of it. Anything to get some languages back that bring some joy into programming back!

Re: C++ Headers are Expensive

#20

Earlier quoted context omitted.

pImpl pattern is great for those who don’t care about performance but it’s inappropriate for most header libraries. You wouldn’t want a library that hides the implementation of std::vector for example. With a visible implementation the compiler compile e.g. operator[] down one x86 instruction. With a pImpl pattern it will be an indirect function call in all likelihood that will be hundreds of times slower. It can mak…

This depends on what you are building. Don't commit the sin of early optimization. Does a client of the framework you are writing -- which is probably using STL internally -- need a single instruction operation for adding a value for a call that you make less than 0.001% of the time? Optimization is about end results. Apply the Pareto Principle, and don't forget that your users also need to compile your code in a rea…

Overall user experience, mobile battery life, and many other metrics are really hard to fix by micro-optimizing a few functions. The key to a system that doesn't feel sluggish is being conscious of performance issues when making design decisions.

This pendulum swings back and forth, and we went from "every bit counts" madness of the early days, to the polar opposite of "just burn cycles, whatever".

Systems where every interaction feels sluggish are a pain to use, and often nearly impossible to refactor for better performance.

Post reply on HN