【问题标题】:__int128 alignment segment fault with gcc -O SSE optimize__int128 对齐段错误与 gcc -O SSE 优化
【发布时间】:2023-04-01 22:01:01
【问题描述】:

我使用__int128 作为结构的成员。 它适用于 -O0 查找(无优化)。

但是,如果启用优化,它会因段错误而崩溃 (-O1)。

它在指令 movdqa 处崩溃,该指令需要 var 对齐 16。 而地址是由malloc() 分配的,它只对齐8。

我尝试通过-mno-sse禁用SSE优化,但编译失败:

/usr/include/x86_64-linux-gnu/bits/stdlib-float.h:27:1: error: SSE register return with SSE disabled

如果我想同时使用__int128-O1 该怎么办?

提前致谢 吴

顺便说一句,如果__int128 仅用于堆栈(而不是堆)似乎没问题。

==== 编辑 ====

对不起,我没有说实话。

其实我没有使用malloc()。我使用了一个内存池库,它返回由 8 对齐的地址。 我说malloc()只是想让事情变得简单。

经过测试,我知道malloc() 对齐 16。__int128 成员在结构中也对齐 16。

所以问题只是我的内存池库。

非常感谢。

【问题讨论】:

  • 对于 x86-64,alignof(maxalign_t) == 16 所以malloc 总是返回 16 字节对齐的指针。听起来您的 malloc 已损坏,如果用于 long double 也会违反 ABI。 malloc 保证对齐到足以容纳任何标准类型。这不能是 32 位代码,因为 gcc 在 32 位目标中不支持 __int128
  • 你能提供一个minimal reproducible example吗?您使用的是什么编译器和库版本,在什么 Linux 发行版上?
  • 我错了,我想当然地认为malloc() 对齐 8。谢谢。

标签: gcc compiler-optimization sse memory-alignment


【解决方案1】:

对于 x86-64 System V,alignof(max_align_t) == 16 所以malloc 总是返回 16 字节对齐的指针。听起来您的分配器已损坏,并且如果用于 long double 也会违反 ABI。 (将其重新发布为答案,因为事实证明它答案)。

malloc 返回的内存保证能够容纳任何标准类型,这意味着如果大小足够大,则足够对齐。

这不能是 32 位代码,因为 gcc 不支持 32 位目标中的 __int128。 (我认为 32 位 glibc malloc 仅保证 8 字节对齐。实际上在当前系统上,alignof(max_align_t) == 16 在 32 位模式下也是如此。)


一般来说,如果您违反类型的对齐要求,编译器可以生成出错的代码。在 x86 上,通常只使用未对齐的内存,直到编译器使用需要对齐的 SIMD 指令。即使使用未对齐的uint16_t* 进行自动矢量化也会出错(Why does unaligned access to mmap'ed memory sometimes segfault on AMD64?),所以不要假设窄类型总是安全的。如果您需要在 C 中表达未对齐的负载,请使用 memcpy


显然alignof(__int128) 是 16。所以他们没有重复 i386 System V 的怪异之处,即使是 8 字节对象也只能保证 4 字节对齐,而结构打包规则意味着编译器不能总是给它们自然对齐。

这是一件好事,因为它使使用 SSE 复制变得高效,并且意味着 _Atomic __int128 不需要任何额外的特殊处理来避免缓存行拆分,这会使 lock cmpxchg16b 变得非常慢。

【讨论】:

  • __int128 不是总是表示为一对 64 位整数吗?我发现它具有 16 字节对齐而不是 8 字节对齐很奇怪,这是包含两个 64 位整数的结构所具有的。此外,AFAICT,因为调用约定需要在 2 x 64 位寄存器中传递它,所以(几乎)不值得使用 SIMD 指令进行复制。您必须先将类型移入 SIMD 寄存器,然后再返回,但没有任何 SIMD 操作可用于处理 128 位整数,因此这不太值得。
  • @gnzlbg:函数 args 确实如此。但是__int128 可以在任何地方使用,包括结构、数组、全局变量等。在某些情况下(如问题中),将__int128 从内存复制到内存是编译器决定做的事情,但事实并非如此稍后需要整数寄存器中的值来实际对其进行算术运算。但是是的,在__int128大多数 用例中,您不只是在不进行数学运算的情况下复制它们,因此编译器将使用一对整数加载/存储。或者,如果没有间接,它们已经作为参数或返回值存在于寄存器中。
  • 如果你通过内存复制这些,例如,因为你正在复制一个结构或数组,那么这将通过 memcpy 发生,它将在适当的时候在内部使用 SIMD,并且更少的字节它必须复制更好。我看不出将对齐提高到 16 有什么帮助。这基本上提高了包含__int128 的任何内容的对齐方式,并可能引入填充,这反过来可能最终使memcpy 变慢。
  • @gnzlbg:用于复制单个__int128,gcc 内联movdqa;它并没有真正调用memcpy。早在 2001 年左右,当 x86-64 仍然是纸质规范并且正在设计 ABI 时,设计者认为movdqa 总是比movdqu 快。 (在 Nehalem / Bulldozer 之前,这是一个正确的假设。)请记住,SSE2 是 x86-64 的 baseline,因此设置 ABI 是为了利用这一优势。即使在现代 CPU 上,缓存行拆分仍然有一些成本。使用movdqa 需要编译时保证__int128 对象是16 字节对齐的; ABI 选择在任何地方都这样做。
  • @gnzlbg:给__int128 完全对齐不会增加数组的大小,或者(如果你把它放在第一位)一个结构。您经常希望排列结构以避免填充,通常是从最对齐到最不对齐的顺序排列。
猜你喜欢
  • 2021-06-10
  • 2011-12-16
  • 1970-01-01
  • 1970-01-01
  • 2011-03-14
  • 1970-01-01
  • 2019-03-02
  • 2015-05-13
  • 2013-10-07
相关资源
最近更新 更多