-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
1,514 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
--- | ||
title: (CMake Series) Part 1 - Introduction | ||
date: 2020-03-09 20:09:15 | ||
categories: [CMake] | ||
tags: [C/C++,CMake,Build Systems,Tutorial] | ||
author: xizhibei | ||
issue_link: https://github.com/xizhibei/blog/issues/133 | ||
--- | ||
<!-- en_title: cmake-1-introduction --> | ||
|
||
Since the last time I picked up C++ in [Why the Order of C++ Static Libraries Matters](https://github.com/xizhibei/blog/issues/100), I've embarked on a point of no return. | ||
|
||
Today, let's talk about CMake, a powerful tool for modern C++ projects. | ||
|
||
### Introduction | ||
|
||
Why do we need CMake? For C++ developers, they are accustomed to using GNU Make to compile C++ projects, which is fine for simple projects. Furthermore, there are so many open-source tools available, especially the autotools series, which are quite convenient. Many C++ projects still require you to use `./configure` to preprocess before compiling, which is much more convenient than using Make alone, thus very friendly to beginners (not to mention advanced users, as not many can make Vim fly). | ||
|
||
Personally, I find the autotools series less tidy (I really dislike it when I see a bunch of temporary files generated in the current directory; conversely, CMake's dedicated build directory is a godsend for my OCD). Since I haven’t used it much, I can't comment further or make detailed comparisons. Those interested can also check out the discussion on [What are the differences between Autotools, Cmake, and Scons?](https://stackoverflow.com/questions/4071880/what-are-the-differences-between-autotools-cmake-and-scons). | ||
|
||
In terms of accessibility, CMake is a blessing for beginners, although the tricky part is that you need to learn a new language, which is still very simple compared to programming languages. | ||
|
||
### Preparation | ||
|
||
In most cases, you can just install it directly with one of the following commands depending on your machine: | ||
|
||
```bash | ||
brew install cmake | ||
sudo apt install cmake | ||
pip install cmake | ||
``` | ||
|
||
Alternatively, you can directly download and install it from the [official website](https://cmake.org/download/). | ||
|
||
### A Simple Example | ||
|
||
Let's start with the simplest project. | ||
|
||
```cpp | ||
// main.cpp | ||
#include <stdio.h> | ||
|
||
int main(int argc, char *argv[]) | ||
{ | ||
printf("Hello world"); | ||
return 0; | ||
} | ||
``` | ||
You can then create a CMakeLists.txt in the current directory: | ||
```cmake | ||
# Minimum version of CMake required, you can also set a version range, such as 3.1...3.15 | ||
cmake_minimum_required (VERSION 3.0) | ||
# Project information, version number, and description can be set | ||
project (demo VERSION 0.1.0 DESCRIPTION "Demo project") | ||
# Create an executable named demo | ||
add_executable(demo main.cpp) | ||
``` | ||
|
||
Next, execute the classic four commands to compile the executable: | ||
|
||
```bash | ||
mkdir build | ||
cd build | ||
cmake .. | ||
make | ||
``` | ||
|
||
Unlike Make, CMake greatly simplifies the configuration process. With just a few lines, you can write powerful cross-platform compilation scripts. You can see from the output that a series of steps have been automated: | ||
|
||
``` | ||
-- The C compiler identification is GNU 7.4.0 | ||
-- The CXX compiler identification is GNU 7.4.0 | ||
-- Check for working C compiler: /usr/bin/cc | ||
-- Check for working C compiler: /usr/bin/cc -- works | ||
-- Detecting C compiler ABI info | ||
-- Detecting C compiler ABI info - done | ||
-- Detecting C compile features | ||
-- Detecting C compile features - done | ||
-- Check for working CXX compiler: /usr/bin/c++ | ||
-- Check for working CXX compiler: /usr/bin/c++ -- works | ||
-- Detecting CXX compiler ABI info | ||
-- Detecting CXX compiler ABI info - done | ||
-- Detecting CXX compile features | ||
-- Detecting CXX compile features - done | ||
-- Configuring done | ||
-- Generating done | ||
-- Build files have been written to: /home/xizhibei/demo/build | ||
``` | ||
|
||
Simple, right? Let's try a more complex example. | ||
|
||
### A Slightly More Complex Example | ||
|
||
As you add more features to your project, you'll need to update your CMakeLists.txt. | ||
|
||
Suppose your project structure looks like this: | ||
|
||
``` | ||
--- root | ||
|-- CMakeLists.txt | ||
|-- main.cpp | ||
|-- include/ | ||
|------ a.h | ||
|------ b.h | ||
|-- lib/ | ||
|------ a.cpp | ||
|------ b.cpp | ||
``` | ||
|
||
Corresponding changes are as follows: | ||
|
||
```cmake | ||
cmake_minimum_required (VERSION 3.0) | ||
project (demo VERSION 0.1.0 DESCRIPTION "Demo project") | ||
# Add a static library called demo_lib | ||
add_library(demo_lib STATIC lib/a.cpp lib/b.cpp) | ||
# Specify the location of header files | ||
target_include_directories(demo_lib PUBLIC ${CMAKE_SOURCE_DIR}/include) | ||
# As above | ||
add_executable(demo main.cpp) | ||
# No need to set target_include_directories again since we have already set includes for demo_lib, CMake will automatically add them | ||
# Link the demo_lib library to the demo executable | ||
target_link_libraries(demo demo_lib) | ||
``` | ||
|
||
Following a more standard approach, we should create a new CMakeLists.txt under the lib directory and include it with `add_subdirectory(lib)`. However, this approach is used here to save space. | ||
|
||
Thus, a simple C++ project is complete. | ||
|
||
### Other | ||
|
||
We've completed a simple example, but how do we add third-party dependencies? How do we add compilation options? How do we test? How do we add documentation? | ||
|
||
Don't worry, I'll gradually explain these in a lengthy series. | ||
|
||
### Ref | ||
|
||
1. <http://www.cmake.org/> | ||
|
||
*** | ||
First published on Github issues: https://github.com/xizhibei/blog/issues/133, welcome to Star and Watch | ||
|
||
{% post_link footer_en %} | ||
*** |
141 changes: 141 additions & 0 deletions
141
source/_posts/en/cmake-10-refine-compile-speed-and-program-performance.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
--- | ||
title: "(CMake Series) Part 10 - Enhancing Compilation Speed and Program Performance" | ||
date: 2020-07-29 17:31:23 | ||
categories: [CMake] | ||
tags: [C/C++, CMake, Performance Optimization] | ||
author: xizhibei | ||
issue_link: https://github.com/xizhibei/blog/issues/145 | ||
--- | ||
<!-- en_title: cmake-10-refine-compile-speed-and-program-performance --> | ||
|
||
Every C/C++ developer knows that as projects grow, compilation speed can start to drag, a love-hate situation: it tests our patience and drives us crazy when we're eager to modify code, yet it provides a perfect excuse for a break to walk or sip some tea when we need a pause. | ||
|
||
Additionally, the runtime speed of the program is another critical factor—slow compile times are bearable, but slow execution is not, not just for us but for our bosses and users too. | ||
|
||
That said, for our own development efficiency, enhancing compilation speed is indisputably beneficial. Today, let’s discuss how to optimize both compilation and program performance within CMake. | ||
|
||
### Compilation Speed Optimization | ||
|
||
#### Ninja Generator | ||
|
||
The default generator in CMake is Unix Makefiles, which uses the common `make` command. However, the Ninja generator is a better choice. If you haven't used it, I recommend giving it a try. | ||
|
||
#### CCache | ||
|
||
The simplest and most effective tool for speeding up compilation is enabling a compilation cache, and `ccache` is the tool we need. | ||
|
||
Its principle is straightforward: it wraps the compiler, receives compilation parameters and files, and when it detects that there's no corresponding cache, it calls the compiler to store the output in a file. If the compilation parameters and files haven’t changed, it retrieves the output directly from the cache file, greatly reducing recompilation time. | ||
|
||
In earlier versions of CMake (before 2.8 for Unix Makefiles and 3.4 for Ninja), there was no support for ccache, and we had to manually set `CMAKE_C_COMPILER` and `CMAKE_CXX_COMPILER` to prefix with ccache: | ||
|
||
```cmake | ||
set(CMAKE_C_COMPILER ccache gcc) | ||
set(CMAKE_CXX_COMPILER ccache g++) | ||
``` | ||
|
||
In newer versions, it's even easier: | ||
|
||
```cmake | ||
find_program(CCACHE_PROGRAM ccache) | ||
if(CCACHE_PROGRAM) | ||
message(STATUS "Setting up ccache ...") | ||
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) | ||
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) | ||
endif() | ||
``` | ||
|
||
Then, refer to [Using ccache with CMake][1] for the XCode generator. | ||
|
||
You might wonder how effective it is. Below are the results I got from running `ccache -s` on one machine: | ||
|
||
``` | ||
cache directory /home/me/.ccache | ||
primary config /home/me/.ccache/ccache.conf | ||
secondary config (readonly) /etc/ccache.conf | ||
stats zero time Tue Apr 7 16:06:27 2020 | ||
cache hit (direct) 41056 | ||
cache hit (preprocessed) 7179 | ||
cache miss 25047 | ||
cache hit rate 65.82 % | ||
called for link 10928 | ||
called for preprocessing 5929 | ||
compile failed 3055 | ||
preprocessor error 1325 | ||
can't use precompiled header 86 | ||
bad compiler arguments 56 | ||
autoconf compile/link 3428 | ||
no input file 616 | ||
cleanups performed 90 | ||
files in cache 25512 | ||
cache size 4.5 GB | ||
max cache size 5.0 GB | ||
``` | ||
|
||
In the past few months, it saved me approximately 65.82% of compilation time. | ||
|
||
#### Precompiled headers (PCH) and Unity builds<sup>[2]</sup> | ||
|
||
**Precompiled headers**: These significantly reduce the repetitive compilation time of C++ header files. You can add headers of some third-party libraries, like nlohmann/json, spdlog/spdlog.h, Boost, and rarely changed project headers into precompilation: | ||
|
||
```cmake | ||
target_precompile_headers(<my_target> PRIVATE my_pch.h) | ||
``` | ||
|
||
However, the intermediate files generated will be quite large, taking up significant disk space. | ||
|
||
**Unity builds**: As the name suggests, this involves merging multiple CPP files into a single compilation unit. This reduces the number of times the compiler needs to parse, optimizes the same templates, reduces the number of compiler invocations, and makes the linker more efficient. | ||
|
||
It’s simple to use: | ||
|
||
```cmake | ||
set(CMAKE_UNITY_BUILD ON) | ||
``` | ||
|
||
```cmake | ||
set_target_properties(<target> PROPERTIES UNITY_BUILD ON) | ||
``` | ||
|
||
However, these techniques are advanced and not suitable for all projects as they might **increase project maintenance costs**. If used incorrectly, they may lead to compilation failures. | ||
|
||
#### Other Tips | ||
|
||
- Switching from gcc to clang; | ||
- Changing from static to dynamic linking; | ||
- Upgrading to a high-performance machine with a better CPU and SSD, or even using a RAM disk (these are cases of trading money for performance but are very effective). | ||
|
||
### Program Performance Optimization | ||
|
||
For CMake, the simplest optimization is switching from Debug to Release mode. | ||
|
||
Additionally, consider [Interprocedural optimization](https://en.wikipedia.org/wiki/Interprocedural_optimization), which can be understood as a program-level Release mode because the ordinary Release mode optimizes at the individual file level. | ||
|
||
Not every compiler supports it, so you need to check first: | ||
|
||
```cmake | ||
include(CheckIPOSupported) | ||
check_ipo_supported(RESULT _IsIPOSupported) | ||
if(_IsIPOSupported) | ||
message(STATUS "Turning on INTERPROCEDURAL_OPTIMIZATION") | ||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) | ||
endif() | ||
``` | ||
|
||
In terms of performance optimization, compilers have limited capabilities; more often, optimization should occur during the coding phase of the entire program. | ||
|
||
Issues such as business logic, algorithms, and waiting times are not within the compiler's capabilities to resolve, yet they are crucial in impacting the final outcome. | ||
|
||
### Ref | ||
|
||
1. [Using ccache with CMake][1] | ||
2. [CMake 3.16 added support for precompiled headers & unity builds - what you need to know][2] | ||
|
||
[1]: https://crascit.com/2016/04/09/using-ccache-with-cmake/ | ||
|
||
[2]: https://onqtam.com/programming/2019-12-20-pch-unity-cmake-3-16/ | ||
|
||
|
||
*** | ||
First published on GitHub issues: https://github.com/xizhibei/blog/issues/145, welcome to Star and Watch | ||
|
||
{% post_link footer_en %} | ||
*** |
102 changes: 102 additions & 0 deletions
102
source/_posts/en/cmake-2-third-party-dependances-management.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
--- | ||
title: "(CMake Series) Part 2 - Third-Party Dependency Management" | ||
date: 2020-03-15 23:18:50 | ||
categories: [CMake] | ||
tags: [C/C++, CMake, Build Systems, Dependency Management] | ||
author: xizhibei | ||
issue_link: https://github.com/xizhibei/blog/issues/134 | ||
--- | ||
<!-- en_title: cmake-2-third-party-dependency-management --> | ||
|
||
Continuing from the last [(CMake Series) Part 1 - Introduction](/en/2020/03/09/cmake-1-introduction/). | ||
|
||
This time, let's talk about dependency management in CMake. | ||
|
||
### Dependency Management | ||
|
||
When we talk about dependency management in CMake, we are often referring to the management of dependencies in C/C++ projects. However, this ancient language still lacks a unified official dependency management tool to date. | ||
|
||
Looking at its successors, Ruby has `gem`, Node.js has `npm`, Golang has `go mod`, and Rust has `cargo`. | ||
|
||
You might mention that C++ introduced Modules in C++20, but currently, [compiler support](https://en.cppreference.com/w/cpp/compiler_support) is still insufficient, not to mention support from famous C++ projects. If in the future it becomes as simple as modern languages to install all dependencies with one command and use them directly, it would be greatly beneficial. | ||
|
||
So, what kind of support does CMake provide? Essentially, projects supported by CMake usually provide a file named `xxx-config.cmake` or `xxxConfig.cmake`. Their role is to provide the means to find and incorporate them into the current project for use. | ||
|
||
### CMake `find_package` | ||
|
||
Let's see what it looks like: | ||
|
||
```cmake | ||
find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE] | ||
[REQUIRED] [[COMPONENTS] [components...]] | ||
[OPTIONAL_COMPONENTS components...] | ||
[NO_POLICY_SCOPE]) | ||
``` | ||
|
||
Thus, if you need to include the OpenCV dependency, you need to write the following line before compiling your project files: | ||
|
||
```cmake | ||
find_package(OpenCV 3.4.9 REQUIRED) | ||
``` | ||
|
||
This line means that you need to find the OpenCV dependency with version `3.4.9` (version compatibility logic is controlled by the package itself if not `EXACT`), and it is mandatory, which is indicated by `REQUIRED`. Of course, if this dependency is not crucial, you can use the `QUIET` keyword. | ||
|
||
Then at the point of use: | ||
|
||
```cmake | ||
target_link_libraries(lib PUBLIC ${OpenCV_LIBS}) | ||
``` | ||
|
||
Note the `PUBLIC` keyword here, which means that if other libraries or executable programs depend on lib, you don't need to declare the need for OpenCV related libraries again, CMake will automatically add the dependency. Also, if you are using CMake version 3.5 or below, you also need to do this: | ||
|
||
```cmake | ||
target_include_directories(lib PUBLIC ${OpenCV_INCLUDE_DIRS}) | ||
``` | ||
|
||
Moreover, if your found dependency requires sub-module dependencies, like Boost, you would need to use `COMPONENTS` or `OPTIONAL_COMPONENTS`: | ||
|
||
```cmake | ||
find_package(Boost 1.50 REQUIRED COMPONENTS filesystem) | ||
target_link_libraries(lib PUBLIC Boost::filesystem) | ||
``` | ||
|
||
### Third-Party Dependency Management | ||
|
||
Now, the question arises, since I can use `find_package` to manage packages so conveniently, where do these packages come from? | ||
|
||
Friend, that's the key. If you're developing on a local machine, some can be installed directly (and there are also some cross-compilation libraries, which can be searched to confirm), such as on Ubuntu, if you need OpenCV, simply `sudo apt install libopencv-dev`; and on MacOS, `brew install opencv`. | ||
|
||
If you are cross-compiling, or if the current development library version in your system does not meet your requirements, then you will need to compile from source. | ||
|
||
At this point, the question arises, how should the source code be placed in our development project? Just copy it in? For those who have read my previous article on [how to clone a large Git project](https://github.com/xizhibei/blog/issues/131), you would know not to make this mistake. | ||
|
||
So, what should be done? It's simple, use `git submodule`. | ||
|
||
You can place the respective code repository in a specific folder, such as `extern` or `third_party`, etc. | ||
|
||
Then you can add it with a command like this: `git submodule add https://github.com/opencv/opencv -b 3.4 extern/opencv`. | ||
|
||
For users, they need to use `git submodule update --init --recursive`, or add the `--recursive` parameter when cloning. | ||
|
||
Then, you need to add extra configuration steps to your CMakeLists.txt. If the project's CMakeLists.txt supports being used as a module, you can directly add it as a subdirectory: `add_subdirectory(extern/opencv)`. | ||
|
||
I have tried this method, but encountered insurmountable cross-compilation issues at the time, so I gave up, as some projects' CMakeLists.txt are not standard, do not support being used as submodules of other projects, and not all projects support CMake. Of course, you can also modify by writing patches for the project or by writing specialized bash scripts to solve these issues. | ||
|
||
Here's a prerequisite, it requires the user to know the usage plan of `git submodule`. If you do not like this method, you have three other options: | ||
|
||
1. ExternalProject: Runs during build, cannot use `add_subdirectory` at configuration time. | ||
2. [DownloadProject](https://github.com/Crascit/DownloadProject): Runs at configuration time, can `add_subdirectory`; | ||
3. FetchContent (CMake 3.11+): The official version of the second solution, simpler but only available in higher versions, considering most machines have lower versions of CMake, it may require users to upgrade to use; | ||
|
||
Currently, in our team projects, we are using ExternalProject, because **as a separate step, it allows pre-compiling libraries dependent on various platforms, saving unnecessary time for other team members.** | ||
|
||
I've made the third-party dependencies compilable with one line of command, so every time it can be quickly compiled on a server with high-performance CPU, then the build artifacts are directly saved in a public storage space, and later others can directly download and use them, of course, the artifacts are signed for verification. | ||
|
||
Moreover, even the downloading step can be done by CMake during the configuration stage, greatly improving the team's development efficiency. | ||
|
||
|
||
*** | ||
Originally published on Github issues: https://github.com/xizhibei/blog/issues/134, feel free to Star and Watch | ||
|
||
{% post_link footer_en %} | ||
*** |
Oops, something went wrong.