C++ – Compile-Time vs Run-Time Configuration

cconfigurationperformance

I'm starting new project and need to decide on how to handle configuration. I recently run into suckless project. While it has its own issues, I really liked its approach to configuration – just a header file with static variables.

What are pros and cons of using compile-time options vs run-time options (== config file)? Would it be acceptable for product aimed for more tech-savvy users to use compile-time config? Does it still have benefits on modern systems?

I'm focusing modern desktop/laptops, only supported architecture likely will be amd64. Project is GUI application focused on accesing various informations.

Best Answer

This answer is specific to C++ (as indicated by the tag on this question).


Most other compiled languages (outside C and C++) do not have such consideration, because in those languages there is no benefit in moving configurations to compile-time. Outside of C and C++, conditional compilation is greatly discouraged or simply unsupported. Also, C and C++ compilers apply aggressive dead-code elimination and other compile-time optimizations so that code that are provably excluded by compile-time configuration will not exist in the binary.

This question may also become a non-issue when just-in-time compilation (JIT) is considered. It is widely speculated that JIT will be available to C++ some day.


From a miles-high perspective, there are three main considerations for compile-time vs runtime (or program-launchtime) configuration.


Necessity.

Necessity refers to software, user or legal requirements which are rigid and cannot be worked around, and therefore impacts whether a configuration is required to be compile-time or runtime.

For example, if there is a requirement that a configuration can be changed without restarting an already running application, then obviously that configuration has to be run-time modifiable.

If the project has an optional dependency on a third-party component, and if that component has licensing terms that make it inapplicable to some subset of customers, then you will have to provide a build configuration for including or excluding the linking and use of that third-party component from your project. Obviously that configuration has to be compile-time.


Burden / Overhead.

This includes the time cost and inconvenience cost of everything - user's time recompiling, code execution overhead (conditional check, reading configurations, etc), and many other things.

As Dan Pichelman points out in the comment, the time spent on recompiling will typically greatly surpass the time spent on anything else.

The only exception to this observation is, if for some other reasons, the code is already being recompiled frequently, say, daily or even hourly. This can happen when a continuous-integration (automated builds and deployments) system is already in place. In such environments, putting configuration into compile-time can be effective because there is no additional cost and only a minor latency from change to effect.


Bloat.

Bloat refers to the increase in size, number of build artifacts, or other quantities when one needs to support multiple configurations (whether compile-time or runtime), when compared to the same quantity when only one such configuration is needed.

For example, if one has the choice of using Algorithm A versus Algorithm B for a certain task:

  • Option 1: compile both algorithms into the binary, thus allowing the choice to be made at runtime. The binary size is the sum of the binary size of both algorithms, minus the size of shared binary parts.
  • Option 2: only compile one of the two algorithms into the binary. The choice is fixed at compile-time; the application/user won't be able to choose at runtime.
  • Option 3: make one build (binary) containing only Algorithm A, and another build (binary) containing only Algorithm B. Provide both to the user. A change of choice will require restarting the application using the binary of choice.
  • Option 4: refactor both algorithms into a generalized algorithm model, and maximize the reuse of logic, in the hope of reducing binary size.

It is obvious that Option 3 will have to largest bloat, because a lot of binary logic that is common to both will have to be duplicated. The bloat resulting from Option 1 appears to be reasonable in comparison to Option 3.

Option 2 lacks the runtime choice, so it is subject to the first criteria - necessity - depending on the software's and user's requirements.

Option 4 takes extra programming effort, but will have the least impact to burden and bloat compared to all other options.

Related Topic