aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/blog/2024-04-30-taming-cpp-motivation/taming-cpp-motivation.md175
1 files changed, 175 insertions, 0 deletions
diff --git a/src/blog/2024-04-30-taming-cpp-motivation/taming-cpp-motivation.md b/src/blog/2024-04-30-taming-cpp-motivation/taming-cpp-motivation.md
new file mode 100644
index 0000000..3aef0c7
--- /dev/null
+++ b/src/blog/2024-04-30-taming-cpp-motivation/taming-cpp-motivation.md
@@ -0,0 +1,175 @@
1# Taming C++, episode 1: motivation
2
3C++ is pretty much the standard language in the software industry for
4any project where performance matters. I have used it a fair bit in
5the past - during my [IOI](https://ioinformatics.org) time and for
6a university course - but I used it only as "C with
7[STL](https://en.wikipedia.org/wiki/Standard_Template_Library)". I have
8also reviewed it for my job interview a couple of years ago, but I have
9not touched it since then. Given that I am at the early stages of my
10career as a software developer and that I am interested in performance,
11it's about time I learn modern C++!
12
13## Motivation
14
15As much as I like programming and learning new stuff, this is something
16that requires some effort. And it turns out that keeping a copy of
17[A Tour of C++](https://www.stroustrup.com/tour3.html) on my desk and
18looking at its cover from time to time does not provide sufficient
19motivation to take up this new task.
20
21Starting a new project would be a good way to start playing with a new
22language, but at the moment I would rather keep working on my ongoing
23projects than start a new one. Writing a (series of) blog post(s)
24where I share my experience with the rest of the world seemed like a
25good alternative, but I needed some extra push to get started.
26
27Finally, a few weeks ago I have discovered Bert Hubert's series of posts
28[*Modern C++ for C programmers*](https://berthub.eu/articles/posts/c++-1).
29The first post in that series showed an example that got my attention:
30for a task as simple as sorting a list of integers, using their respective
31standard libraries, C++ outperforms C by a significant margin!
32
33## Performance
34
35To get started with modern C++, I decided to repeat Bert's experiment.
36You can find the code I wrote in
37[this repository](https://git.tronto.net/taming-cpp).
38
39To sort a list of a hundred million integers, in C we can use `qsort()`
40from `stdlib.h` (see
41[sort.c](https://git.tronto.net/taming-cpp/file/sort-benchmark/integers/sort.c.html)):
42
43```
44qsort(a, ARRAYSIZE, sizeof(int), compar);
45```
46
47Where `compar()` is a comparison function such as
48
49```
50int compar(const void *x, const void *y) {
51 return *(int *)x - *(int *)y;
52}
53```
54
55In C++ we can use `sort()` from `algorithm` (see
56[sort.cpp](https://git.tronto.net/taming-cpp/file/sort-benchmark/integers/sort.cpp.html)):
57
58```
59std::sort(a, a+ARRAYSIZE,
60 [](const int &x, const int &y) { return x < y; });
61```
62
63Here we use a
64[lambda expression](https://en.cppreference.com/w/cpp/language/lambda)
65instead of a comparison function.
66
67Modern C++ also offers parallelized
68version of common algorithms as part of the standard library, so if
69we want to completely humiliate poor C we can use this (see
70[sort_parallel.cpp](https://git.tronto.net/taming-cpp/file/sort-benchmark/integers/sort_parallel.cpp.html)):
71
72```
73std::sort(std::execution::par, a, a+ARRAYSIZE,
74 [](const int &x, const int &y) { return x < y; });
75```
76
77Parallelizing stuff like this looks almost like cheating, but it wasn't
78without some struggle - see the "Gotchas" section below.
79
80So, what is the result? These are the times I get on my
81[Debian 12 desktop](https://sebastiano.tronto.net/blog/2023-10-15-build-time)
82(AMD Ryzen 7 7700):
83
84```
85C time for 100000000 numbers: 9.815525s
86C++ time for 100000000 numbers: 4.87299s
87C++ time for 100000000 numbers (parallel): 0.488956s
88```
89
90Even without parallelization, C++ is twice as fast as C!
91
92You might think that C++ is somehow optimizing for integer
93sorting. But this is not the case, as demonstrated by a similar experiment
94with *pairs* of integers (see
95[sort.c](https://git.tronto.net/taming-cpp/file/sort-benchmark/pairs/sort.c.html),
96[sort.cpp](https://git.tronto.net/taming-cpp/file/sort-benchmark/pairs/sort.cpp.html) and
97[sort_parallel.cpp](https://git.tronto.net/taming-cpp/file/sort-benchmark/pairs/sort_parallel.cpp.html)
98- I used a non-standard mixed lexicographic order to be reasonably sure the
99compiler does not come up with any ad-hoc optimization):
100
101```
102C time for 100000000 pairs: 12.383896s
103C++ time for 100000000 pairs: 6.50033s
104C++ time for 100000000 pairs (parallel): 0.713616s
105```
106
107## Complexity is not for nothing
108
109As explained by Bert in his post, the reason
110the C++ version is faster is that by using
111[templates](https://en.cppreference.com/w/cpp/language/templates)
112the compiler can inline the call to the comparison function. C is not
113as sophisticated, so this is not possible: the only way a standard
114library function can call your custom code is via a
115[function pointer](https://en.wikipedia.org/wiki/Function_pointer).
116Regardless of possible performance issues with pointer
117dereferencing, calling a function without inlining it always causes
118some overhead. This is completely negligible for larger tasks, but for a
119small function that is called millions of times it makes a big difference!
120
121And all of this without even considering the elephant in the room, that is
122the fact that with virtually zero extra effort - but again, see the "Gotchas"
123section below - one can parallelize C++ STL algorithms and make them an order
124of magnitude faster on modern hardware! In C I would have needed to write a
125parallel sort on my own with something like
126[pthreads](https://en.wikipedia.org/wiki/Pthreads).
127
128Lesson learned for a C developer: sometimes the extra complexity of
129other languages does bring some benefit.
130
131## Gotchas
132
133I mentioned above that I had some trouble compiling and running the
134parallel version of my code. In fact it took me almost two hours to make
135it work! This was for a couple of reasons.
136
137The first reason is that, at least on Linux with GCC + GLIBC, the C++
138standard library requires
139[TBB](https://en.wikipedia.org/wiki/Threading_Building_Blocks). I
140figured this out rather early, and it did not take me long to learn
141that I had to add an `-ltbb` option to my command line either. What
142took me an incredibly long time to understand is that `-ltbb` had to
143be put *at the end* of the command line options! To make it clear,
144something like this:
145
146```
147$ g++ -O3 -std=c++20 -ltbb -o sort sort_parallel.cpp
148```
149
150does not work, you have to write
151
152```
153$ g++ -O3 -std=c++20 -o sort sort_parallel.cpp -ltbb
154```
155
156Another problem I had was caused by my use of macros. You can see
157in the source files that I am using an `ARRAYSIZE` macro for the
158size of the array, and I provide a value for it at compile time.
159Originally I had called this macro `N`, but this clashed with some
160internal name used in TBB, and I got all sorts of walls of text
161of weird template errors - something C++ is infamous for.
162
163The last issue I had was that I was stupid and tried to compile my
164C++ code with a C compiler. Indeed, while debugging the issue with
165`-ltbb` I tried switching to [clang](https://clang.llvm.org), because
166I sometimes get better error messages with it. But instead of using
167`clang++` I used `clang`, which only compiles C.
168
169## Until next time?
170
171Now that I have broken the ice with C++ I think it will be easier
172to continue studying it. I may or may not keep writing about this here:
173turning this into another blog series may be a good way to force myself
174to do this regularly, but on the other hand it may not be interesting
175for my readers. We'll see!

Generated with cgit - Back to sebastiano.tronto.net