Have an amazing solution built in RAD Studio? Let us know. Looking for discounts? Visit our Special Offers page!
C++CodeNews

C++Builder Optimization Guide with TwineCompile

34 banner blog jomitech cbuilder 660x300

In this post, we’re going to be covering tips and tricks on how you can optimize your c++ software projects to compile as fast as possible. While we’ll definitely be spending some time looking at optimizations geared for use with TwineCompile, some of these tricks apply to C++Builder in general.

C++Builder, and TwineCompile include a lot of functionality that’s designed to optimize compile speed but it often has to be tuned because every project is unique and has different conditions and setups. We’ll take a look at various tips and when and how to apply them to different project types and structures.

Trick #1 – TwineCompile

The first trick is easy – start using TwineCompile! TwineCompile is an IDE plugin that integrates into C++Builder and adds multi-threading, caching, and other powerful enhancements to the C++Builder compilers (both Classic and CLANG). In some cases, simply installing TwineCompile will make projects compile 50x faster on latest-generation build workstations.

TwineCompile is available free to all C++Builder 10.4.1+ users with an active Update Subscription via the GetIt Package Manager. This should be the first step in the quest for faster compile times as you can take advantage of these performance enhancements without needing to make a single change to your project structure or files (unlike the following tips).

To get TwineCompile, open the GetIt Package Manager in the IDE and search for TwineCompile. Follow the instructions to install it.

TwineCompile’s IDE integration will automatically hook the IDE Compile/Make/Build commands to invoke TwineCompile instead of the IDE’s build process. This allows you to continue to use the IDE as you do today, and take full advantage of the compile performance offered by TwineCompile.

C++Builder Optimization Guide with TwineCompile 1

TwineCompile Trick #2 – CLANG Powered PCH (Pre-compiled headers)

C++Builder has long had a secret weapon that can work magic on slow compile times. Often the single most effective enhancement is to properly set up and use pre-compiled headers on a project. The introduction of the CLANG compiler significantly improved this functionality and made it much easier to use. If your project is currently using the CLANG compiler, I would highly recommend taking advantage of the PCH support in CLANG as it will make an enormous difference to your compile times. If your project is still using the Classic compiler, I would recommend updating it to use the CLANG compiler – you’ll not only get the ability to use a modern, standards-compliant C++ compiler with C++v17 support, but you’ll also be able to use the CLANG PCH system.

banner c contest 1220x300 3913431

The concept behind pre-compiled headers is fairly simple – take a set of headers that are used in most (if not all) units of a project and compile them into a binary representation. When a unit of the project includes these pre-compiled headers, the compiler does not have to re-process/compile the headers from scratch, but can simply load the pre- compiled representation from disk and move on to compiling the code in the unit.

Some discipline is required as the header order in every unit must be identical to ensure that the compiler can use the pre-compiled header. In addition, the pre-compiled header should not contain headers that are frequently modified, as the overhead of compiling the headers is fairly significant and you will lose all the advantage of the pre-compiled header if it has to be rebuilt all the time.

  1. Start by analyzing your project and identifying the headers that most of the units include. This may require some re- organization to make most or all of the units to include the same header set. Note that including additional, unused,

headers does not have a negative effect, so if some units don’t use all the headers, it’s perfectly fine. The more headers that are pre-compiled, the more effective it will be at improving your compile times.

  • If your project already has a PCH header file (such as the PCH .h file auto-generated by the IDE for new projects), skip down to step #6.
  • If your project does not have a PCH header file (such as the PCH .h file auto-generated by the IDE for new projects), add a new Header file to the project:
C++Builder Optimization Guide with TwineCompile picture2
  • Save the new header file with a descriptive name such as ProjectNamePCH.h
  • Right-click the header file in the Projects pane and select the Use for Precompiling option:
C++Builder Optimization Guide with TwineCompile picture3
  • Open this precompiling header file and put all the include statements that you collected in step #1. For example:

#include  <vcl.h>

#include  <tchar.h>

  • Go through all the units in your project and remove the #include statements that refer to the headers located in the PCH header. At the top of each unit, before any other statements, put the following pragma statement to tell the compiler that the unit is not adjusting the pre-compiled header:

#pragma  hdrstop

  • Open the Project Options, go to the Pre-compiled headers section under C++ Compiler and select Generate And Use for the PCH usage option
  • Build the project. It will spend some time compiling the PCH header file into the binary representation, and then the subsequent units will compile many times faster, as these headers will no longer have to be processed for each unit over and over again.

Trick #3 – TwineCompile single-header PCH for the Classic compiler

If your project is still using the Classic compiler and you’re unable to migrate it to the newer CLANG compiler, it’s still possible to gain significant compile speed gains by using pre-compiled headers due to the classic compiler having an amazingly powerful PCH system. TwineCompile has been built to take full advantage of this system and arranges the build process so that the classic compiler is automatically invoked with the right parameters for each file that is using the pre-compiled header.

Unlike the CLANG compiler, the classic compiler is designed to generate and use the pre-compiled header as part of compiling the regular source files. There is no separate compile step required to compile a file that has been designated as the pre-compiled header file.

There are two ways to set up a project to use a single-header PCH with the classic compiler:

  1. Using an injection command to automatically include the single-header PCH file into every unit as part of the compile process.
  2. Manually adding the #include statement for the single-header PCH file at the top of every unit.

The first approach is obviously the easiest as it takes care of making sure the header is available in every unit automatically. The second approach can be useful as the header is actually included in each unit, so it’s available for browsing and other uses such as code analysis tool.

  1. As with the CLANG steps, start by analyzing your project and identifying the headers that most of the units include.

This may require some re-organization to make most or all of the units to include the same header set. Note that including additional, unused, headers does not have a negative effect, so if some units don’t use all the headers, it’s perfectly fine. The more headers that are pre-compiled, the more effective it will be at improving your compile times.

  • Create a new Header file in your project:
C++Builder Optimization Guide with TwineCompile picture4
  • Save the new header file with a descriptive name such as ProjectNamePCH.h
  • Open this precompiling header file and put all the include statements that you collected in step #1. For example:

#include  <vcl.h>

#include  <tchar.h>

5a. Approach A – if you want use the Injection functionality, open the Project Options, go to the Pre-compiled headers section under C++ Compiler, and enter the header file name into the Inject header option:

C++Builder Optimization Guide with TwineCompile picture6

5b. Approach B – if you want to manually add the header into your units, place the #include statement for this header file followed by a #pragma hdrstop line at the very top of any units. For example:

#include  "ProjectNamePCH.h"

#pragma  hdrstop

  • Open the Project Options, go to the Pre-compiled headers section under C++ Compiler:

6a. Select the Generate And Use option for PCH usage. This instructs TwineCompile to use the first source code file to generate the PCH file and use it for all subsequent units.

6b. Select the Cache precompiled headers option

6c. Enter a name for the generated PCH file into the PCH Filename option. For example: Project.csm. This file will contain the binary data compiled from the header selected as the pre-compiled header.

The options should look something like this:

C++Builder Optimization Guide with TwineCompile picture7
  • Build the project. TwineCompile will automatically adjust the compiler options for the files being compiled so that the PCH file will be generated and used as part of the compile process, with a significant improvement in the compile times.

Trick #4 – TwineCompile automatic PCH for the Classic compiler

For projects that cannot be migrated to the CLANG compiler and don’t have the project structure to support a single PCH file that will work for all units, hope is not lost. There is some advanced PCH functionality in the Classic compiler, coupled with TwineCompile, that provides most of the performance advantages of a tuned pre-compiled header for these types of projects. The Classic compiler supports the concept of a “adaptive” (my words, not the official description) pre-compiled header that is continually created and adjusted as part of a project build to support the

different variations of headers that the project’s units may use. The trick is getting it to work in your project and that’s where TwineCompile comes in.

  1. Open the Project Options, go to the Pre-compiled headers section under C++ Compiler:

1a. Select the Generate And Use option for PCH usage. Note that this project does not have any sort of PCH structure, so normally this option would cause more problems than it solves.

1b. Select the Cache precompiled headers option

1c. Enter a name for the generated PCH file into the PCH Filename option. For example: Project.csm. This file will contain the binary data compiled from the header selected as the pre-compiled header.

The options should look something like this:

C++Builder Optimization Guide with TwineCompile picture8
  • Open the TwineCompile Options from the TwineCompile menu, go to Pre-Compiled Headers and select the “Use PCH file for each thread” option. Click OK.
C++Builder Optimization Guide with TwineCompile picture9
  • Build the project. TwineCompile will automatically adjust the compiler options for each file so that they can generate and use a PCH file that will adapt for the different variations of the headers included in each unit. The performance will not be quite as good as a proper PCH setup, but will be significantly better than if PCH was not used at all.

TwineCompile Trick #5 – Optimizing your project layout

Or to put this trick another way – minimizing the number of files that need to be compiled when a change is made, but balancing that against the time needed to link.

A common tactic used to optimize C++ compile times is to break projects into lots of small libraries or packages. The idea is that when any code is changed, only the few files in this project will need to be recompiled. The recompiled library can just be relinked with the other libraries to form the final result.

There are two reasons why this is often a bad idea, especially if you are using TwineCompile:

  1. You are trading compile time with link time. Advice that recommends this approach is generally oriented to slower C++ compilers such as gcc, where the link time is much faster than the very slow compile times. So it can be helpful to offload the work to the linker. The C++Builder compilers (both CLANG and Classic) are fairly fast for C++ compilers, so adding 15 link steps to avoid compiling 30 C++ files often results in a significant loss of performance.
  • Linking is a serial process. Compiling is parallel. When you’re using TwineCompile, C++ files are compiled in parallel, so the more units that can be compiled simultaneously, the more performance can be realized. A single project with 200 C++ units and a single link step will build many times faster than 10 projects with 20 C++ units, each with their own link step.

As with most things in life – there is a tradeoff where a divided project structure does make sense. Unfortunately, there is no single right answer for how a project should be structured, so I’ll leave you with the following guidelines:

  1. Minimize the number of files that have to be compiled for any change to the source.
  2. Minimize the number of libraries, packages or applications that have to be linked. Even static libraries, while fairly fast to link, have an overhead.
  3. Maximize your processor resources. The linker will only use one core, 16 C++ files on an 8-core + HT processor will use all 16 logical cores. So compiling those 16 files can use 16x more resources than the linker can.

Trick #6 – SORTA Compile

One of TwineCompile’s most powerful features is its automatic background compile system. Essentially, this feature automatically compiles your modified source code files in the background, so that when you click Make or Run, there are no files to compile, everything is up to date, and the build process just has to link the application.

Unlike many of the tricks above, this one is not directly tied to making the compile or build process faster, but aims to reduce or even eliminate the need to compile the source code.

To enable SORTA Compile, open the TwineCompile Options, go to the SORTA Compile section, and select the Enable SORTA Compile option:

C++Builder Optimization Guide with TwineCompile picture10

There are a number of triggers that are used to invoke this functionality, including when a unit is saved, when the editor tab changes or when a file is modified and not touched for a period of time.

Using SORTA Compile effectively does require an adjustment to the coding workflow, but can provide enormous benefits to the traditional four-step project evolution process: Edit, Compile, Run, Test, repeat.

TwineCompile Trick #7 – Static versus Dynamic Linking

If you apply the tricks mentioned above to your projects, you’ll probably find that you’ll start waiting for the linker all the time. Waiting for the compiler will all but disappear completely. This is both a good and bad thing. Good because it

means you’re saving all this time, bad because there’s not much else you’ll be able to do with optimizing the build performance.

With that said, some applications can be re-structured to put different pieces of functionality into separate DLLs that are loaded by the application at runtime. This means that when code changes are made to a certain part of the application, only that specific code is compiled and linked into its own DLL file. The rest of the application does not need to be rebuilt because it’s linking to this DLL at runtime.

Obviously, this approach adds the deployment and management overhead of extra DLL files that have to be bundled with the application, but the savings in build performance can often be worth the extra overhead.

Trick #6 – Hardware

Our last trick is an old and well known trick, often known as “throwing hardware at the problem”. While newer, faster hardware in the past only provided minor benefits, today’s latest hardware advances, coupled with TwineCompile’s ability to maximize the use of system resources, can provide some significant benefits.

There are two key areas which should be focused on:

1.  Disk performance

This means SSD drives using NVMe/PCIe connections. SATA SSDs are good, but lack the random access performance of SSDs that operate over the PCIe bus.

Spindles are a definite no-no. If your projects, temp directories, operating system or C++Builder installation path is on a spindle, your build performance will be abysmal.

2.  CPU cores

Straight-line single-core performance for C++ compiling is less useful these days than having many cores. Go for the maximum number of cores that are available.

AMD Threadripper processors with 64 cores (128 threads) are ideal for C++ compiling in C++Builder with TwineCompile. Note that for a processor like this, if you have fewer than 128 C++ files in your project, you’re wasting CPU resources – this goes back to the point we made in Trick #5 about optimizing your project layout to maximize the use of CPU resources by compiling lots of files in the same project.

Final Thoughts

We have come a long way since the old days of C++ compiling when builds took a long time and there was nothing that could be done about it. Tools and techniques have been developed that make a significant difference to projects of all sizes. We’ve covered a number of these in this post and hopefully you’ll be able to apply them to your own projects to make your C++ development experience faster, simpler and easier.


RAD Studio C++ Builder, why not try it today?

See What's New in 12.2 Athens See What's New in 12.2 Athens Dev Days of Summer 2-24

Reduce development time and get to market faster with RAD Studio, Delphi, or C++Builder.
Design. Code. Compile. Deploy.
Start Free Trial   Upgrade Today

   Free Delphi Community Edition   Free C++Builder Community Edition

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

IN THE ARTICLES