Should I keep using unsigned ints in the age of 64-bit computers?

Viewed 30

I have traditionally used unsigned int types to store quantities known to be small integers, like days of the month and months of the year for example. To store such values I might use uint8_t. One of the perceived benefits of this is that my structures are more compact. So, if I have a structure with a lot of dates in it, the 8-bit types might result in memory use being a quarter of what it would be if I used generic ints. In the old days this could be important if I had structures taking up megabytes on a computer with only 1 or 2 gigabytes of RAM.

However, nowadays even low-end desktop computers generally have at least 8 GB of memory and 64-bit processors. So, I am wondering if I am doing more harm than good by using small, specific integer types rather than using word size integers. The potential harm is that processing power might be expended handling non-word-sized values and thus effect the speed of execution.

If saving memory is no longer a concern, then my second priority is to design towards computational efficiency, however I do not know whether small memory footprint or word-sized objects dominate with regards to execution speed. In other words, I know it is helpful to have a smaller overall memory footprint because more stuff fits into cache, but on the other hand cycles could be spent upcasting byte-sized integers to the native 64-bit word size. Which factor dominates?

Note that the kind of programs I am referring to here are ones that mostly move data around and do small amounts of adding and subtracting. These are not number crunching applications. So, for example, imagine a payroll system that is storing years worth of data for thousands of employees.

1 Answers

TL;DR: CPU caches definitively matters much more than the overhead of converting integers.

cycles could be spent upcasting byte-sized integers to the native 64-bit word size.

On mainstream x86-64 processors this is very cheap to perform conversions between different integers size. It can even be free since registers like rax can be used by instructions as well as eax which is the 32-bit part of rax, ax which is the 16-bit part and ah/al which are the 8-bit lower/higher parts of the 16-bit lower part of rax. Here is a simple representation of such a register:

enter image description here

While this x86-64 feature is not available on other mainstream architectures like ARM, this is still very cheap to perform such an operation. Modern mainstream processors can generally do it in 1 cycle.

Moreover, modern processors can also execute multiple instructions in one cycle thanks to instruction level parallelism and out-of-order execution. For example, Intel Skylake, AMD Zen2 and ARM Cortex-A77 processors contain 4 ALUs per core so they can compute 4 basic integer operations per cycle. With a processor working at 4 GHz, this means 16 billion basic integer instructions per second assuming there is no dependencies between them.

That being said, if you are dealing with many integer stored contiguously in memory, then computing parts of your program may benefit from using SIMD instructions (eg. SSE, AVX, Neon) and they might already be automatically vectorized by compilers. Since SIMD registers have a fixed-size, computing smaller integers can be faster (as more of them can fit in the SIMD register). However, in practice this is not always so simple (some instructions are available/fast only with some specific integer sizes).

Additionally, some instructions are much slower with 64-bit integers regarding the target processor used. For example, a 64-bit division on Skylake processors have a very high latency while it is much smaller with 32-bit integers.

Which factor dominates?

In your case like in most applications, CPU caches definitively matters much more. The RAM is a very scarce resource: not only it has a very high-latency on mainstream platforms, but also a pretty low throughput shared between cores that can be very easily saturated. Thus, using compact data structure can drastically improve performance as long as they can fit in cache. Otherwise, it should still be faster (smaller blocks in RAM can be fetched faster as long as the computation is not latency bound).

Related