4 ms·
> C++ can be vastly simpler and more readable, while retaining 100% control over memory and layout. But unlike assembly, you don't have control over CPU flag r
by jnxx 6y ago
> C++ can be vastly simpler and more readable, while retaining 100% control over memory and layout.
But unlike assembly, you don't have control over CPU flag registers and special instructions. Can you explain why and when, exactly, 100% control over memory is needed? Outside of OS kernels, high-frequency trading engines, and real-time control systems? If using assembly is too costly, why is using C++ economical?
And, do you really have control what happens at the machine level? Second and third level caches, hyperthreading, multicore, out-of-order execution, CPU affinity, NUMA, TLB, virtual memory, all that stuff, there is nothing in C++ which controls that.
And I would even argue that this in most cases is a good thing, because unless you are writing an OS kernel, it is none of your programs business - what it should focus on is the logical flow of the computation.
And this is what modern optimizing compilers do: They transform expressions into machine code which has an equivalent observable effect, and executes that what the programs specifies as fast and efficient as possible. Fi you write:
int a = 0;
for (int i=0; i < 100; i++)
a += i;
printf("a = %i\n", a);
you could be tempted to believe that the CPU executes increments on a 32-bit or 64-bit register. This is not what happens on a modern optimizing compiler. Look at that link (which uses -O3):
https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(fontScale:14,j:1,lang:___c,selection:(endColumn:6,endLineNumber:2,positionColumn:6,positionLineNumber:2,selectionStartColumn:6,selectionStartLineNumber:2,startColumn:6,startLineNumber:2),source:'//+Type+your+code+here,+or+load+an+example.%0Aint+f(int+num)+%7B%0A++++int+a%3B%0A++++for+(int+i%3D0%3B+i+%3C+100%3B+i%2B%2B)%0A+++++++a+%2B%3D+i%3B%0A++++return+a%3B%0A%7D'),l:'5',n:'0',o:'C+source+%231',t:'0')),k:50,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:cg102,filters:(b:'0',binary:'1',commentOnly:'0',demangle:'0',directives:'0',execute:'1',intel:'0',libraryCode:'1',trim:'1'),fontScale:14,j:1,lang:___c,libs:!(),options:'-O3',selection:(endColumn:1,endLineNumber:1,positionColumn:1,positionLineNumber:1,selectionStartColumn:1,selectionStartLineNumber:1,startColumn:1,startLineNumber:1),source:1),l:'5',n:'0',o:'x86-64+gcc+10.2+(Editor+%231,+Compiler+%231)+C',t:'0')),k:50,l:'4',n:'0',o:'',s:0,t:'0')),l:'2',n:'0',o:'',t:'0')),version:4 https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(fontScal...
- the compiler reduces it to a single move and return instruction. This is not specific to C++ compilers - a good Lisp or D or Ocaml compiler will to the same, while managing the memory for you.
Now, control is, as in psychology, a double-edged sword. It allows you to take influence, but too much control is not a good thing, as the example of micro-management shows. In the case of the compiler, if you control too much, it interferes with the compiler's job to transform your expression into efficient equivalent machine code. The fine-tuned code you write today might be optimized for some of the modern hardware, but while it will probably still compile, it might be much less than optimal 15 years from now, and in the case of C++ without the compiler having any leeway to optimize it because you told it way to explicitly what to do.
It also interferes with your job to produce clear, valid, and readable algorithms and code. And the numerous controls which C++ gives make for a very broad and very fuzzy interface, which in turn makes it more difficult to produce good code and hard to do that in a way which is both reliable, strictly valid, and easy to understand. The issue with non-ASCII characters in words in the original article is a good example. Here is another one:
Take
include <vector>;
include <algorithm>;
std::vector<bool> vb(100);
std::fill(vb.begin(), vb.end(), false);
is this valid code?
The C++ reference says:
https://en.cppreference.com/w/cpp/container/vector_bool https://en.cppreference.com/w/cpp/container/vector_bool
"Since its representation may be optimized, std::vector<bool> does not necessarily meet all Container or SequenceContainer requirements. For example, because std::vector<bool>::iterator is implementation-defined, it may not satisfy the LegacyForwardIterator requirement. Use of algorithms such as std::search that require LegacyForwardIterators may result in either compile-time or run-time errors. "
Do you think this is funny?
- bsdubernerd 6y agoYou don't have direct control over caches, however if you have control over memory layout you can indirectly influence how the code performs by leveraging the proper cache sizes/lanes. If that's your goal, you cannot focus on program flow alone, and that's the main point. Low-level and high-performance code is not "pretty" by modern standards, as it often requires DMA and tight control over data placement in order to fully leverage instruction-level and hardware-level parallelism. The hardware influences how the data structure layout should be first, and we work on top of it. We abstract structs and containers that capture this layout, so that we can still have a readable program flow at the end. In this sense, C++ can still be used as a glorified assembler, without the need to drop down to ASM for the simple stuff as your for loop: nobody wants to do that (me included). But contrarily to C, you can avoid a ton of preprocessor macros and get improved type checking, while still using ASM in selected spots if needed. Your example about vector<bool> is not really surprising. Do you want the iterator to be efficient, or consistent? Tough choice, depending on the scenario. It's annoying that it's not standardized in one form or the other. I remember I was fretting over this detail over 10 years ago, but in actuality I used vector<bool> exactly 0 times in my career so far. Again, please don't consider this as if I was praising C++. There are _many_ areas, including lack of standardization in the stdlib area (vector<bool> being one of many), that I don't like. I just want to say that it's an incredibly versatile tool which is very hard to replace given the same constraints.
- jnxx 6y ago> Low-level and high-performance code is not "pretty" by modern standards, as it often requires DMA and tight control over data placement in order to fully leverage instruction-level and hardware-level parallelism. This is true if you look at implementations like these: https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/rust-gpp.html https://benchmarksgame-team.pages.debian.net/benchmarksgame/... for example: https://benchmarksgame-team.pages.debian.net/benchmarksgame/program/mandelbrot-rust-8.html https://benchmarksgame-team.pages.debian.net/benchmarksgame/... vs. https://benchmarksgame-team.pages.debian.net/benchmarksgame/program/mandelbrot-gpp-4.html https://benchmarksgame-team.pages.debian.net/benchmarksgame/... I'd say the C++ code in this particular case is not only much longer but also uglier. However, how much of a fraction of C++ code in use does really require direct control over DMA calls? If you could get an at least equally fast result as when writing "normal C++" in Rust, with more safety and less effort for debugging, would this not be tempting for many people using C++? Because this is what I observed in my case. And this given that I've written no more than a few hundred lines of performance-critical code in Rust, and have worked for > 10 years on performance-critical code in C++ and C.