How to detect machine word size in C/C++?

Viewed 13383

Is there a more-or-less reliable way (not necessarily perfect) to detect the machine word size of the target architecture for which I'm compiling?

By machine word size I mean the size of the integer accumulator register (e.g. EAX on x86, RAX on x86_64 etc., not streaming extensions, segment or floating-point registers).

The standard does not seem to provide a "machine word" data type. So I'm not looking for a 100% portable way, just something that works in most common cases (Intel x86 Pentium+, ARM, MIPS, PPC - that is, register-based, contemporary commodity processors).

size_t and uintptr_t sound like good candidates (and in practice matched the register size everywhere I tested) but are of course something else and are thus not guaranteed to always do so as is already described in Is size_t the word size.

Context

Let's assume I'm implementing a hashing loop over a block of contiguous data. It is OK to have the resulting hash depend on the compiler, only speed matters.

Example: http://rextester.com/VSANH87912

Testing on Windows shows that hashing in chunks of 64 bits is faster in 64-bit mode and in 32 bits in 32-bit mode:

64-bit mode
int64: 55 ms
int32: 111 ms

32-bit mode
int64: 252 ms
int32: 158 ms
6 Answers

I'll give you the right answer to the question you should be asking:

Q: How do I choose the fastest hash routine for a particular machine if I don't have to use a particular one and it doesn't have to be the same except within a single build (or maybe run) of an application?

A: Implement a parametrized hashing routine, possibly using a variety of primitives including SIMD instructions. On a given piece of hardware, some set of these will work and you will want to enumerate that set using some combination of compile time #ifdefs and dynamic CPU feature detection. (E.g. you can't use AVX2 on any ARM processor, determined at compile time, and you can't use it on older x86, determined by the cpuinfo instruction.) Take the set that works and time them on test data on the machines of interest. Either do so dynamically at system/application startup or test as many cases as you can and hardcode which routine to use on which system based on some sniffing algorithm. (E.g. the Linux kernel does this to determine the fastest memcpy routine, etc.)

The circumstances under which you need the hash to be consistent will be application dependent. If you need the choice to be entirely at compile time, then you'll need to craft a set of preprocessor macros the compiler defines. Often it is possible to have multiple implementations that produce the same hash but using different hardware approaches for different sizes.

Skipping SIMD is probably not a good idea if you are defining a new hash and want it to be really fast, though it may be possible in some applications to saturate the memory speed without using SIMD so it doesn't matter.

If all of that sounds like too much work, use size_t as the accumulator size. Or use the largest size for which std::atomic tells you the type is lock free. See: std::atomic_is_lock_free, std::atomic::is_lock_free, or std::atomic::is_always_lock_free.

By "machine word size" we'll have to assume that the meaning is: the largest size of a piece of data that the CPU can process in a single instruction. (Sometimes called data bus width although that's a simplicifaction.)

On various CPU:s, size_t, uintptr_t and ptrdiff_t could be anything - these are related to the address bus width, rather than the CPU data width. So we can forget about these types, they don't tell us anything.

On all mainstream CPU:s, char is always 8 bits, short is always 16 bits and long long is always 64 bits. So the only interesting types remaining are int and long.


The following mainstream CPU:s do exist:

8 bits

int   = 16 bits   
long  = 32 bits

16 bits

int   = 16 bits   
long  = 32 bits

32 bits

int   = 32 bits   
long  = 32 bits

64 bits

int   = 32 bits   
long  = 32 bits

Unconventional variations to the above may exist, but generally there's no telling from the above how to distinguish 8-bit from 16-bit or 32-bit from 64-bit.

Alignment is no help to us either, because it may or may not apply to various CPU:s. Many CPU:s can read misaligned words just fine, but at the expensive of slower code.

So there is no way to tell the "machine word size" by using standard C.


It is however possible to write fully portable C that can run on anything between 8 and 64 bits, by using the types from stdint.h, notably the uint_fast types. Some things to keep in mind are:

  • Implicit integer promotions across different systems. Anything of uint32_t or larger is generally safe and portable.
  • The default type of integer constants ("literals"). This is most often (but not always) int, and what an int is on a given system may vary.
  • Alignment and struct/union padding.
  • Pointer size is not necessarily the same as machine word size. Particularly true on many 8, 16 and 64 bit computers.
Related