问题 堆栈上的G ++ SSE内存对齐


我正在尝试使用Streaming SIMD Extensions重新编写光线跟踪器。我的原始光线跟踪器使用内联汇编和 MOVUPS 将数据加载到xmm寄存器的指令。我已经读过编译器内在函数并不比内联汇编慢得多(我怀疑我甚至可以通过避免未对齐的内存访问来获得速度),而且更加可移植,所以我试图迁移我的SSE代码以使用xmmintrin.h中的内在函数。受影响的主要类是vector,它看起来像这样:

#include "xmmintrin.h"
union vector {
    __m128 simd;
    float raw[4];
    //some constructors
    //a bunch of functions and operators
} __attribute__ ((aligned (16)));

我之前已经读过g ++编译器将自动地沿着内存边界分配结构,这些结构等于最大成员变量的大小,但是这似乎没有发生,并且对齐的属性没有帮助。我的研究表明,这可能是因为我在堆栈上分配了一大堆函数局部向量,并且在x86中无法保证堆栈上的对齐。有没有办法强制这种对齐?我应该提一下,这是在32位机器上的本机x86 Linux下运行,而不是Cygwin。我打算在此应用程序中进一步实现多线程,因此将违规的矢量实例声明为静态不是一种选择。如果需要,我愿意增加矢量数据结构的大小。


5899
2018-02-11 05:03


起源

如果您有最新版本的g ++支持 std::aligned_storage,您可以以便携式方式获得对齐存储,也可以在其他编译器上工作 - James McNellis
如果绕过联盟并使用直接__m128怎么办?这有什么改变吗? - Anycorn
完全绕过联盟会使访问个人成员更加痛苦。我有一个vectorpacket类,它可以从SSE中获得比标准向量更多的好处,但作为一个例子,我的基准测试表明我更快地连续添加单点产品的成员而不是重复地将寄存器拖曳到在我的SSE块中保留时添加。我不熟悉 std::aligned_storage;那说,我的机器有g ++ 4.4.3,但还有第二台机器我希望能够在锁定到3.4.6的情况下运行它。 - Octavianus
software.intel.com/en-us/forums/showthread.php?t=63876  注意他们将数组转换为m128类型。你可以类似地将m128转换为数组。 - Anycorn


答案:


最简单的方法是 std::aligned_storage,将对齐作为第二个参数。

如果您还没有,可能需要检查 Boost的版本

然后你可以建立你的联盟:

union vector {
  __m128 simd;
  std::aligned_storage<16, 16> alignment_only;
}

最后,如果它不起作用,您可以随时创建自己的小班:

template <typename Type, intptr_t Align> // Align must be a power of 2
class RawStorage
{
public:
  Type* operator->() {
    return reinterpret_cast<Type const*>(aligned());
  }

  Type const* operator->() const {
    return reinterpret_cast<Type const*>(aligned());
  }

  Type& operator*() { return *(operator->()); }
  Type const& operator*() const { return *(operator->()); }

private:
  unsigned char* aligned() {
    if (data & ~(Align-1) == data) { return data; }
    return (data + Align) & ~(Align-1);
  }

  unsigned char data[sizeof(Type) + Align - 1];
};

它会分配比必要的更多的存储空间,但这种方式可以保证对齐。

int main(int argc, char* argv[])
{
  RawStorage<__m128, 16> simd;
  *simd = /* ... */;

  return 0;
}

幸运的是,如果检测到对齐是正确的,编译器可能能够优化掉指针对齐的东西。


6
2018-02-11 08:33



这看起来很吸引人,但我在哪里可以找到std :: aligned_storage?谷歌暴力无益;它建议使用type_traits,但包括这个不允许我用它编译。对齐_存储是如此新,以至于g ++ 4.4.3不支持它? - Octavianus
@Octavianus:可能,我手头没有g ++ 4.4.3。也许你应该尝试使用 std::tr1::aligned_storage 相反(in <type_traits>) - Matthieu M.


几个星期前,我在大学时代重新编写了一个旧的光线追踪作业,将其更新为在64位Linux上运行并使用SIMD指令。 (旧版本偶然在DOS下运行486,让你知道我上次做什么用它)。

很可能有更好的方法,但这就是我做的......

typedef float    v4f_t __attribute__((vector_size (16)));

class Vector {
    ...
    union {
        v4f_t     simd;
        float     f[4];
    } __attribute__ ((aligned (16)));

    ...
};

拆解我编译的二进制文件表明它确实正在使用 MOVAPS 指令。

希望这可以帮助。


3
2018-02-11 19:33



__m128 typedef与v4f_t的typedef相同,所以这就是我所害怕的。 - Octavianus


我一直使用这个联合技巧 __m128 它适用于Mac上的GCC和Windows上的Visual C ++,因此这必须是您使用的编译器中的错误。

其他答案包含很好的解决方法。


1
2018-02-11 08:37



这不是编译器错误。许多机器在堆栈上默认为16字节对齐,但是一些旧机器不保证8字节以上的任何东西。 - Octavianus
__m128保证16字节对齐 - Frederik Slijkerman
是的,它确实。但是如果你把它粘在一个结构中,它似乎只能保证结构开头的16字节对齐。 - Octavianus


通常你需要的是:

union vector {
    __m128 simd;
    float raw[4];
};

即没有额外的 __attribute__ ((aligned (16))) 工会本身所需要的。

这在我使用过的几乎所有编译器上都有预期的效果,当时gcc 2.95.2就是明显的例外,在某些情况下用于搞乱堆栈对齐。


1
2018-02-11 08:40





如果需要N个这些对象的数组,请分配 vector raw[N+1],并使用 vector* const array = reinterpret_cast<vector*>(reinterpret_cast<intptr_t>(raw+1) & ~15) 作为数组的基址。这将始终保持一致。


0
2018-02-11 05:20



我的主要问题是声明这些对象的本地实例 - 我实际上并不需要它们的数组。 - Octavianus
@Octavianus:这当然可以用于N = 1,你只需要更高的开销。 - Ben Voigt