它似乎uint32_t比uint_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受青睐?是简单地知道还是比其他技术有优势?
Yak*_*ont 76
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.这可能会导致错误.没有什么是完美的.
chu*_*ica 31
为什么很多人使用
uint32_t而不是uint32_fast_t?
注意:错误的名字uint32_fast_t应该是uint_fast32_t.
uint32_t具有更严格的规范uint_fast32_t,因此可以实现更一致的功能.
uint32_t 优点:
uint32_t 缺点:
uint_fast32_t 优点:
uint_fast32_t 缺点:
uint32_fast_t.看起来很多人只是不需要并使用这种类型.我们甚至没有使用正确的名字!uint_fast32_t只是一阶近似.最后,最好的取决于编码目标.除非编码具有非常广泛的可移植性或某些特定的性能功能,否则请使用uint32_t.
使用这些类型时还有另一个问题:它们的排名与 int/unsigned
据推测uint_fastN_t至少是等级unsigned.这没有指定,但是具有一定的可测试条件.
因此,uintN_t更可能是更uint_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类型被严格指定并导致可移植代码.
chq*_*lie 25
为什么很多人使用
uint32_t而不是uint32_fast_t?
傻回答:
uint32_fast_t,拼写正确uint_fast32_t.实际答案:
uint32_t或者使用int32_t精确的语义,正好32位,无符号环绕arithmetic(uint32_t)或2的补码表示(int32_t).所述xxx_fast32_t类型可以是较大的,因此不宜存储二进制文件,在堆积阵列和结构使用,或发送在网络上.此外,他们甚至可能不会更快.务实的答案:
uint_fast32_t,正如评论和答案中所证明的那样,并且可能假设unsigned int具有相同的语义,尽管许多当前的架构仍然具有16位int且一些罕见的博物馆样本具有其他奇怪的int大小小于32.UX回答:
uint32_t,uint_fast32_t是慢的使用方法:它需要更长的时间来输入,尤其是占查找拼写和语法的C文档中;-)优雅很重要,(显然基于意见):
uint32_t看起来很糟糕,许多程序员喜欢定义自己的u32或uint32类型......从这个角度看,uint_fast32_t看起来笨拙无法修复.毫不奇怪,它和朋友一起坐在板凳上uint_least32_t等等.一个原因是,unsigned int已经"最快",不需要任何特殊的typedef或需要包含某些东西.因此,如果您需要快速,只需使用基础int或unsigned int类型.
虽然标准没有明确保证它是最快的,但它通过在3.9.1中说明"普通内容具有执行环境的体系结构所建议的自然大小"来间接地这样做.换句话说,(或其未签名的对应物)是处理器最舒适的.int
当然,你现在不知道它的大小unsigned int.你只知道它至少和它一样大short(我似乎记得short必须至少16位,虽然我现在在标准中找不到它!).通常它只是简单的4个字节,但它理论上可以更大,或者在极端情况下,甚至更小(尽管我个人从未遇到过这种情况的架构,甚至在20世纪80年代的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是不是这样经常使用.
我没有看到uint32_t用于其范围的证据.相反,在我看到的大多数时候uint32_t都使用它,它是在各种算法中保存4个八位字节的数据,保证环绕和移位语义!
还有其他原因可以uint32_t代替uint_fast32_t:通常它会提供稳定的ABI.另外,可以准确地知道存储器使用.无论速度增益是什么uint_fast32_t,只要该类型与其不同,这就非常有用uint32_t.
对于值<65536,已经有一个方便的类型,它被调用unsigned int(unsigned short需要至少具有该范围,但是unsigned int具有本机字大小)对于值<4294967296,还有另一个被调用unsigned long.
最后,人们不使用,uint_fast32_t因为打字很烦人,容易输入错误:D
几个原因.
总之,"快速"类型是毫无价值的垃圾.如果您确实需要确定给定应用程序的最快类型,则需要在编译器上对代码进行基准测试.
小智 5
据我了解,int最初应该是“本机”整数类型,并额外保证其大小至少为 16 位 - 当时被认为是“合理”大小。
当 32 位平台变得更加普遍时,我们可以说“合理”大小已更改为 32 位:
int。int至少为 32 位。int保证正好是 32 位。但当 64 位平台成为常态时,没有人扩展int为 64 位整数,因为:
int32 位大小。int可能是不合理的,因为在大多数情况下使用的数字远小于 20 亿。现在,你为什么uint32_t愿意uint_fast32_t?出于同样的原因,C# 和 Java 语言始终使用固定大小的整数:程序员编写代码时不会考虑不同类型的可能大小,他们会为一个平台编写代码并在该平台上测试代码。大多数代码隐式依赖于数据类型的特定大小。这就是为什么uint32_t在大多数情况下这是一个更好的选择 - 它不允许其行为有任何歧义。
而且,uint_fast32_t大小真的等于或大于 32 位的平台上最快的类型吗?并不真地。考虑 GCC 在 Windows 上为 x86_64 编译的代码:
extern uint64_t get(void);
uint64_t sum(uint64_t value)
{
return value + get();
}
Run Code Online (Sandbox Code Playgroud)
生成的程序集如下所示:
push %rbx
sub $0x20,%rsp
mov %rcx,%rbx
callq d <sum+0xd>
add %rbx,%rax
add $0x20,%rsp
pop %rbx
retq
Run Code Online (Sandbox Code Playgroud)
现在,如果将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
Run Code Online (Sandbox Code Playgroud)
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 位架构来说不再适用,因为编译器现在必须在某些情况下发出额外的指令(如上所示)。
从正确性和编码的容易性的观点来看,uint32_t具有许多优点uint_fast32_t,特别是因为更精确定义的大小和算术语义,正如上面许多用户所指出的那样.
可能遗漏的是那个被认为优势的东西uint_fast32_t- 它可以更快,从未以任何有意义的方式实现.大多数主导64位时代的64位处理器(主要是x86-64和Aarch64)都是从32位架构发展而来,即使在64位模式下也具有快速的 32位本机操作.所以uint_fast32_t就像uint32_t在那些平台上一样.
即使某些"运行"平台如POWER,MIPS64,SPARC仅提供64位ALU操作,绝大多数有趣的32位操作也可以在64位寄存器上完成:底部32位将有所需的结果(所有主流平台至少允许你加载/存储32位).左移是主要的问题,但在许多情况下,甚至可以通过编译器中的值/范围跟踪优化来优化.
我怀疑偶尔稍慢的左移或32x32 - > 64乘法将超过这些值的内存使用的两倍,除了最模糊的应用程序.
最后,我会注意到,尽管权衡主要被描述为"内存使用和向量化潜力"(赞成uint32_t)与指令数量/速度(有利于uint_fast32_t) - 即使这一点我也不清楚.是的,在某些平台上,您需要针对某些 32位操作的其他说明,但您还需要保存一些指令,因为:
struct two32{ uint32_t a, b; }为raxlike two32{1, 2} 可以优化为单个,mov rax, 0x20001而64位版本需要两个指令.原则上,对于相邻的算术运算(相同的操作,不同的操作数)也应该是可能的,但我在实践中没有看到它.较小的数据类型通常利用更好的现代调用约定,如SysV ABI,它将数据结构数据有效地打包到寄存器中.例如,您可以在寄存器中返回最多16字节的结构rdx:rax.对于具有4个uint32_t值的函数返回结构(从常量初始化),转换为
ret_constant32():
movabs rax, 8589934593
movabs rdx, 17179869187
ret
Run Code Online (Sandbox Code Playgroud)
具有4个64位的相同结构uint_fast32_t需要寄存器移动和4个存储器以执行相同的操作(并且调用者可能需要在返回后从内存中读取值):
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
Run Code Online (Sandbox Code Playgroud)
类似地,当传递结构参数时,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位版本,每个参数都有自己的寄存器,只需要一个add或者lea.如果你接受"通过时紧密包装结构"设计总体上是有意义的,那么较小的值将更多地利用这个功能.
| 归档时间: |
|
| 查看次数: |
8183 次 |
| 最近记录: |