10

我正在为 Windows 64 位目标使用基于 GCC 4.6.1 的 MinGW64 构建。我正在玩新的英特尔 AVX 指令。我的命令行参数是-march=corei7-avx -mtune=corei7-avx -mavx.

但是在堆栈上分配局部变量时,我开始遇到分段错误错误。GCC 使用对齐的移动和VMOVAPS来回移动,这些指令需要 32 字节对齐。但是,Windows 64 位的堆栈只有 16 字节对齐。VMOVAPD__m256__m256d

如何将 GCC 的堆栈对齐更改为 32 字节?

我曾尝试使用-mstackrealign但无济于事,因为它仅与 16 个字节对齐。我也无法__attribute__((force_align_arg_pointer))工作,无论如何它都对齐到 16 个字节。我无法找到任何其他可以解决此问题的编译器选项。任何帮助是极大的赞赏。

编辑: 我尝试使用-mpreferred-stack-boundary=5,但 GCC 说此目标不支持 5 。我没主意了。

4

3 回答 3

17

我一直在探索这个问题,提交了一份 GCC 错误报告,发现这是一个与 MinGW64 相关的问题。请参阅GCC 错误#49001。显然,GCC 不支持 Windows 上的 32 字节堆栈对齐。这有效地防止了使用 256 位 AVX 指令。

我研究了几种方法来处理这个问题。最简单和最直接的解决方案是用未对齐的替代方案 VMOVUPS 等替换对齐的内存访问 VMOVAPS/PD/DQA。所以我昨晚学习了 Python(顺便说一句,这是一个非常好的工具)并使用以下脚本完成了这项工作GCC 生成的输入汇编文件:

import re
import fileinput
import sys

# fix aligned stack access
# replace aligned vmov* by unaligned vmov* with 32-byte aligned operands 
# see Intel's AVX programming guide, page 39
vmova = re.compile(r"\s*?vmov(\w+).*?((\(%r.*?%ymm)|(%ymm.*?\(%r))")
aligndict = {"aps" : "ups", "apd" : "upd", "dqa" : "dqu"};
for line in fileinput.FileInput(sys.argv[1:],inplace=1):
    m = vmova.match(line)
    if m and m.group(1) in aligndict:
        s = m.group(1)
        print line.replace("vmov"+s, "vmov"+aligndict[s]),
    else:
        print line,

这种方法非常安全且万无一失。尽管我在极少数情况下观察到了性能损失。当堆栈未对齐时,内存访问会跨越高速缓存行边界。幸运的是,代码的执行速度在大多数情况下与对齐访问一样快。我的建议:关键循环中的内联函数!

我还尝试使用另一个 Python 脚本修复每个函数序言中的堆栈分配,尝试始终将其对齐在 32 字节边界。这似乎适用于某些代码,但不适用于其他代码。我必须依靠 GCC 的善意,它会分配对齐的局部变量(相对于堆栈指针),它通常会这样做。情况并非总是如此,尤其是当由于需要在函数调用之前保存所有 ymm 寄存器而导致严重的寄存器溢出时。(所有 ymm 寄存器都是被调用者保存的)。如果有兴趣,我可以发布脚本。

最好的解决方案是修复 GCC MinGW64 构建。不幸的是,我不知道它的内部工作原理,上周才开始使用它。

于 2011-05-17T02:19:44.543 回答
1

你可以得到你想要的效果

  1. 声明变量不是变量,而是结构中的字段
  2. 通过适当的填充量声明比结构大的数组
  3. 进行指针/地址运算以在数组中找到一个 32 字节对齐的地址
  4. 将该地址转换为指向您的结构的指针
  5. 最后使用结构的数据成员

当 malloc() 没有正确对齐堆上的内容时,您可以使用相同的技术。

例如

void foo() {
    struct I_wish_these_were_32B_aligned {
          vec32B foo;
          char bar[32];
    }; // not - no variable definition, just the struct declaration.
    unsigned char a[sizeof(I_wish_these_were_32B_aligned) + 32)];
    unsigned char* a_aligned_to_32B = align_to_32B(a);
    I_wish_these_were_32B_aligned* s = (I_wish_these_were_32B_aligned)a_aligned_to_32B;
    s->foo = ...
}

在哪里

unsigned char* align_to_32B(unsiged char* a) {
     uint64_t u = (unit64_t)a;
     mask_aligned32B = (1 << 5) - 1;
     if (u & mask_aligned32B == 0) return (unsigned char*)u;
     return (unsigned char*)((u|mask_aligned_32B) + 1);
}
于 2012-04-26T06:20:59.317 回答
1

在我的函数中使用 AVX 时,我遇到了同样的分段错误问题。这也是由于堆栈未对齐。鉴于这是一个编译器问题(并且 Windows 中没有可能提供帮助的选项),我通过以下方式解决了堆栈使用问题:

  1. 使用静态变量(请参阅此问题)。鉴于它们没有存储在堆栈中,您可以通过__attribute__((align(32)))在声明中使用来强制它们对齐。例如:static __m256i r __attribute__((aligned(32)))

  2. 内联接收/返回 AVX 数据的函数/方法。您可以通过在函数原型/声明中添加inline和来强制 GCC 内联您的函数/方法。__attribute__((always_inline))内联函数会增加程序的大小,但它们也会阻止函数使用堆栈(因此避免了堆栈对齐问题)。示例:inline __m256i myAvxFunction(void) __attribute__((always_inline));

请注意,静态变量的使用不是线程安全的,如参考资料中所述。如果您正在编写一个多线程应用程序,您可能必须为您的关键路径添加一些保护。

于 2017-05-23T16:42:35.193 回答