When should I use int32_t rather than int

Started by
35 comments, last by DvDmanDT 7 years, 6 months ago

Hi Guys,

I have been lurking in this forum for a while now and today I decided to finally create an account :)

So here is my question. I'm creating a game on both ARMv7 and x86-AMD64 CPUs, should I always use int8_t, int16_t, int32_t, int64_t or should I used int, float, double, char etc..? Does it matter? Is there any performance difference? Should I care? And when Should and when shouldn't I use int8_t, int16_t, int32_t, int64_t?

I know that int32_t is always guaranteed to give you 32bits of memory space if the platform supports it. same goes for int_8 int_16 etc...

Thanks.

Advertisement

int/short/long etc might use a type that is faster for that particular target, potentially causing your application to behave differently. Using int32_t etc will make the behavior consistent, but possibly sacrifising performance in doing so. I highly recommend focusing on correct/consistent behavior until you find a specific scenario where it makes a large difference in performance without causing bugs.

A typical example of where one might want to use int instead of int32_t is for function returns that just return a status code of some sort. In C, you don't have bools, so you use int instead. In that case, int would be better than int32_t, but for storing actual data, I highly recommend picking a type that fits that data.

So you recommend using int for error code function return and int32_t for data storage?

I highly recommend focusing on correct/consistent behavior until you find a specific scenario where it makes a large difference in performance without causing bugs.

How would I detect a scenario where there is a performance difference between the two?

int doesn't give you any guarantee on the number of bytes it uses. So when you need this guarantee you would need to use any of the sized integers, e.g. for serialization. For performance there is actually int_fast32_t. E.g iirc on the PS3 the fast integer was actually 64bit and they didn't want to make the regular int 64bit. We used fast int in for loops. Personally I would *not* worry about this.

You should only specify the size when it actually matters and let the compiler do the work of figuring out the best size as much as possible. The places specific sizes will matter vary but an incomplete list would be things such as file formats, network IO, talking to hardware at a low level and various other relatively uncommon tasks. So, for instance:

for (int32_t i=0; i<10; ++i) // Don't do this.

instead use:

for (int i=0; i<10; ++i)

The compiler will choose the best size for this to optimize based on compiler settings. Minimal code size it may choose an int16_t, maximal speed it may choose int64_t for a 64 bit target. The compiler is generally the best to make these decisions anymore.

Other special cases also come into play where you may need to pick a specific size. For instance, you may be performing an average over a long sequence. If you choose to average a bunch of int8_t's, you may need to make sure the compiler uses int64_t to prevent an overflow during the addition step. Specific algorithms sometimes require this.

These are just general 'rules of thumb', for every suggestion there is a reason you might break it. But at that point you generally should understand when and why you need to make those decisions and they are generally one off exceptions.

How would I detect a scenario where there is a performance difference between the two?


You would have to use some form of profiling tool or perhaps a code analysis tool to find such performance problems.

I personally recommend using typedefs for things like status codes so that you can easily optimize for particular targets if it becomes necessary (it probably won't!).

If you are targeting "big" cpus like those in computers, phones, tablets, consoles etc, 'int' will almost always be 32 bits and the performance impact of using the "wrong" size will be negligible unless you are processing large arrays of data. I'd be more worried about memory usage and cache efficiency on those platforms. If you really do want to go for optimal performance, I still recommend using typedefs to the well defined types (such as int32_t) and manually selecting which ones to use for a particular task on a particular platform. I much prefer to have consistent behavior across all (potential) targets over optimal performance, but this is a personal preference that is far from universal.

Selecting the "right" type matters a lot more if you are targeting small processors/microcontrolles, such as Arduinos and similar. Those platforms tend to have small amounts of really really fast memory (1-512kb or so). The effect is that the relative impact of every non-memory access instruction is much bigger, so if you use a 16-bit int for a processor that can only work with 32 bits, the performance can suffer greatly (as in a single operation might take 3-20 times as long). On "big" cpus, the memory tends to be so slow that a couple of extra non-memory-access instructions makes little difference.

You should only specify the size when it actually matters and let the compiler do the work of figuring out the best size as much as possible. The places specific sizes will matter vary but an incomplete list would be things such as file formats, network IO, talking to hardware at a low level and various other relatively uncommon tasks. So, for instance:

for (int32_t i=0; i<10; ++i) // Don't do this.

instead use:

for (int i=0; i<10; ++i)


I disagree with this. If you suddenly compile for a platform where int is 16 bits, you may end up with all sorts of weird and hard to find bugs. This has actually happend to me. I've always considered the part where int/long/etc are compiler defined to be an incredibly flawed design decision. It's supposed to help with portability, but in my opionion/experience it does the exact opposite since the same code might behave completely different on another target. Anyway, this is a rant that most C/C++ people won't agree with and that is off topic, so I'll just leave it at this.

Yeah I'm in the "use int/uint/float by default, and other sized types when you must" camp.

You should only specify the size when it actually matters and let the compiler do the work of figuring out the best size as much as possible. The places specific sizes will matter vary but an incomplete list would be things such as file formats, network IO, talking to hardware at a low level and various other relatively uncommon tasks. So, for instance:
for (int32_t i=0; i<10; ++i) // Don't do this.
instead use:
for (int i=0; i<10; ++i)


I disagree with this. If you suddenly compile for a platform where int is 16 bits, you may end up with all sorts of weird and hard to find bugs. This has actually happend to me. I've always considered the part where int/long/etc are compiler defined to be an incredibly flawed design decision. It's supposed to help with portability, but in my opionion/experience it does the exact opposite since the same code might behave completely different on another target.

Technically if you follow All8's advice including the bolded bit, then you won't get weird bugs :wink:
"When it actually matters" is:
* When you need to use a specific memory layout -- e.g. you require a struct have a 4 byte field.
* When you're working with "large" ranges -- e.g. a list of 100k elements.

So All8's loop should be:
static_assert( 10 <= INT_MAX );
for (int i=0; i<10; ++i)...


In my case, I don't care about porting my game engine to a 16-bit platform, so I'm actually happy to include something like:
static_assert( INT_MAX >= 0x7FFFFFFF, "Code assumes a 32/64 bit platform. Porting will be hell." );

int doesn't give you any guarantee on the number of bytes it uses. So when you need this guarantee you would need to use any of the sized integers, e.g. for serialization. For performance there is actually int_fast32_t. E.g iirc on the PS3 the fast integer was actually 64bit and they didn't want to make the regular int 64bit. We used fast int in for loops. Personally I would now worry about this.

PPC64 could do int64 at full speed, so there was no need to avoid int64 for perf reasons, but int32 was also full speed IIRC -- there were instructions to operate on half a register as an int32. Most compilers should still implement int as int32_t, as it's the same speed but half the RAM usage.

Fair enough, it might be somewhat far fetched to account for porting to 16-bit systems, but can go the other way as well, where a type is suddenly larger than expected. Take things like random number generators, noise maps and/or hashing algorithms where you do multiplications and/or bit shifts that usually overflow but all of a suddenly don't.

Also, it can be easy to make stupid mistakes. This is back to the sudden 16-bit issue, but you get the idea (yes, really shitty code, I know):


#define MAP_WIDTH 256
#define MAP_HEIGHT 256
#define MAP_COORD(x,y) ((y)*(MAP_WIDTH)+(x))

tile_t g_map[MAP_WIDTH*MAP_HEIGHT];

// somewhere..
static_assert(MAP_HEIGHT <= INT_MAX && MAP_WIDTH <= INT_MAX, "Map dimensions too big!");
for(int y = 0; y < MAP_HEIGHT; y++)
{
  for(int x = 0; x < MAP_WIDTH; x++)
  {
     render_or_whatever(x, y, g_map[MAP_COORD(x,y)]); // int math that breaks for 16 bit ints but works fine for 32 bits
  }
}

Actually, we've started to see a trend where some game companies ship physical objects that interact with a PC or phone game in some way, ie by placing some figure on a specific surface to access a character in the game. Those physical objects may have some microcontroller in them, likely 8 or 16 bit, where you could want to share some small subset of your code between that firmare and your PC application/game. It's not super common right now, but with VR and AR we might very well see an increase in these types of things where it may very well matter.

Fair enough, it might be somewhat far fetched to account for porting to 16-bit systems, but can go the other way as well, where a type is suddenly larger than expected. Take things like random number generators, noise maps and/or hashing algorithms where you do multiplications and/or bit shifts that usually overflow but all of a suddenly don't.

This is exactly why I pointed out that specific algorithms may need special care and you may need to specify size. Generally speaking, once again, you want to let the compiler do the work as much as possible, but you show an exception to the rule as I kept mentioning. :)

Also, be aware that all uint*_t and int*_t are optional in the standard and don't necessarily exist. Only those types exist, that your compiler and CPU support. int_fast*_t, uint_fast*_t, int_least*_t and uint_least*_t exist on all conforming compilers.

The reason is that not all CPUs have integer types of all sizes, though I only know of DSPs that are very special. AFAIK all mainstream consumer CPUs have all the fixed-width types.

This topic is closed to new replies.

Advertisement