I have never really done a large amount of C programming but am in the middle of teaching myself low latency C++. Would it do more harm than good to read the K&R C programming book? I am a bit worried I will be reading about C conventions which may no longer hold in C++ (especially with C's fondness of global variables).
closed as primarily opinion-based by 17 of 26, user53019, Blrfl, Ixrec, Scant Roger Dec 23 '15 at 2:23
Many good questions generate some degree of opinion based on expert experience, but answers to this question will tend to be almost entirely based on opinions, rather than facts, references, or specific expertise. If this question can be reworded to fit the rules in the help center, please edit the question.
C isn't the same as C++, particularly today. They have wildly diverged in terms of good practices and idiomatics. K&R may be a classic but that's just what it is, a classic. There are better modern C books out there and certainly better resources on low-latency C++ that focus on C++ as C++ rather than C with gribblies.
I'm a c# programmer, and I'm planning to study K&R. I'm hoping it won't corrupt me so that I'm no longer able to write c# code.
Seriously, just think of C as a low-level programming language that follows the same fundamental principles as any other programming language, which bears some similarity to C++ and share some of its features.
In other words, it's useful to study C as a language in its own right, and I believe it will be a useful primer for learning C++, if you want to go that route.
K&R is a good read regardless, if only because C is the grandmother of C++, Java, C#, S/R, ... You'll get a good sense that computers execute instructions, not abstractions. That poor little chip has to cycle away doing what you asked it to do, and the less you ask it to do, the sooner it will finish.
C++ is a terrific language but - it strongly tempts you do do things that take you far away from your goal of minimum latency.
It tempts you to build massive class hierarchies, with lots of
delete-ing, notifications galore, and falling in love with container classes.
The idea of doing these things in healthy moderation is usually absent.
The result is, without realizing it, you can build in sources of slowness taking you orders of magnitude away from optimality. You might have the greatest big-O algorithms, but the constant factors can be killers. However, you can get back to optimality if you know how and you're willing to take a meat-axe to the code. You can stay in C++ and have nice classes, without wasting any cycles. Here's an example.
When doing deterministic or low latency coding on very limited hardware, sometimes those "corrupt/bad/non-PC" coding practices can be beneficial, or even necessary to meet the real-time requirements and/or consume the least nano-watts of system energy. For instance, hand placing global variables in the minimum number of 1st level data cache lines or small local scratch memory bank, etc. C is useful in this manner as a sort of near-macro-assembly langauge for RISC (and PDP-11) processors. Maybe a book on assembly coding and machine language for your choosen processor would be useful as well. It's all part of having a big toolbox. Just make sure not to use the right special tool in the wrong place.
With a predominant focus on low-latency programming, probably K&R's C Programming Language is a bit basic and out-of-date. I still have fondness for that book but it's more of a piece of history now. Perhaps more useful would be to study more of those real-time fields like audio processing, game engines, data-oriented design, embedded system programming, as well as simply newer books on the C language.
I'm far from an expert on this subject as one who never faced the most demanding latency needs and never did any embedded system programming (hopefully I don't embarrass myself here), but it's an aesthetic I appreciate from seeing "average" C++ codebases in performance-critical fields end up losing their responsiveness quickly, only to reveal in a profiling session that these kinds of measurements have their hotspots distributed all over the place (leaving few glaring hotspots to optimize when trying to optimize for responsiveness rather than throughput of a single, heavy-duty operation).
A lot of the predominant focus I find in my field, at least, is throughput with a focus on optimizing bulky algorithms over homogeneous data. Much seems to be forgotten about keeping things highly responsive for all the disparate (or simply small) tasks in between. I like how Mike put it, that the constants here tend to be just as important as algorithmic complexity.
Yet throughput, latency, and deterministic response are all kind of fuzzy to me. This might be significantly oversimplified but I see latency concerns nowadays, and in the context of beefy hardware, to have to do with disparate tasks over non-homogeneous data, with a focus on throughput placing a predominant focus on uniform tasks over homogeneous data (any task which might be ideal for the GPU as one example). Techniques like data-oriented design still potentially apply towards latency demands, but with some tweaks.
One way to optimize for latency in this kind of perspective (possibly flawed) is to recognize that homogeneity and even contiguous memory layouts (and therefore often locality of reference) are often broken by disparate data types. From a machine level, data actually takes on a uniform and homogeneous data of bits and bytes (albeit with alignment requirements that vary based on the type of registers and instructions being used). The stack can store a wild mixture of data types for this reason, as it's modeled at the bits and bytes level rather than the notion of a homogeneous container of elements (and one way to improve latency is to utilize the hell out of it). Understanding this can help you reach down to the custom memory allocator level and start preallocating more disparate types of data in bulk, pooling from a common memory pool to provide requested memory chunks for lots of disparate data types.
In this sense, we're still kind of applying those bulky data-oriented design mindsets, but flattening away the disparity by reaching towards the lower-level, common denominator notion of bits and bytes in memory instead of "data type" (a meta concept which is often obliterated for the most part anyway at runtime). With that kind of adjustment, the idea of the more throughput-focused techniques and latency-focused techniques start to blur more and more together, since we're reducing the disparity in representation that can lead to hiccups in performance.
There's also other things that help like simply burning cycles more, using spin locks/CAS loops, things of that sort. A lot of these techniques are often described as evil and more likely to be misused than used properly, so it can get into a lot of taboo subjects leading to arguments with other developers (especially the most dogmatic) about what is "right". My suggestion is just go with what the measurements and responsiveness favor. Similar to how they teach artists in art school, once an artist becomes familiar and intimate with the classical rules, he is then (but only then) allowed to bend and break them.
In any case, K&R's C Programming Language is probably not the best book on this kind of subject (or even learning C in general), but there are plenty of other resources. Actually programming in C can help quite a bit. C and C++, as languages may share a lot in common with each other at the common denominator level, but there's a wildly different aesthetic along with idiomatic practices. C can make you more quickly get to the bare bits and bytes stage which is useful when improving your understanding of concepts like memory efficiency, if only for the aesthetics and idiomatic practices it promotes. The C++ aesthetic can be a wonderful complement to that in providing high-level, safe, abstract, and/or generic interfaces over these low-level optimization techniques which apply more at the implementation level.