x32 ABI指定了为x86_64架构生成的代码的32位指针。它结合了x86_64架构(包括64位CPU寄存器)的优点和减少的32位指针开销。
<stdint.h>
标头定义了typedef int_fast8_t
,int_fast16_t
,int_fast32_t
和int_fast64_t
(以及相应的无符号类型uint_fast8_t
等),每个都是:
一种整数类型,通常最快 至少具有指定宽度的整数类型
脚注:
不保证指定类型对所有目的都是最快的; 如果实施没有明确的理由选择一种类型 另外,它会简单地选择一些满足的整数类型 签名和宽度要求。
(引自N1570 C11 draft。)
问题是,无论是否使用x32 ABI,如何为x86_64架构定义[u]int_fast16_t
和[u]int_fast32_t
类型?是否有指定这些类型的x32文档?它们是否应与32位x86定义(均为32位)兼容,或者,由于x32可以访问64位CPU寄存器,它们是否应该具有相同的大小,有或没有x32 ABI? (请注意,无论x32 ABI是否正在使用,x86_64都有64位寄存器。)
这是一个测试程序(取决于特定于gcc的__x86_64__
宏):
#include <stdio.h>
#include <stdint.h>
#include <limits.h>
int main(void) {
#if defined __x86_64__ && SIZE_MAX == 0xFFFFFFFF
puts("This is x86_64 with the x32 ABI");
#elif defined __x86_64__ && SIZE_MAX > 0xFFFFFFFF
puts("This is x86_64 without the x32 ABI");
#else
puts("This is not x86_64");
#endif
printf("uint_fast8_t is %2zu bits\n", CHAR_BIT * sizeof (uint_fast8_t));
printf("uint_fast16_t is %2zu bits\n", CHAR_BIT * sizeof (uint_fast16_t));
printf("uint_fast32_t is %2zu bits\n", CHAR_BIT * sizeof (uint_fast32_t));
printf("uint_fast64_t is %2zu bits\n", CHAR_BIT * sizeof (uint_fast64_t));
}
当我用gcc -m64
编译时,输出为:
This is x86_64 without the x32 ABI
uint_fast8_t is 8 bits
uint_fast16_t is 64 bits
uint_fast32_t is 64 bits
uint_fast64_t is 64 bits
当我用gcc -mx32
编译时,输出为:
This is x86_64 with the x32 ABI
uint_fast8_t is 8 bits
uint_fast16_t is 32 bits
uint_fast32_t is 32 bits
uint_fast64_t is 64 bits
(除第一行外,它将输出与gcc -m32
匹配,后者生成32位x86代码)。
这是glibc中的错误(定义{{1}}标头),还是遵循一些x32 ABI要求? x32 ABI document或x86_64 ABI document中没有<stdint.h>
类型的引用,但可能还有其他内容指定了它。
有人可能认为fast16和fast32类型应该是64位或x32,因为64位寄存器是可用的;会对当前行为更有意义吗?
(我已经对原始问题进行了大量编辑,该问题仅涉及x32 ABI。现在问题是x86_64是否有x32。)
答案 0 :(得分:1)
一般来说,您会期望32位整数类型比x86-64 CPU上的64位整数类型快一些。部分是因为它们使用较少的内存,但也因为64位指令需要额外的前缀字节超过其32位对应物。 32位除法指令明显快于64位指令,但其他指令执行延迟相同。
将它们加载到64位寄存器时通常不需要扩展32位。虽然在这种情况下CPU会自动对值进行零扩展,但这通常只是一个好处,因为它可以避免部分寄存器停顿。加载到寄存器上部的内容不如整个寄存器被修改的事实重要。寄存器上半部分的内容无关紧要,因为当它们用于保存32位类型时,它们通常仅用于32位指令,这些指令仅适用于寄存器的低32位部分。
当使用x32和x86-64 ABI时,int_fast32_t
类型的大小之间的不一致可能是指针是64位宽的最佳证明。每当向指针添加32位整数时,就需要对其进行扩展,这在使用x86-64 ABI时更容易发生。
要考虑的另一个因素是x32 ABI的重点是通过使用较小的类型来获得更好的性能。任何受益于指针和相关类型较小的应用程序也应该受益于int_fast32_t
更小。
答案 1 :(得分:0)
我编译了以下示例代码,以检查生成的代码,以获得具有不同整数类型的简单求和:
#include <stdint.h>
typedef int16_t INT;
//typedef int32_t INT;
//typedef int64_t INT;
INT foo()
{
volatile INT a = 1, b = 2;
return a + b;
}
然后我反汇编了每个整数类型生成的代码。编译命令为gcc -Ofast -mx32 -c test.c
。请注意,在完整的64位模式下,生成的代码几乎相同,因为我的代码中没有指针(只有%rsp
而不是%esp
)。
int16_t
会发出:
00000000 <foo>:
0: b8 01 00 00 00 mov $0x1,%eax
5: ba 02 00 00 00 mov $0x2,%edx
a: 67 66 89 44 24 fc mov %ax,-0x4(%esp)
10: 67 66 89 54 24 fe mov %dx,-0x2(%esp)
16: 67 0f b7 54 24 fc movzwl -0x4(%esp),%edx
1c: 67 0f b7 44 24 fe movzwl -0x2(%esp),%eax
22: 01 d0 add %edx,%eax
24: c3 retq
使用int32_t
:
00000000 <foo>:
0: 67 c7 44 24 f8 01 00 00 00 movl $0x1,-0x8(%esp)
9: 67 c7 44 24 fc 02 00 00 00 movl $0x2,-0x4(%esp)
12: 67 8b 54 24 f8 mov -0x8(%esp),%edx
17: 67 8b 44 24 fc mov -0x4(%esp),%eax
1c: 01 d0 add %edx,%eax
1e: c3 retq
使用int64_t
:
00000000 <foo>:
0: 67 48 c7 44 24 f0 01 00 00 00 movq $0x1,-0x10(%esp)
a: 67 48 c7 44 24 f8 02 00 00 00 movq $0x2,-0x8(%esp)
14: 67 48 8b 54 24 f0 mov -0x10(%esp),%rdx
1a: 67 48 8b 44 24 f8 mov -0x8(%esp),%rax
20: 48 01 d0 add %rdx,%rax
23: c3 retq
现在,我没有声称确切地知道编译器为什么生成了这个代码(可能volatile
关键字与非寄存器大小整数类型组合不是最佳选择?)。但是根据生成的代码,我们可以得出以下结论:
int16_t
。它需要额外的指令来移动值。int32_t
。虽然32位和64位版本具有相同数量的指令,但32位代码的字节数较短,因此它将更加缓存,因此速度更快。因此,快速类型的自然选择是:
int_fast16_t
,请选择int32_t
。int_fast32_t
,请选择int32_t
。int_fast64_t
,请选择int64_t
(还有其他内容)。答案 2 :(得分:-3)
坚韧。我们来看看int_fast8_t。如果开发人员使用大型数组来存储大量8位有符号整数,那么由于缓存,int8_t将是最快的。我声明使用大型int_fast8_t数组可能是一个坏主意。
您需要使用大型代码库,并且如果使用int_fast8_t签名,则系统地替换int8_t和signed char以及plain char。然后使用int_fast8_t的不同typedef对代码进行基准测试,并测量最快的代码。
请注意,未定义的行为将会发生变化。例如,如果类型为int8_t,则赋值255将给出-1的结果,否则为255。