考虑下面的代码。我们知道__uint128_t
变量存储在2个64位寄存器中(假设x64处理器)。要求是将前64位存储在一个无符号长变量中,将后64位存储在另一个无符号长变量中。
__uint128_t a = SOMEVALUE;
unsigned long b = a&0xffffffffffffffff;
unsigned long c = a>>64;
字符串
这里,B 存储前64位,c 存储后64位。是否有其他更简单的方法来分别访问2个寄存器,而不是执行&
和>>
操作?我问这个是因为对于我的项目来说,这段代码将被执行一万多亿次。所以还是先验证一下这个疑惑吧。
有什么我能用的汇编代码吗?
3条答案
按热度按时间nuypyhwy1#
您所写的可能是最好的,尽管通过强制转换进行截断比长常量更容易阅读。作为一个经验法则,如果你写的代码是明显和清晰的,那么编译器通常最容易看到你的意图并进行适当的优化。
在Compiler Explorer上,我提供了这个函数:
字符串
当使用
gcc -O3
为x64编译时,它会生成您想要的代码:型
7qhs6swi2#
Shift/mask或工会是要走的路。特别是如果你只想读取
__int128
的部分,位操作是清晰的,并且可以可靠地高效编译。**如果你要替换高64位或低64位,
union
**可能比按位掩码/移位/ OR更容易让编译器看到。如果这两种方式都能高效地编译,我不会感到惊讶,但union
可能对人类的可读性有好处。请注意,联合中的半部分的排序将取决于字节序,而位移位不依赖于字节序。
我建议使用**
uint64_t
或unsigned long long
**而不是unsigned long
,因为Windows x64使用32位long
。大多数其他64位ABI使用LP 64 ABI,但32位long
的另一种情况是用于64位CPU的ILP 32 ABI,如AArch 64 ILP 32和x32 ABI。sizeof(void*) = 4
,但仍支持__int128
。我会使用强制转换将
__int128
截断为64位,而不必在0xffffffffffffffff
中键入正确的f
数量。对我来说,**(uint64_t)a
**更好地遵循了Toby的“显而易见”的指导方针。使强制转换显式化,而不是仅仅通过赋值给一个更窄的变量,这对人类读者有好处。C保证从较宽的整数类型到较窄的无符号类型的模归约,这意味着从无符号或2的补码有符号的源类型进行按位截断。(GCC中的有符号整数是always 2's complement。a>>64
完全没问题。即使对于有符号的__int128
,算术右移然后赋值为64位类型也会丢弃高64个符号位,这些符号位可能是全1或全0,GCC仍然会优化它。字符串
对于x86-64,这两个都编译为
lea rax, [rdi + rsi]
/ret
。(Godbolt)。128位整数类型名
在现代GNU C中,the manual目前只提到(
unsigned
)__int128
,而不是__uint128_t
。AFAIK,继续使用遗留
__uint128_t
没有错; GCC开发人员没有理由要删除相同类型的名称。请参阅Is there a 128 bit integer in gcc?-__int128
自GCC4.6以来就一直存在,目前已经很老了。但除非你关心古老的GCC版本,否则我推荐unsigned __int128
用于新代码,就像我上面的例子一样。在ISO C23中,
unsigned _BitInt(128)
将被标准化,因此您可能更喜欢这样。但据我所知,只有clang支持它(但不限于像__int128
/__uint128_t
那样的64位目标)。在新代码中,最好使用typedef
这使您可以根据需要更改为便携式
_BitInt
,并保存键入。型
如果你发现移位和/或强制转换会给你的代码增加噪音,你可以编写帮助函数或宏。
型
然后,您可以简单地使用
hi64(x)
和/或lo64(x)
。m3eecexj3#
变量不存储在寄存器中。它们存储在内存中,并在寄存器中进行处理。
C语言提供了
union
结构来以多种方式Map数据,如字符串
现在你可以随意引用
u.a
、u.b[0]
和u.b[1]
,编译器被认为可以为给定的处理器生成高效的代码。请注意,使用掩码和移位的构造永远不会这样实现,因为处理器无法一次处理128个数据。相反,您的
a
将始终被处理为两个64位数字。事实上,将永远不会执行掩蔽和移位。