为什么首选uint32_t而不是uint_fast32_t?


81

似乎uint32_tuint_fast32_t(我意识到这是轶事证据)更普遍。不过,这对我来说似乎违反直觉。

几乎总是在我看到实现使用时uint32_t,它真正想要的是一个整数,最多可以容纳4,294,967,295(通常在65,535和4,294,967,295之间的下限)。

然后使用似乎很奇怪uint32_t,因为不需要“完全32位”保证,而“最快可用> = 32位”保证uint_fast32_t似乎是正确的主意。而且,尽管通常会实现它,uint32_t但实际上并不能保证它存在。

那么,为什么会uint32_t优先呢?它是更容易为人所知,还是在技术上有其他优势?


24
简单的答案,也许他们需要一个正好32位的整数?
Stargateur

7
首先,我听说过uint32_fast_t,如果我理解正确的话,它至少为32位(意味着可能更多?听起来像是在误导我)。我目前uint32_t在我的项目中使用的是朋友,因为我要打包这些数据并通过网络发送,并且我希望发送方和接收方确切地知道字段的大小。听起来这可能不是最健壮的解决方案,因为平台可能无法实现uint32_t,但是我的所有工作显然都可以,所以我对正在做的事情表示满意。
yano

5
@yano:对于网络,您还应该关心字节顺序/字节序-uint32_t不会给您带来麻烦(可惜没有uint32_t_beand uint32_t_le,这几乎适用于uint32_t当前可能是最佳选择的几乎所有情况)。
布伦丹

3
@Brendan-关于_be和_le,htonl()和ntohl()是否提供相同的功能?
mpez0

2
@Brendan是一个隐藏在标准int中的重量级对象,所有这些都是原始类型。我原则上同意您的意见,应该在某个地方的标准中进行处理,但是我认为这可能不是地方
Steve Cox

Answers:


78

uint32_t保证在支持它的任何平台上具有几乎相同的属性。1个

uint_fast32_t 相比之下,几乎没有任何保证。

如果切换到uint_fast32_t大小不同的平台,则uint_fast32_t必须重新测试和验证所有使用的代码。所有稳定性假设都将成为现实。整个系统将以不同的方式工作。

编写代码时,您甚至可能无法访问uint_fast32_t大小不为32位的系统。

uint32_t 不会有所不同(请参见脚注)。

正确性比速度更重要。因此,过早正确性比过早优化是更好的计划。

如果我正在uint_fast32_t为64位或更多位的系统编写代码,则可能会在两种情况下都测试我的代码并使用它。限制需求和机会,这样做是一个错误的计划。

最后,uint_fast32_t当您将其存储任何时间长度或实例数时,可能会比uint32仅由于缓存大小问题和内存带宽而变慢。如今,计算机受内存限制的频率要比受CPU约束的频率高得多,并且uint_fast32_t在隔离时可能会更快,但是在您考虑了内存开销之后却没有。


1正如@chux在评论中指出的那样,如果unsigned大于uint32_t,则算术运算uint32_t将执行常规的整数提升,否则将保持为uint32_t。这可能会导致错误。没有什么是完美的。


15
“保证uint32_t在任何支持它的平台上具有相同的属性。” 当unsigned宽度大于uint32_t,然后uint32_t在一个平台上进行通常的整数促销而在另一个平台上则不进行促销时,就会遇到一个难题。然而,有了uint32_t这些整数数学问题就大大减少了。
chux-恢复莫妮卡

2
@chux在乘法时可能会导致UB的特殊情况,因为升级首选带符号的int且带符号的整数溢出为UB。
CodesInChaos

2
尽管这个答案就目前而言是正确的,但它在很大程度上忽略了关键细节。简而言之,uint32_t对于类型的机器表示的确切细节很重要的地方,而uint_fast32_t对于计算速度最重要的地方,(无)符号性和最小范围很重要,并且表示的细节不是必需的。还有uint_least32_t的地方(UN)的符号性和最小的范围内是最重要的,紧凑比速度更重要,准确的表示是不是必需的。
John Bollinger

@JohnBollinger很好,但是没有在实现超过1个变量的实际硬件上进行测试,可变大小类型是一个陷阱。人们之所以使用uint32_t而不是其他类型的原因是因为他们通常没有这样的硬件来进行测试。(在int32_t较小的范围内,甚至int和也是如此short)。
Yakk-Adam Nevraumont

1
极端情况的示例:让unsigned short==uint32_tint== int48_t。如果计算类似的值(uint32_t)0xFFFFFFFF * (uint32_t)0xFFFFFFFF,则操作数将提升为signed int,并将触发有符号整数溢出,这是未定义的行为。看到这个问题。
娜雪(Nayuki)

32

为什么很多人使用uint32_t而不是uint32_fast_t

注意:命名错误uint32_fast_t应为uint_fast32_t

uint32_t具有更严格的规范uint_fast32_t,因此功能更加一致。


uint32_t 优点:

  • 各种算法都指定这种类型。IMO-使用的最佳理由。
  • 确切的宽度和范围已知。
  • 这种类型的阵列不会造成浪费。
  • 无符号整数数学及其溢出更可预测。
  • 其他语言的32位类型在范围和数学上更接近匹配。
  • 永不填充。

uint32_t 缺点:

  • 并非始终可用(但在2018年很少见)。
    例如:缺乏8/16/32位整数(9/18 /平台36位,其它)。
    例如:使用非2的补码的平台。旧的2200

uint_fast32_t 优点:

  • 始终可用。
    始终允许所有新旧平台使用快速/最小类型。
  • 支持32位范围的“最快”类型。

uint_fast32_t 缺点:

  • 范围仅是最低限度的已知。例如,它可以是64位类型。
  • 这种类型的阵列可能会浪费内存。
  • 所有答案(一开始也是我的答案),帖子和评论使用了错误的名称uint32_fast_t。看起来很多人根本不需要使用这种类型。我们甚至没有使用正确的名称!
  • 可能的填充-(稀有)。
  • 在某些情况下,“最快”类型实际上可能是另一种类型。因此uint_fast32_t只有一阶近似。

最后,最佳选择取决于编码目标。除非为非常广泛的可移植性或某些特定的性能功能进行编码,否则请使用uint32_t


使用这些类型时还存在另一个问题:与 int/unsigned

据推测uint_fastN_t可能是unsigned。这没有指定,但是有一定条件并且可以测试。

因此,uintN_tuint_fastN_t缩小范围更可能unsigned。这意味着,与可移植性uintN_t相比,使用数学的代码更有可能受到整数提升uint_fastN_t

考虑到这一点:uint_fastN_t选择数学运算具有可移植性优势。


关于int32_t而不是的注释int_fast32_t:在稀有机器上,INT_FAST32_MIN可能是-2,147,483,647,而不是-2,147,483,648。更大的要点:(u)intN_t类型被严格指定并导致可移植代码。


2
支持32位范围的最快类型=>真的吗?这是RAM以CPU速度运行时的遗迹,如今,平衡在PC上已经发生了巨大变化,因此(1)从内存中提取32位整数的速度是提取64位整数的速度的两倍,并且(2)矢量化指令在32位整数上的运算次数是在64位整数上的运算次数的两倍。真的还是最快的吗?
Matthieu M.

4
对于某些事物最快,而对于其他事物则较慢。 当您考虑数组与需要零扩展的情况时,“整数最快的大小”并没有一个万能的答案。 在x86-64 System V ABI中,uint32_fast_t它是64位类型的,因此,它可以保存偶数符号扩展,并且imul rax, [mem]在将其与64位整数或指针一起使用时,可以代替单独的零扩展加载指令。但是,这就是你得到双倍的缓存占用和额外的代码大小的价格(REX一切前缀。)
彼得·科德斯

1
此外,在大多数x86 CPU上,64位除法比32位除法要慢得多,并且某些(例如Bulldozer系列,Atom和Silvermont)的64位乘法比32位慢。Bulldozer系列也具有较慢的64位popcnt。请记住,将这种类型用于32位值仅是安全的,因为它在其他体系结构上较小,因此您无需为此付出任何代价。
彼得·科德斯

2
我希望作为所有C和C ++应用程序的加权平均值,uint32_fast_t在x86上进行制作是一个糟糕的选择。这是更快的是少之又少,当他们发生的操作,效益大多是微不足道的:为区别imul rax, [mem]是@PeterCordes提到的情况下是非常非常小:在未融合领域的融合领域和零一个微指令。在最有趣的情况下,它甚至不会添加一个周期。平衡内存使用量的两倍和更糟糕的向量化之间的平衡,很难看到它经常赢。
BeeOnRope

2
@PeterCordes-有趣但也很糟糕:)。这将使fast_t情况更糟int:不仅在不同平台上具有不同的大小,而且根据优化决策和不同文件中的不同大小,其大小也将不同!作为一个实际问题,我认为它甚至不能与整个程序的优化工作:在C和C ++的大小是固定的这样sizeof(uint32_fast_t)或任何其决定了它甚至直接拥有总是返回相同的值,因此它非常适合编译器执行艰难做这样的转变。
BeeOnRope

25

为什么很多人使用uint32_t而不是uint32_fast_t

愚蠢的答案:

  • 没有标准类型uint32_fast_t,正确的拼写是uint_fast32_t

实用答案:

  • 实际上uint32_tint32_t出于精确的语义,很多人使用32位无符号环绕算术(uint32_t)或2的补码表示(int32_t)。这些xxx_fast32_t类型可能更大,因此不适合存储到二进制文件,在打包的数组和结构中使用或通过网络发送。此外,它们甚至可能不会更快。

务实的答案:

  • uint_fast32_t正如评论和答案所示,许多人只是不知道(或根本不在乎),并且可能假设普通人unsigned int具有相同的语义,尽管许多当前体系结构仍然具有16位,int并且一些罕见的Museum样本具有其他奇怪的int大小小于32。

UX答案:

  • 尽管可能比快uint32_tuint_fast32_t但使用起来却更慢:键入时间更长,尤其是考虑到在C文档中查找拼写和语义;-)

优雅很重要,(显然基于观点):

  • uint32_t看起来很糟糕,以至于许多程序员更喜欢定义自己的类型u32uint32类型...从这个角度来看,uint_fast32_t看起来笨拙,无法修复。毫不奇怪,它与它的朋友uint_least32_t等坐在板凳上。

UX +1。它比std::reference_wrapper我想的要好,但有时我想知道标准委员会是否真的希望使用它标准化的类型……
Matthieu M.

7

原因之一是它unsigned int已经是“最快的”,不需要任何特殊的typedef或包含一些东西。因此,如果您快速需要它,只需使用基本intunsigned int类型。
尽管该标准未明确保证它是最快的,但它通过在3.9.1中声明“普通int具有执行环境的体系结构建议的自然大小”间接地做到这一点。换句话说,(或它的无符号对应项)是处理器最满意的。int

当然,现在您不知道unsigned int可能会有多大。您只知道它至少short()一样大(而且我似乎还记得它short必须至少为16位,尽管我现在在标准中找不到它!)。通常它只是简单的4个字节,但是理论上它可以更大,或者在极端情况下甚至可以更小(尽管我个人从未遇到过这种情况的架构,甚至在1980年代的8位计算机上也没有。 ..也许有些微控制器知道我患有痴呆症,int当时很明显是16位。

C ++标准不必费心指定<cstdint>类型是什么或它们保证什么,它仅提及“与C相同”。

uint32_t,根据C标准,可确保您准确获得32位。没什么不同,也没有填充位。有时这正是您所需要的,因此非常有价值。

uint_least32_t保证无论大小如何,它都不能小于32位(但也可能会更大)。有时,但这就是您想要的,而不是确切的机智或“无关”。

最后,uint_fast32_t在我看来,除了意图记录目的之外,这是多余的。C标准声明“指定通常是最快的整数类型”(请注意“通常”一词),并明确提到它不必为了所有目的都最快。换句话说,uint_fast32_t与几乎相同uint_least32_t通常也是最快的,没有给出任何保证(但没有任何一种保证)。

由于大多数的时候你要么不关心的确切大小,或者您想正是32(或64,有时16)位,因为“不关心”unsigned int的类型是最快的,无论如何,这解释了为什么uint_fast32_t是不是这样经常使用。


3
我很惊讶您不记得int8位处理器上的16位,而我不记得那些使用更大的东西的日子。如果有内存可用,则用于分段x86架构的编译器也使用16位int
Mark Ransom

@MarkRansom:哇,你是对的。我非常确信这int是68000上的32位(以我为例)。不是……
Damon

int本来是过去最快的类型,最小宽度为16位(这就是C具有整数提升规则的原因),但是今天对于64位体系结构,这不再是事实。例如8个字节的整数比x86_64位上的4个字节的整数快,因为使用4个字节的整数,编译器必须先插入将4个字节的值扩展为8个字节的值的附加指令,然后再将其与其他8个字节的值进行比较。
StaceyGirl '17

在x64上,“ unsigned int”不一定是最快的。奇怪的事情发生了。
约书亚

另一个常见的情况是long,由于历史原因,必须为32位,并且int现在不应该大于long,因此int即使64位更快,也可能需要保持32位。
戴维斯洛

6

我没有看到uint32_t可用于其范围的证据。取而代之的是,在见过的大多数时间里uint32_t,它都是在各种算法中精确地保存4个八位位组的数据,并保证了环绕和移位语义!

uint32_t代替使用其他原因还有uint_fast32_t:通常是它将提供稳定的ABI。另外,可以准确地知道内存使用情况。这非常偏移无论速度增益是从uint_fast32_t只要该类型是从截然不同uint32_t

对于<65536的值,已经有一个方便的类型,它被称为unsigned intunsigned short也必须至少具有该范围,但unsigned int具有本机字长)对于4429967296的值,还有一个方便的类型unsigned long


最后,人们不使用uint_fast32_t它,因为它很烦人,而且容易打错:D


@ikegami:您通过short编辑更改了我的意图。int大概是在时,其中一个是来自不同short
Antti Haapala

1
那么,您的最后一句话是完全错误的。声称应该使用unsigned int而不是uint16_fast_t意味着您声称比编译器了解更多。
ikegami

另外,我很抱歉更改您的文字意图。那不是我的意图。
ikegami

unsigned long如果您的平台具有64位long并且仅需要数字,则不是一个好选择<2^32
Ruslan

1
@ikegami:即使被提升,“ unsigned int”类型也将始终表现为无符号类型。在这方面,它优于uint16_tuint_fast16_t。如果uint_fast16_t比普通整数类型更宽松地指定,这样其范围就不必与不占用地址的对象保持一致,这可能会在内部执行32位算术但具有16位数据总线的平台上提供一些性能优势。 。但是,该标准不允许这种灵活性。
超级猫

5

几个原因。

  1. 许多人不知道“快速”类型的存在。
  2. 键入起来比较冗长。
  3. 当您不知道类型的实际大小时,就很难对程序的行为进行推理。
  4. 该标准实际上并没有最快地确定下来,也不能真正确定实际上最快的类型取决于上下文。
  5. 我没有看到任何证据表明平台开发人员在定义平台时会考虑这些类型的大小。例如,在x86-64 Linux上,即使x86-64具有对32位值的快速操作的硬件支持,“快速”类型也都是64位。

总之,“快速”类型是毫无价值的垃圾。如果确实需要确定哪种类型对于给定的应用程序最快,则需要在编译器上对代码进行基准测试。


从历史上看,有些处理器具有32位和/或64位内存访问指令,但没有8位和16位。因此,int_fast {8,16} _t在20多年前不是十分愚蠢。AFAIK上一个这样的主流处理器是原始的DEC Alpha 21064(改进了第二代21164)。也许仍有嵌入式DSP或任何只字做访问,但便携性一般不上这样的事情了极大的关注,所以我不明白你为什么会货物邪教于那些_t。还有手工制造的Cray“一切都是64位”计算机。
user1998586

1
1b类:许多人不在乎“快速”类型的存在。那是我的类别。
gnasher729

类别6:许多人不相信“快速”类型是最快的。我属于那个类别。
清晰的时间为

5

从正确性和易于编码的角度来看,如上文的许多用户所指出的,由于更精确地定义了大小和算术语义,因此uint32_t具有许多优势uint_fast32_t

什么或许已经被错过的是,一个所谓好处uint_fast32_t-它可以更快,只是从来没有以任何有意义的方式实现。主导64位时代的大多数64位处理器(大多数是x86-64和Aarch64)都是从32位体系结构演变而来的,即使在64位模式下也具有快速的32位本机操作。因此uint_fast32_tuint32_t这些平台上的相同。

即使某些“也可以运行”的平台(例如POWER,MIPS64,SPARC)仅提供64位ALU操作,绝大多数有趣的32位操作也可以在64位寄存器上完成:最低的32位将具有理想的结果(并且所有主流平台至少都允许您加载/存储32位)。左移是主要的问题之一,但即使在许多情况下,也可以通过编译器中的值/范围跟踪优化来优化。

我怀疑偶尔出现的稍微慢一些的左移或32x32-> 64乘法是否会超过除最晦涩的应用程序以外的所有内存,而不是这种值的两倍

最后,我会指出,虽然权衡的主要特征是“内存使用和矢量化潜力”(支持uint32_t)与指令数/速度(支持uint_fast32_t)-但这对我来说还不清楚。是的,在某些平台上,您需要一些有关32位操作的附加说明,但是您还将保存一些说明,因为:

  • 使用较小的类型通常允许编译器通过使用一个64位操作来完成两个32位操作来巧妙地组合相邻操作。这种“穷人矢量化”的例子并不少见。例如,可以将一个常量struct two32{ uint32_t a, b; }raxlike的创建two32{1, 2} 优化为一个,mov rax, 0x20001而64位版本则需要两个指令。原则上,这对于相邻的算术运算(相同的运算,不同的操作数)也应该是可能的,但是我在实践中还没有看到它。
  • 较低的“内存使用量”通常也导致较少的指令,即使内存或缓存占用空间不成问题,因为复制了任何类型的结构或这种类型的数组,每复制一个寄存器,您也会得到两倍的收益。
  • 较小的数据类型通常利用更好的现代调用约定,例如SysV ABI,该约定将数据结构数据有效地打包到寄存器中。例如,您最多可以在register中返回一个16字节的结构rdx:rax。对于具有4个uint32_t值的函数返回结构(从常量初始化),该结构将转换为

    ret_constant32():
        movabs  rax, 8589934593
        movabs  rdx, 17179869187
        ret
    

    具有4个64位的相同结构uint_fast32_t需要一个寄存器移动和四个存储到内存中以执行相同的操作(并且调用者可能必须在返回后从内存中读取值):

    ret_constant64():
        mov     rax, rdi
        mov     QWORD PTR [rdi], 1
        mov     QWORD PTR [rdi+8], 2
        mov     QWORD PTR [rdi+16], 3
        mov     QWORD PTR [rdi+24], 4
        ret
    

    类似地,在传递结构参数时,32位值被密集地封装到可用于参数的寄存器中,密度大约是后者的两倍,因此,它使寄存器用完参数并溢出到堆栈1的可能性降低。

  • 即使您选择uint_fast32_t在“速度很重要”的地方使用,您也会经常在某些地方需要固定大小的类型。例如,当传递外部输出的值时,来自外部输入的,作为ABI的一部分,作为需要特定布局的结构的一部分,或者因为您明智地使用uint32_t大型的值聚合来节省内存占用。在您的uint_fast32_t类型和``uint32_t''类型需要接口的地方,您可能会发现(除了开发复杂性之外),不必要的符号扩展或其他与大小不匹配相关的代码。在许多情况下,编译器可以很好地解决此问题,但是在混合使用不同大小的类型时,在优化输出中看到这一点并不罕见。

您可以使用上面的一些示例,以及有关Godbolt的更多示例


1需要明确的是,对于较小的值,将结构紧密打包到寄存器中的惯例并不总是一个明显的胜利。这确实意味着较小的值可能必须先“提取”后才能使用。例如,一个简单的函数返回两个结构成员的总和,mov rax, rdi; shr rax, 32; add edi, eax而对于64位版本,则需要一段时间,每个参数都拥有自己的寄存器,而只需要一个addor即可lea。仍然,如果您接受“通过时紧紧包装结构”设计在总体上有意义,那么较小的值将更多地利用此功能。


x86-64 Linux上的glibc使用64位uint_fast32_t,这是IMO的错误。(显然Windows在Windows上uint_fast32_t是32位类型。)在x86-64 Linux上是64位是为什么我永远不建议任何人使用uint_fast32_t:它针对低指令数进行了优化(函数args和返回值从不需要零扩展)用作数组索引),而不是主要重要平台之一上的整体速度或代码大小。
彼得·科德斯

2
哦,对,我阅读了您上面有关SysV ABI的评论,但是正如您稍后指出的那样,可能是由另一个小组/文档来决定的-但是我想一旦发生,它几乎是一成不变的。我认为,即使在没有良好32位操作支持的平台上,即使忽略了内存占用效应和向量化,纯周期计数/指令计数也偏爱较大的类型甚至是值得怀疑的,因为在某些情况下编译器可以更好地优化较小的类型。我在上面添加了一些示例。@PeterCordes
BeeOnRope

SysV的封装多重结构成员到同一个寄存器的成本更多返回指令时相当频繁pair<int,bool>pair<int,int>。如果两个成员都不都是编译时常量,则通常不只是一个OR,而且调用者必须解压缩返回值。(bugs.llvm.org/show_bug.cgi?id=34840 LLVM优化了私有函数的返回值传递,并且应将32位int视为整体,rax因此bool单独使用,dl而不是需要64位常量test
彼得·科德斯

1
我认为编译器通常不会拆分功能。将快速路径作为单独的功能删除是一种有用的源代码级优化(尤其是在可以内联的标头中)。如果90%的输入是“不做任何事”,这可能会很好;在调用者的循环中进行过滤是一个巨大的胜利。IIRC,Linux使用__attribute__((noinline))完全相同,以确保GCC不在线的错误处理功能,并把一堆push rbx/ .../ pop rbx/ ...的有许多呼叫者和本身并不内嵌了一些重要的内核函数的快速路径上。
彼得·科德斯

1
在Java中也非常重要,因为内联是进一步优化的关键(尤其是去虚拟化,与C ++不同,它普遍存在),因此通常需要在其中拆分出一条快速路径,而“字节码优化”实际上是一回事(尽管传统的观点认为,这是没有意义的,因为JIT进行了最终编译)只是为了减少字节码计数,因为内联决策基于字节码大小,而不是基于内联机器码大小(并且相关性可能因数量级而异)。
BeeOnRope

4

出于实际目的,uint_fast32_t是完全没有用的。在最广泛使用的平台(x86_64)上,它的定义不正确,除非您使用了质量很低的编译器,否则它在任何地方都没有真正的优势。从概念上讲,在数据结构/数组中使用“快速”类型从来没有任何意义-通过增加数据类型/大小的成本(缓存未命中等),您从该类型上获得的更多节省将无法实现。您的工作数据集。对于单个局部变量(循环计数器,临时变量等),非玩具编译器通常可以在生成的代码中使用更大的类型(如果这样做更有效),并且只能在必要时截断为标称大小以确保正确性(以及签名类型,则永远都不需要)。

从理论上讲,一个有用的变体是uint_least32_t,用于需要存储任何32位值,但又希望可移植到缺少精确大小的32位类型的机器上的情况。但是,实际上,这并不是您需要担心的。


4

据我了解,int最初应该是“本机”整数类型,并另外保证它的大小至少应为16位-那时该大小被认为是“合理”的。

当32位平台变得更加普遍时,我们可以说“合理”的大小已更改为32位:

  • 现代Windowsint在所有平台上都使用32位。
  • POSIX保证int至少为32位。
  • C#,Java的类型int可以保证精确为32位。

但是当64位平台成为标准时,int由于以下原因,没有人将其扩展为64位整数:

  • 可移植性:很多代码取决于int32位的大小。
  • 内存消耗:int在大多数情况下,将每种内存使用量翻倍可能是不合理的,因为在大多数情况下,使用的数量远小于20亿。

现在,你为什么会喜欢uint32_tuint_fast32_t?出于相同的原因,C#和Java也总是使用固定大小的整数:程序员不会考虑考虑不同类型的可能大小而编写代码,而是为一个平台编写并在该平台上测试代码。大多数代码隐式依赖于数据类型的特定大小。这就是为什么uint32_t在大多数情况下是更好的选择的原因-它不允许对其行为有任何歧义。

此外,uint_fast32_t在大小等于或大于32位的平台上,真的是最快的类型吗?并不是的。考虑Windows上的GCC for x86_64的以下代码编译器:

extern uint64_t get(void);

uint64_t sum(uint64_t value)
{
    return value + get();
}

生成的程序集如下所示:

push   %rbx
sub    $0x20,%rsp
mov    %rcx,%rbx
callq  d <sum+0xd>
add    %rbx,%rax
add    $0x20,%rsp
pop    %rbx
retq

现在,如果将get()的返回值更改为uint_fast32_t(在Windows x86_64上为4字节),则会得到以下信息:

push   %rbx
sub    $0x20,%rsp
mov    %rcx,%rbx
callq  d <sum+0xd>
mov    %eax,%eax        ; <-- additional instruction
add    %rbx,%rax
add    $0x20,%rsp
pop    %rbx
retq

注意,除了mov %eax,%eax函数调用后的附加指令(该指令将32位值扩展为64位值)外,生成的代码几乎相同。

如果仅使用32位值,则不会出现此类问题,但是您可能会使用带有size_t变量的值(可能是数组大小?),而在x86_64上是64位。在Linux上uint_fast32_t是8个字节,因此情况有所不同。

许多程序员int在需要返回较小值时使用它们(例如,在[-32,32]范围内)。如果int使用平台本机整数大小,这将是完美的选择,但是由于它不在64位平台上,因此与平台本机类型匹配的另一种类型是更好的选择(除非它经常与其他较小大小的整数一起使用)。

基本上,无论标准说什么,uint_fast32_t总之在某些实现上都是破坏的。如果您关心某些地方生成的其他指令,则应定义自己的“本机”整数类型。或者,您可以size_t为此目的使用它,因为它通常会匹配native大小(我不包括像8086这样古老而晦涩的平台,仅包括可以运行Windows,Linux等的平台)。


另一个int应该显示为本地整数类型的标志是“整数提升规则”。大多数CPU只能在本机上执行操作,因此32位CPU通常只能执行32位加法,减法等操作(Intel CPU在这里是一个例外)。仅通过加载和存储指令支持其他大小的整数类型。例如,应使用适当的“加载8位有符号”或“加载8位无符号”指令加载8位值,并在加载后将值扩展为32位。没有整数提升规则,C编译器将不得不为使用小于本地类型的类型的表达式添加更多代码。不幸的是,这在64位体系结构上已不再适用,因为在某些情况下编译器现在必须发出其他指令(如上所示)。


2
关于“没有人将int扩展为64位整数的想法”和“不幸的是,对于64位体系结构,这不再适用”的想法是很不错的。公平地说,比较“最快”并比较汇编代码:在这种情况下,第二条代码片段的附加指令似乎比较慢,但是有时代码长度和速度之间并没有很好的相关性。比较强的比较会报告运行时间-但这并不是那么容易。
chux-恢复莫妮卡

我不是很容易衡量第二个代码的速度,英特尔CPU可能做得很好,但是更长的代码也意味着大量的缓存污染。偶尔执行一条指令可能不会造成伤害,但是uint_fast32_t的用途变得模棱两可。
StaceyGirl '17

我非常同意uint_fast32_t在几乎所有情况下(除了非常特殊的情况下)变得模糊不清的用处。我怀疑uint_fastN_t根本的驱动原因 是为了适应“unsigned即使在新平台上它通常不是最快的,也不要将其用作64位,因为太多的代码会中断”,但“我仍然想要至少N位的快速类型。 。” 如果可以的话,我会再给你紫外线。
chux-恢复莫妮卡

大多数64位体系结构都可以轻松地在32位整数上运行。甚至DEC Alpha(这是一个新的分支64位体系结构,而不是对现有的32位ISA(如PowerPC64或MIPS64)的扩展)也具有32位和64位加载/存储。(但不是字节或16位加载/存储!)。大多数指令仅是64位,但是它具有对32位加/减的本机硬件支持,并将其相乘将结果截断为32位。(alasir.com/articles/alpha_history/press/alpha_intro.html)因此,制作int64位数据几乎不会提高速度,通常缓存占用空间也会降低速度。
彼得·科德斯

另外,如果您使用的是int64位,则uint32_t固定宽度typedef将需要__attribute__或其他技巧,或者一些小于的自定义类型int。(或者short,但是那样的话您也有同样的问题uint16_t。)没有人想要那个。32位几乎可以容纳所有内容(不同于16位);在需要的时候使用32位整数并不是在64位计算机上以任何有意义的方式“低效”。
彼得·科德斯

2

在许多情况下,当算法对数据数组进行处理时,提高性能的最佳方法是最大程度地减少高速缓存未命中的次数。每个元素越小,它们可以容纳到缓存中的数量就越多。这就是为什么仍然需要编写许多代码以在64位计算机上使用32位指针的原因:它们不需要接近4 GiB的数据,但是制作所有指针和偏移量的成本需要8个字节而不是4个字节将是实质性的。

也有一些ABI和协议被指定为恰好需要32位,例如IPv4地址。那才是uint32_t真正的意思:精确地使用32位,而不管这在CPU上是否有效。以前将它们声明为longor unsigned long,这在64位转换期间引起了很多问题。如果您只需要一个无符号类型,该类型最多可以容纳2³²-1的数字,那么unsigned long自从第一个C标准问世以来,这就是定义。但是,在实践中,足够多的旧代码假定along可以容纳任何指针,文件偏移量或时间戳,而足够多的旧代码假定其恰好是32位宽,那么编译器就不必longint_fast32_t不破坏太多内容的情况下实现相同的功能。

从理论上讲,程序使用起来会更适合未来uint_least32_t,甚至可能将uint_least32_t元素加载到uint_fast32_t变量中进行计算。完全没有uint32_t类型的实现甚至可以声明自己符合标准!(它只是将无法编译许多现有的程序)。在实践中,没有任何建筑,其中多intuint32_tuint_least32_t是不一样的,并没有优势,目前,以表现uint_fast32_t。那么为什么要使事情复杂化呢?

然而,看看32_t我们已经拥有时所有类型都必须存在的原因long,您会发现这些假设以前已经浮出水面。您的代码很可能最终有一天会在一台计算机上运行,​​该计算机上的精确宽度32位计算比本地字长慢,并且最好使用它uint_least32_t进行存储和uint_fast32_t计算。或者,如果您在到达那座桥时只是想做些简单的事情,就过桥了unsigned long


但是有些体系结构中int没有32位,例如ILP64。并不是说它们很常见。
安蒂·哈帕拉

我认为现在时态不存在ILP64吗?多个网页声称“ Cray”使用了它,所有这些都引用了1997年以来的Unix.org页面,但是90年代中期的UNICOS实际上做得有些奇怪,今天的Crays使用了Intel硬件。该页面还声称ETA超级计算机使用了ILP64,但很久以前就倒闭了。维基百科声称HAL从Solaris到SPARC64的端口使用ILP64,但是它们也已经停产了多年。CppReference表示,ILP64仅在少数早期的64位Unices中使用。因此,它仅与某些非常深奥的逆向计算有关。
戴维斯洛

请注意,如果您今天使用英特尔数学内核库的“ ILP64接口”,int则将为32位宽。类型MKL_INT将改变。
戴维斯洛

1

给出一个直接的答案:我认为uint32_t使用过uint_fast32_t或者真正的原因uint_least32_t仅仅是因为它更容易键入,并且由于它更短,因此更易于阅读:如果您使用某些类型构造结构,其中一些是uint_fast32_t或类似,那么通常很难将它们与C中的intorbool或其他类型很好地对齐,它们很短(例如:charvs character.)。我当然不能用硬数据来支持这一点,但是其他答案也只能猜测原因。

出于技术原因uint32_t,我不认为有任何理由-当您绝对需要精确的32位unsigned int时,此类型是您唯一的标准选择。在几乎所有其他情况下,其他变体在技术上是更可取的-特别是,uint_fast32_t如果您担心速度,并且uint_least32_t担心存储空间。uint32_t在这两种情况中的任何一种情况下使用时,都有可能无法编译,因为该类型不需要存在。

实际上,uint32_t除了一些非常罕见的(当今)DSP或笑话实现之外,所有当前平台上都存在和类型的关联,因此使用确切类型的实际风险很小。类似地,虽然您可能会遇到固定宽度类型的速度损失,但它们(在现代cpus上)不再瘫痪了。

我认为,这就是为什么较短的类型在大多数情况下会由于程序员的懒惰而最终胜出。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.