NSInteger vs int vs int32_t

  • In a public API, I have to specify integer arguments and return values. The API would be for Objective-C on iOS and Mac OS. What is the preferred type to use in several use cases?

    For example, one API would need to use an unsigned integer whose values will be quite small, say in a range 0 ... 100, that is an uint16_t would be sufficient. (well, I would choose a uint32_t, here).

    Anyway, is it preferable to use always Cocoa macros (NSInteger, NSUInteger, etc.) for an Objective-C API - or may/should I use int, unsigned int, or maybe int32_t, uint32_t, etc. when it seems more appropriate?
  • I'm no expert, but in my experience, I'd say it's certainly safer to use a variables that are able to play well in a Cocoa collection, if you're going to need to use other routines like writing them out to a pList or serialize them in a dictionary.

    I found this out when trying to serialize JSON and also when trying to write out an annotation list to a pList.  There is a struct in a CLCoordinate and this breaks both of the operations.

    On Jul 2, 2012, at 7:24 AM, Andreas Grosam wrote:

    > In a public API, I have to specify integer arguments and return values. The API would be for Objective-C on iOS and Mac OS. What is the preferred type to use in several use cases?
    >
    > For example, one API would need to use an unsigned integer whose values will be quite small, say in a range 0 ... 100, that is an uint16_t would be sufficient. (well, I would choose a uint32_t, here).
    >
    >
    > Anyway, is it preferable to use always Cocoa macros (NSInteger, NSUInteger, etc.) for an Objective-C API - or may/should I use int, unsigned int, or maybe int32_t, uint32_t, etc. when it seems more appropriate?
  • On 2 Jul 2012, at 6:24 AM, Andreas Grosam wrote:

    > Anyway, is it preferable to use always Cocoa macros (NSInteger, NSUInteger, etc.) for an Objective-C API - or may/should I use int, unsigned int, or maybe int32_t, uint32_t, etc. when it seems more appropriate?

    It depends on whether it's important to you that the integer be strictly 32 bits wide. NS(U)Integer is 32 bits on iOS and 32-bit Mac, 64 bits on 64-bit Mac.

    If your concern is a better impedance match with Cocoa, use NS(U)Integer.

    If you have a reason to want a specific width, use (u)int32_t.

    — F
  • On Jul 2, 2012, at 4:24 AM, Andreas Grosam wrote:

    > Anyway, is it preferable to use always Cocoa macros (NSInteger, NSUInteger, etc.) for an Objective-C API - or may/should I use int, unsigned int, or maybe int32_t, uint32_t, etc. when it seems more appropriate?

    Sure, use smaller types if it's appropriate. But consider first whether the size savings are worthwhile (keeping in mind C's data alignment and struct packing rules.)

    I'm not a big fan of NSInteger/NSUInteger because they have the very annoying property of being different sizes in 32 vs 64 bit builds (one consequence is that there is no '%' formatting specifier that works correctly with them.) But if your API represents things like collection sizes or array indexes, it's best to play along and use those types.

    —Jens
  • NSInteger and NSUInteger also have the advantage of having the same @encode() on 32-bit and 64-bit, which can be important for binary compatibility of archives and IPC between architectures, depending on how you do it.

      -- Chris

    On Jul 2, 2012, at 8:58 AM, Jens Alfke <jens...> wrote:

    >
    > On Jul 2, 2012, at 4:24 AM, Andreas Grosam wrote:
    >
    >> Anyway, is it preferable to use always Cocoa macros (NSInteger, NSUInteger, etc.) for an Objective-C API - or may/should I use int, unsigned int, or maybe int32_t, uint32_t, etc. when it seems more appropriate?
    >
    > Sure, use smaller types if it's appropriate. But consider first whether the size savings are worthwhile (keeping in mind C's data alignment and struct packing rules.)
    >
    > I'm not a big fan of NSInteger/NSUInteger because they have the very annoying property of being different sizes in 32 vs 64 bit builds (one consequence is that there is no '%' formatting specifier that works correctly with them.) But if your API represents things like collection sizes or array indexes, it's best to play along and use those types.
    >
    > ―Jens
  • On Jul 2, 2012, at 12:06 PM, Chris Hanson wrote:

    > NSInteger and NSUInteger also have the advantage of having the same @encode() on 32-bit and 64-bit, which can be important for binary compatibility of archives and IPC between architectures, depending on how you do it.

    How can they? They're different sizes. Even if your protocol prefixes them with a type-code or length to get around that, you have another problem: if you write an NSInteger in 64-bit and read it in 32-bit, you're potentially losing data. (The same situation there's a compiler warning for when you assign to a variable of smaller width.)

    If you want binary compatibility you should definitely be using types that have an explicit guarantee of size, like int32_t or int64_t.

    I really don't understand the thought behind creating NSInteger. It seems dangerous to have a 'standard' type whose size isn't fixed. It leads to mistakes like storing a file size in an NSUInteger — that's fine in 64-bit, but in a 32-bit app it blows up on large files. I thought we'd already learned this lesson with the old C 'int' type, which has been giving people cross-platform problems since the 1970s.

    —Jens
  • On Jul 2, 2012, at 1:10 PM, Jens Alfke <jens...> wrote:
    > I really don't understand the thought behind creating NSInteger. It seems dangerous to have a 'standard' type whose size isn't fixed. It leads to mistakes like storing a file size in an NSUInteger — that's fine in 64-bit, but in a 32-bit app it blows up on large files. I thought we'd already learned this lesson with the old C 'int' type, which has been giving people cross-platform problems since the 1970s.

    NSInteger exists to allow APIs to use 64-bit integers in 64-bit mode while preserving source- and binary-compatibility in 32-bit mode. If you start with a pile of 32-bit code that uses `int` and `@encode(int)`, then NSInteger is the correct way to move forward to 64-bit.

    --
    Greg Parker    <gparker...>    Runtime Wrangler
  • On Jul 2, 2012, at 3:10 PM, Jens Alfke wrote:

    > On Jul 2, 2012, at 12:06 PM, Chris Hanson wrote:
    >
    >> NSInteger and NSUInteger also have the advantage of having the same @encode() on 32-bit and 64-bit, which can be important for binary compatibility of archives and IPC between architectures, depending on how you do it.
    >
    > How can they? They're different sizes. Even if your protocol prefixes them with a type-code or length to get around that, you have another problem: if you write an NSInteger in 64-bit and read it in 32-bit, you're potentially losing data. (The same situation there's a compiler warning for when you assign to a variable of smaller width.)
    >
    > If you want binary compatibility you should definitely be using types that have an explicit guarantee of size, like int32_t or int64_t.

    When I’m parsing a binary file format, of course I always use the explicitly-sized types. Most of the time, that’s not what you’re doing, though, and for general-purpose stuff NS(U)Integer is better.

    > I really don't understand the thought behind creating NSInteger. It seems dangerous to have a 'standard' type whose size isn't fixed. It leads to mistakes like storing a file size in an NSUInteger — that's fine in 64-bit, but in a 32-bit app it blows up on large files. I thought we'd already learned this lesson with the old C 'int' type, which has been giving people cross-platform problems since the 1970s.

    NSInteger is always equal to the native integer size of the host machine; 32 bits in 32-bit, 64 bits in 64-bit. I would imagine this helps performance, as the processor will be dealing with its native integer type. Also, for indexes and offsets, things like -[NSData length], it makes sense; a NSUInteger ensures that it will be able to hold any value that’s legal on the architecture. If -[NSData length] had been defined as a uint32_t, that would have caused forward compatibility problems with the move to 64-bit.

    And really, if you are storing NSUIntegers to the disk, you’re just doing it wrong. XML is what you should be using for most new formats these days; when you have to read/write in binary formats, that’s what the u?int[0-9]+_t formats are for. Using a uint32_t in cases where there’s no real reason to require that the integer be exactly 32 bits wide, though, doesn’t seem to provide any benefit.

    Charles
  • On Jul 2, 2012, at 3:28 PM, Charles Srstka wrote:

    > NSInteger is always equal to the native integer size of the host machine; 32 bits in 32-bit, 64 bits in 64-bit. I would imagine this helps performance, as the processor will be dealing with its native integer type.

    It depends. 64-bit values are twice as big as 32-bit ones, so they use up twice as much L2 cache and RAM. That's often a bigger consideration than the CPU overhead, which might be zero, of extending values in registers. (Most of the time performance is not about how many clock cycles your code uses, it's about how many bytes of RAM it uses.)

    > And really, if you are storing NSUIntegers to the disk, you’re just doing it wrong.

    I agree. My comment was in response to Chris's about NSIntegers being good for persistent storage. It didn't make sense to me either.

    > XML is what you should be using for most new formats these days;

    Hey, 1999 called and wants its trendy data format back! No one but squares uses XML anymore; JSON is what's hep. ;-)

    > when you have to read/write in binary formats, that’s what the u?int[0-9]+_t formats are for. Using a uint32_t in cases where there’s no real reason to require that the integer be exactly 32 bits wide, though, doesn’t seem to provide any benefit.

    It's useful when you explicitly _don't_ want a 64-bit value, to save space.

    —Jens
  • On Jul 2, 2012, at 3:56 PM, Jens Alfke wrote:

    >
    > On Jul 2, 2012, at 3:28 PM, Charles Srstka wrote:
    >
    >> NSInteger is always equal to the native integer size of the host machine; 32 bits in 32-bit, 64 bits in 64-bit. I would imagine this helps performance, as the processor will be dealing with its native integer type.
    >
    > It depends. 64-bit values are twice as big as 32-bit ones, so they use up twice as much L2 cache and RAM.

    I would be surprised if cache is managed at anything other than multiples of register width (64 bits).

    --Kyle Sluder
  • On Jul 2, 2012, at 4:17 PM, Kyle Sluder wrote:

    >> It depends. 64-bit values are twice as big as 32-bit ones, so they use up twice as much L2 cache and RAM.
    >
    > I would be surprised if cache is managed at anything other than multiples of register width (64 bits).

    That's not the point. Data containing 64-bit values (in objects, structs, stack frames…) is obviously bigger than data containing smaller values. There's a fairly large increase in memory usage when a process switches to 64-bit, for this reason, and it carries with it a performance hit. You can't do much about the sizes of pointers without a lot of work, but you can use smaller fields for integers where it makes sense.

    —Jens
  • On Jul 2, 2012, at 4:32 PM, Jens Alfke wrote:

    >
    > On Jul 2, 2012, at 4:17 PM, Kyle Sluder wrote:
    >
    >>> It depends. 64-bit values are twice as big as 32-bit ones, so they use up twice as much L2 cache and RAM.
    >>
    >> I would be surprised if cache is managed at anything other than multiples of register width (64 bits).
    >
    > That's not the point. Data containing 64-bit values (in objects, structs, stack frames…) is obviously bigger than data containing smaller values.

    Not necessarily. The CPU could just mask off the top 32 bits when executing 32-bit opcodes on in-cache data. Each 32-bit value is still taking up 64 bits of cache space. That's probably a lot easier and more efficient than making it possible to address arbitrary 32-bit slices of cache.

    Note that I'm only referring to cache here, not RAM.

    --Kyle Sluder
  • On Jul 2, 2012, at 4:34 PM, Kyle Sluder wrote:

    > Not necessarily. The CPU could just mask off the top 32 bits when executing 32-bit opcodes on in-cache data. Each 32-bit value is still taking up 64 bits of cache space. That's probably a lot easier and more efficient than making it possible to address arbitrary 32-bit slices of cache.

    I … I don't think we're talking about the same thing at all. I'm talking about the aggregate size of data structures, not how individual values are aligned in the cache. The cache stores big chunks of data like 256 bytes, anyway; it doesn't do anything like masking individual words.

    Here's a contrived example. Let's say I have a struct whose members include four NSIntegers. If I convert those to int32_t, I make the struct 16 bytes smaller*. If I have a million of those in memory, I've saved 16 megabytes of address space, which is a nontrivial fraction of the size of the L2 cache.

    —Jens

    * Assuming optimal packing and no padding by malloc.
  • On Jul 2, 2012, at 4:34 PM, Kyle Sluder wrote:

    > On Jul 2, 2012, at 4:32 PM, Jens Alfke wrote:
    >
    >>
    >> On Jul 2, 2012, at 4:17 PM, Kyle Sluder wrote:
    >>
    >>>> It depends. 64-bit values are twice as big as 32-bit ones, so they use up twice as much L2 cache and RAM.
    >>>
    >>> I would be surprised if cache is managed at anything other than multiples of register width (64 bits).
    >>
    >> That's not the point. Data containing 64-bit values (in objects, structs, stack frames…) is obviously bigger than data containing smaller values.
    >
    > Not necessarily. The CPU could just mask off the top 32 bits when executing 32-bit opcodes on in-cache data. Each 32-bit value is still taking up 64 bits of cache space. That's probably a lot easier and more efficient than making it possible to address arbitrary 32-bit slices of cache.
    >
    > Note that I'm only referring to cache here, not RAM.

    I suspect you are both talking past each other.

    Jens' assertion is that if you had a 128 byte cache, you could store either 8 64-bit integers or 16 32-bit integers in it. Whereas Kyle is asserting that the CPU need only read 32-bits at a time (or less) from the cache for opcodes that deal with 32-bits (or less) of data at a time.

    Your both correct, but your looking at different parts of the same problem.
    --
    David Duncan
  • On Jul 2, 2012, at 5:34 PM, Kyle Sluder wrote:

    > Not necessarily. The CPU could just mask off the top 32 bits when executing 32-bit opcodes on in-cache data. Each 32-bit value is still taking up 64 bits of cache space. That's probably a lot easier and more efficient than making it possible to address arbitrary 32-bit slices of cache.

    No. A cache line always maps to a contiguous slice of RAM the same size as the line--to do otherwise would be an absolute nightmare of complexity.

    --
    Scott Ribe
    <scott_ribe...>
    http://www.elevated-dev.com/
    (303) 722-0567 voice
  • On Jul 2, 2012, at 5:56 PM, Jens Alfke wrote:

    > Hey, 1999 called and wants its trendy data format back!

    Which one? JSON itself originated in 1999.

    > No one but squares uses XML anymore; JSON is what's hep. ;-)

    You mean other than CoreData and all of Apple’s plist stuff?

    Charles
  • On Jul 2, 2012, at 5:42 PM, David Duncan wrote:

    > I suspect you are both talking past each other.
    >
    > Jens' assertion is that if you had a 128 byte cache, you could store either 8 64-bit integers or 16 32-bit integers in it. Whereas Kyle is asserting that the CPU need only read 32-bits at a time (or less) from the cache for opcodes that deal with 32-bits (or less) of data at a time.

    I don't think so; I think Kyle was asserting that for a 32-bit integer on a 64-bit machine, the CPU would read 64 bits from the cache.

    --
    Scott Ribe
    <scott_ribe...>
    http://www.elevated-dev.com/
    (303) 722-0567 voice
  • On Jul 4, 2012, at 9:36 PM, Nathan Day wrote:

    > If you have an array of 32bit ints on a 64bit machine, are the 32bit ints expanded into 64bit ints when copied into the cache...

    No, absolutely not. A cache line maps to a contiguous section of RAM of the same size. To do otherwise would add huge complexity--and, actually, would probably be impossible. How is the CPU to know before the memory is used, what is 32-bit values vs what is 64 (16, 8)? What happens when memory is accessed in one line of code as 2 32-bit values, and as 1 64-bit value in the next line.
    --
    Scott Ribe
    <scott_ribe...>
    http://www.elevated-dev.com/
    (303) 722-0567 voice
  • You are saying that the CPU read two 32bit int from the cache at the same time, and then does some bit manipulation to get the high or low 32bit word into a 64bit register.

    On 03/07/2012, at 11:00 AM, Scott Ribe <scott_ribe...> wrote:

    > On Jul 2, 2012, at 5:42 PM, David Duncan wrote:
    >
    >> I suspect you are both talking past each other.
    >>
    >> Jens' assertion is that if you had a 128 byte cache, you could store either 8 64-bit integers or 16 32-bit integers in it. Whereas Kyle is asserting that the CPU need only read 32-bits at a time (or less) from the cache for opcodes that deal with 32-bits (or less) of data at a time.
    >
    > I don't think so; I think Kyle was asserting that for a 32-bit integer on a 64-bit machine, the CPU would read 64 bits from the cache.
    >
    > --
    > Scott Ribe
    > <scott_ribe...>
    > http://www.elevated-dev.com/
    > (303) 722-0567 voice
  • On Jul 4, 2012, at 11:28 PM, Nathan Day wrote:

    > You are saying that the CPU read two 32bit int from the cache at the same time, and then does some bit manipulation to get the high or low 32bit word into a 64bit register.

    No, I'm not saying that at all.

    --
    Scott Ribe
    <scott_ribe...>
    http://www.elevated-dev.com/
    (303) 722-0567 voice
  • It must if 64bits is read in that mean you have just read in two 32bit words. So to put a 32bit word in a 64bit register some bit must be ditched, in some way, and if the CPU is optimise to only work with 64bit word alignment (don't know how intel does it), then to get 32 bit aligned words it must do some bit shift.

    On 05/07/2012, at 3:33 PM, Scott Ribe <scott_ribe...> wrote:

    > On Jul 4, 2012, at 11:28 PM, Nathan Day wrote:
    >
    >> You are saying that the CPU read two 32bit int from the cache at the same time, and then does some bit manipulation to get the high or low 32bit word into a 64bit register.
    >
    > No, I'm not saying that at all.
    >
    > --
    > Scott Ribe
    > <scott_ribe...>
    > http://www.elevated-dev.com/
    > (303) 722-0567 voice
    >
    >
    >
    >
  • On Jul 4, 2012, at 11:41 PM, Nathan Day wrote:

    > It must if 64bits is read in that mean you have just read in two 32bit words. So to put a 32bit word in a 64bit register some bit must be ditched, in some way, and if the CPU is optimise to only work with 64bit word alignment (don't know how intel does it), then to get 32 bit aligned words it must do some bit shift.

    Why are you assuming 64 bits must be read from the cache?

    --
    Scott Ribe
    <scott_ribe...>
    http://www.elevated-dev.com/
    (303) 722-0567 voice
  • On Jul 4, 2012, at 10:41 PM, Nathan Day wrote:

    > It must if 64bits is read in that mean you have just read in two 32bit words. So to put a 32bit word in a 64bit register some bit must be ditched, in some way, and if the CPU is optimise to only work with 64bit word alignment (don't know how intel does it), then to get 32 bit aligned words it must do some bit shift.

    Yes, but this happens in hardware way, way down inside the ALU, where I seriously doubt it takes any extra time at all. (It's not a 'shift' at that level so much as the way address lines are wired up to each other.) You'd have to consult the gory details of the timing diagrams in Intel's detailed instruction-set manuals to find out. In any case it has nothing to do with the current discussion.

    The CPU cache is a lot like virtual memory, just lower level. The 'page size' is called a 'cache line' and it's smaller, maybe 64 or 128 bytes. When the CPU needs to access an address it first looks in the cache to see if a cache line containing that address is available; if so it reads it directly from there (which is extremely fast since it's on-chip), otherwise it has to wait to load the entire cache line from RAM, then fetch the address.

    The cache _only_ deals in blocks of 128/256/whatever bytes, just like the disk controller only reads/writes entire sectors. It can't manage 4-byte or 8-byte values any more than the disk controller can. It has no idea whether the CPU wants a byte or a 32-bit word or a 64-bit one, it just knows how to read and write pages from off-chip RAM.

    http://en.wikipedia.org/wiki/Cache_line

    And the real point in this discussion: Just like with VM but on a smaller scale, the better your working set of data fits into the CPU cache, the faster your code will run. And the cache will, obviously, hold twice as many int32_t's as it will NSIntegers (in 64-bit mode.) So using smaller int sizes _can_ improve performance.

    —Jens
  • On Jul 5, 2012, at 9:38 AM, Jens Alfke wrote:

    > And the real point in this discussion: Just like with VM but on a smaller scale, the better your working set of data fits into the CPU cache, the faster your code will run. And the cache will, obviously, hold twice as many int32_t's as it will NSIntegers (in 64-bit mode.) So using smaller int sizes _can_ improve performance.

    Yes, it definitely can. For any application that is dealing with large blocks of data. And if you're working with the vector unit, it's even more important. In theory, that could happen without your knowing it via framework calls--in practice it seems likely that you'd know if you're working with larges blocks of data and algorithms that should be handled that way...

    And theoretically, moving from 32-bit to 64-bit executable could slow you down because of fewer pointers fitting in cache--however the few people I've ever heard mention that were, in my opinion, seriously overblowing it. In my experience with data & algorithm heavy code, just switching from 32- to 64-bit produces about a 10% performance boost, because of the advantages of x86-64 (more registers and so on).

    (Not that it matters anymore, but this is different than the PPC case, where there were not such instruction model differences between 32 & 64).

    --
    Scott Ribe
    <scott_ribe...>
    http://www.elevated-dev.com/
    (303) 722-0567 voice
  • On 5 juil. 2012, at 07:41, Nathan Day <nathan_day...> wrote:

    > It must if 64bits is read in that mean you have just read in two 32bit words. So to put a 32bit word in a 64bit register some bit must be ditched, in some way, and if the CPU is optimise to only work with 64bit word alignment (don't know how intel does it), then to get 32 bit aligned words it must do some bit shift.

    Modern CPU do not enforce strict alignment for integer access. You can perfectly access a Dword (64 bits) at any address, even or odd. It is just more efficient to align 64-bits words at 8-bytes boundary, 32-bits at 4-bytes, etc. This contrasts with the old times: for example, on a 68000 processor, trying to access a 16-bit word at an odd address (e.g. move.w d0, (a0)+ with a0 odd) would result in a exception n°3 (address error).

    You still get boundary enforcement for SIMD instructions though (SSE, AVX). This is somehow reflected in C code through the use of special macros to instruct the compiler to respect these alignments.

    Vincent
  • On Jul 9, 2012, at 5:44 AM, Vincent Habchi <vince...> wrote:
    > Modern CPU do not enforce strict alignment for integer access. You can perfectly access a Dword (64 bits) at any address, even or odd. It is just more efficient to align 64-bits words at 8-bytes boundary, 32-bits at 4-bytes, etc. This contrasts with the old times: for example, on a 68000 processor, trying to access a 16-bit word at an odd address (e.g. move.w d0, (a0)+ with a0 odd) would result in a exception n°3 (address error).

    Some CPUs still enforce aligned integer access, such as the ARM CPUs in some iOS devices. If your iOS crash log says EXC_ARM_DA_ALIGN then you died from a misalignment fault.

    --
    Greg Parker    <gparker...>    Runtime Wrangler
  • On 9 juil. 2012, at 20:40, Greg Parker <gparker...> wrote:

    > On Jul 9, 2012, at 5:44 AM, Vincent Habchi <vince...> wrote:
    >> Modern CPU do not enforce strict alignment for integer access. You can perfectly access a Dword (64 bits) at any address, even or odd. It is just more efficient to align 64-bits words at 8-bytes boundary, 32-bits at 4-bytes, etc. This contrasts with the old times: for example, on a 68000 processor, trying to access a 16-bit word at an odd address (e.g. move.w d0, (a0)+ with a0 odd) would result in a exception n°3 (address error).
    >
    > Some CPUs still enforce aligned integer access, such as the ARM CPUs in some iOS devices.

    Oh, thanks for mentioning this. I was obviously thinking about Intel CPUs. I know very little about ARM. By the way, you can also access misaligned instructions on x86 processors I think, something that was not possible on 68000. If I remember correctly, the integer alignment enforcement was lifted on the 68030, which leaded to interesting hardware developments…

    Vincent
  • At 10:05 AM -0600 7/5/12, Scott Ribe wrote:
    > And theoretically, moving from 32-bit to 64-bit executable could
    > slow you down because of fewer pointers fitting in cache--however
    > the few people I've ever heard mention that were, in my opinion,
    > seriously overblowing it. In my experience with data & algorithm
    > heavy code, just switching from 32- to 64-bit produces about a 10%
    > performance boost, because of the advantages of x86-64 (more
    > registers and so on).

    Now you may be getting to the crux of the matter:

    For a processor family with reasonably clean 32- and 64-bit models
    (i.e. PowerPC) going to 64-bit is a speed hit in many circumstances
    due to the increase in size of everything.

    However, i386 has so much baggage and so few registers that the
    performance gain from x86-64's much less sucky architecture/runtime
    more than makes up for the loss due to memory bloat.

    ;-)

    -Steve
previous month july 2012 next month
MTWTFSS
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31          
Go to today