What Are Intrinsic Operations in C++
Intrinsic operations (aka Intrinsics) are well-known procedures that the compiler knows how to do in the most optimized way. In this article, I will briefly introduce them as well as some common ones that you should know.
Table of Contents
Introduction
Not so long ago I talked about learning assembly and why it is important. In this article, I will extend the more low-level knowledge about assembly and instructions. In that article, I mentioned that assembly is specific to the CPU. This means that each CPU has a different set of instructions and its assembly language for them. But if that were the case in all CPUs then who would write code for compiler – it would be a complete mess.
CPUs have architectures – a well-designed set of instructions that they support. These architectures say that for example, a given CPU supports adding numbers, or processing floats. Well here comes the interesting part – some of these instructions are more complicated like an atomic increment. To increment a value you would need three instructions in assembly – get the value in a CPU register, increment the register by one, and save the value back. This operation is unsafe if multiple threads can access the same memory at the same time because while one thread is getting the value another can be already incrementing it and saving it. The first thread will then rewrite the new value of the second thread instead of incrementing it one more time since it gets a value that is before it is incremented.
The atomic increment then is a specialized CPU instruction that not all processors implement. So when writing C++ code you have 2 choices. Test if the assembly instruction is available and write the assembly code yourself. Or you can use a recipe called intrinsics that handles that for you.
Intrinsic instructions are also gathered into the so-called extensions. You would have the x86 architecture and the given CPU might also support the SSE instruction set. If it supports that then through the standard library, you will be able to access a set of intrinsic instructions that represent optimized assembly code but have a nice C++ signature.
Example
If you’re like me – a Windows programmer you would benefit from reading this Microsoft page with the links to the different architecture intrinsics. I will currently give you an example with the one I mentioned earlier called _aadd_i32. You will notice a pattern where intrinsics usually start with one or two double underscores so they are easily recognizable.
#include <x86gprintrin.h>
#include <iostream>
int main() {
int i = 0;
_aadd_i32(&i, 1);
std::cout << i;
}
This very simple example actually requires us to add compiler flags. For using this with gcc for example you would have to add -mraoint
to force the compiler to add these intrinsics. On Windows there is another intrinsic that does the same thing called _InterlockedAdd
. You can find information about it here.
Essentially this simple intrinsic will compile to the assembly instruction aadd
as you can see here in Godbolt:
And now it is a simple instruction!
Why is this relevant?
This information is especially important when you’re developing programs that need to execute fast. These intrinsics can bring safety for multithreaded environments in the form of the atomic add that you saw earlier but there are also intrinsics from the SSE extension sets that are used to operate on more than one value. For example, some intrinsics add together 4 values at once and this is very useful when talking about vector math which is highly used in game programming.
Conclusion
If you want to bring your A game when programming in C++ you should add intrinsics to your toolbelt of things that you can use. This will optimize your code tremendously. There are cases where the standard library will do that for you. For example, if we take the code from above – the standard library already defines a template type called std::atomic
. If you pass an integer to std::atomic
the template type will actually call the intrinsic function if it is available on the target platform.
Intrinsics are quite specific for low-level languages and you won’t see them that much in languages like Java or C#. Rust on the other hand also implements intrinsics to some extent.
Leave a comment