C Performance optimization for the big picture

Mondo Sports Updated on 2024-02-01

C++ is a well-known programming language. This reputation has been mixed. On the plus side, C++ is so good that if you want to play a good programming language, you can't help but go head-to-head with C++. On the bad side, it's notoriously complex, hard to learn, and hard to use.

Whether C++ is good or bad, there's no denying that C++ is still a very popular and dynamic language. Following the release of the second version of the language standard after more than a decade of silence - C++11 - C++ releases new language standards every three years, each version providing improvements and new features while maintaining basic backward compatibility.

While there are new languages like Rust that are challenging C++ in the language space, it's undeniable that C++ is still the king of programming languages in the performance-oriented world. I don't even think that C++ is inferior to C in terms of performance - C++ can be stronger than C in the pursuit of extreme speed, and the main advantage of C over C++ is that it is simpler: whether it is to learn, to use, or to produce binary ** volume.

Today, we're going to talk about how C++ is so high-performing.

According to Bjarne, the main feature of C++ is the following two concerns:

Just like C, C++ provides itVery low-levelThe ability to manipulate data provides flexibility for developers. Like "high-level" languages, C++ offers a powerful abstraction (arguably more than most languages). And, compared to C, C++ doesMuch safer。This was true in the early days of language, let alone now.

C++'s type system is more restrictive than C, so while there has always been a claim that C++ is a superset of C, this has never strictly been true. Recently (2023) there was a case of a program crash, which in a nutshell is that a developer uses a two-dimensional array of char (char names[max names] [max name len]) and passes it to a function ...... that receives char** argumentsThis is of course wrong, but the C compiler gave an alarm, but the compilation still did not fail. If this is C++, the compiler will simply report the error and not pass it.

And the second point,Zero overhead abstraction, which is critical to the performance of C++. We have a lot of abstractions, and there is no additional overhead in using them. In some cases, using these mechanisms has a "negative overhead" – users "can use the language very safely, and they can get very high performance." At the same time, C++ also gives "customizers" the ability to write libraries that are closer to the use case according to their own needs, which can further facilitate "users".

Of course, customization requires a very high level of skill from the programmer. Beginners to C++ need to master the use of the C++ standard library - if you use the standard library well, you can get very good performance. As the full version of Gartner's famous sayings:

And C++ already provides quite a few mechanisms that allow us to easily achieve high performance, far more than Gartner's 12% in many scenarios.

For example, the sort of the C++ standard library and the Qsort of the C standard library: when the optimization is turned off, you get a 1:2 in a test scenario5 performance difference, C++ seems to be a lot slower; But once -o2 is turned on (inlining is allowed), the performance difference between the two mutates to 35:1, C++ is several times better than C! This is called "negative overhead". C++ is simpler, more intuitive, and more performant than C's. The reason for this is that C++'s function object and template mechanism allows the compiler to be better inlined, resulting in higher performance.

Therefore, the first step in learning to use C++ well is to make good use of the basic mechanics of C++ and the standard library, and understand the performance cost of the different mechanisms of the standard library, including time and space.

The first thing you need to know about learning C++ in any case is destructors and RAII (Resource Acquisition is Initialization) idioms. Yes, although C++ was born as "C with Classes", classes are not the same as object-oriented, and support for object-oriented programming is not the most important feature of C++. What's special about C++'s custom types isn't polymorphism, it's about customizing their behavior—most importantly, what should be done when the object is destroyed. Destructors and the RAII idioms that destructors bring are the most important features of C++ and the key to resource management in C++.

Overloading is another very important C++ feature。In addition to the convenience of not having to distinguish between process char, process string, and process int in name, it's also important for generic programming and for moving semantics, a fundamental feature of modern C++. Stripped of the syntactic details, moving semantics essentially makes it easy for programmers to distinguish between objects that will continue to be used and objects that will not be used in the future, allowing the latter to be "stolen" by using the overloading of constructors and assignment operators. For a normal vector, the copy cost is o(n) or higher (if the vector member is a container or other object with high copy cost), but the movement cost is usually (yes, just usual; However, you usually don't encounter this exception either) is o(1), constant complexity. This is a common way we can efficiently pass objects in C++.

Probably the most common components in the C++ standard library are strings and containers. They are all optimized for movement. Of course, in addition to this basic performance point, containers have their own special performance points, such as the difference in insertion performance in different situations. These are all areas to learn.

For example,vectorThe insertion performance is better at the tail and worse at the middle. However, further aware, you need to know that the prerequisite for good tail insertion performance is that the type of element has a good implementation of movement and that the movement constructor is declarednoexcept!If you implement a movement constructor with an o(1) overhead, but forget to declare it as noexcept, that's still a noexcept, and the vector's tail insertion still has performance issues.

For example, lists are highly performant whether they are inserted from the beginning, end, or middle. However, for lists and vectors of the same element, the traversal performance of lists can be an order of magnitude worse. The reason for this is not exactly C++ knowledge, but related to the cache organization of the hardware. If we care about performance, these are all things to know.

We've already mentioned templates, but both strings and containers are templates, and behavior can be customized with template parameters and allow for efficient inline optimization. Templates are, of course, one of the more complex parts of C++, but the basics are fairly simple: a vector is a vector with an int, and it's no different from a normal class — except that the template creator doesn't have to manually create different classes for different types.

Using C++ well and getting satisfactory performance in your project is certainly not the only one mentioned above. At the most basic level, we also need to understand the standard library algorithms and use concurrency and parallelism appropriately to get the most out of the hardware. We'll leave it at that.

When we are familiar with C++, we will gradually stop being satisfied with the "standard**" of the C++ standard library, we will look for our own third-party libraries, and even build our own wheels to meet the specific needs of the project。At this point, we need to learn more about the advanced features of C++. We need to understand further details about the template, especially specialization. We need to understand SFINAE and template metaprogramming. We need to understand constexpr and the ease it brings to compile-time programming. C++ users may not be concerned about these issues for a while, but customizers, or frameworkrs and toolbuilders in the project, must understand these advanced features of C++ to provide a solid foundation for your project.

For example, the standard library for C++ provides lists, bidirectionally linked lists. There is nothing wrong with this library, but it has unsatisfactory time and space overhead in some use cases, such as our objects need an additional LRU (least recently used) algorithm to discard the oldest of them in addition to normal management. You can use a list, of course, but you need to insert an object every time you insert it, and you need to think about what exactly is in the list in addition to the heap memory allocation overhead. Maybe with a smart pointer? Is the situation getting more complicated?

In this case, the most reasonable option is to use some kind of intrusive list, an intrusive linked list that does not require memory management on every insert or delete. The C++ standard library does not provide this functionality. You can either use the container provided in boost, or write a new one. For this example, boost is probably good enough. But there will always be problems that can't be solved by off-the-shelf libraries, and it's only natural to use the advanced features of C++ to build your own wheels. We can customize it and use it similarly to an existing container with no additional learning costs.

Or,Perhaps you want to use an allocator to create a container memory pool to provide efficient use of memory. This is also very easy to accomplish in C++, as long as you understand the appropriate customization mechanics. According to the onion principle, you can ignore these customization points and just use C++, which is the easiest;You can also "cut" the standard library and use it in your favorite way - of course, this method is really the same as cutting an onion, and it is easy to cry. But it does help you get the highest possible performance.

That's all for this time

Related Pages