【问题标题】:G++ SSE memory alignment on the stack堆栈上的 G++ SSE 内存对齐
【发布时间】:2011-06-25 08:18:26
【问题描述】:

我正在尝试使用 Streaming SIMD Extensions 重新编写光线追踪器。我最初的光线追踪器使用内联汇编和 movups 指令将数据加载到 xmm 寄存器中。我已经读过编译器内在函数并不比内联汇编慢很多(我怀疑我什至可以通过避免未对齐的内存访问来提高速度),并且更便携,所以我正在尝试迁移我的 SSE 代码以使用 xmmintrin.h 中的内在函数.受影响的主要类是向量,它看起来像这样:

#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。我打算在这个应用程序中进一步实现多线程,因此将有问题的向量实例声明为静态不是一种选择。如果需要,我愿意增加矢量数据结构的大小。

【问题讨论】:

  • 如果您有支持 std::aligned_storage 的最新版本的 g++,您可以通过一种可移植的方式获得对齐存储,并且也可以在其他编译器上运行
  • 如果绕过并直接使用 __m128 会怎样?这有什么改变吗?
  • 完全绕过联合使得访问单个成员变得更加痛苦。我也有一个 vectorpacket 类,它从 SSE 中获得比标准向量更多的好处,但作为一个例子,我的基准测试表明,我可以更快地串行添加单个点积的成员,而不是反复改组寄存器到在我的 SSE 块中添加时添加。我不熟悉std::aligned_storage;也就是说,我的机器有 g++ 4.4.3,但是我希望能够在锁定到 3.4.6 的第二台机器上运行它。
  • software.intel.com/en-us/forums/showthread.php?t=63876 注意他们将数组转换为 m128 类型。您可以类似地将 m128 转换为数组。
  • @Anycorn:不,(float*)my_m128安全的。 Intel 向量类型的 may-alias-anything 属性只适用于一种方式,即如何使用char* 访问任何内容,但不能保证使用int* 访问char[] 是安全的。 (它在 MSVC 中是安全的,类似于 gcc -fno-strict-aliasing,但在其他编译器中,您应该使用联合或随机内部函数来访问向量的元素。)请参阅 print a __m128i variable 以获取示例。

标签: c++ assembly memory-management alignment sse


【解决方案1】:

几周前,我重写了我大学时代的旧光线追踪作业,将其更新为在 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 指令。

希望这会有所帮助。

【讨论】:

  • __m128 typedef 与 v4f_t 的 typedef 相同,所以这就是我所拥有的。
  • @Octavianus:实际的__m128 typedef 也包含may_alias 属性,因此可以安全地创建指向任意对象的__m128* 指针,甚至是非float 数据。 (即就像char* 可以别名到任何东西)。但是,是的,联合上的__attribute__((aligned(16))) 是多余的;它已经拥有来自v4f_t__m128 成员的信息。此外,GNU C 原生向量语法允许像v[3] 一样对__m128 v 进行索引,因此联合也是多余的。 (除了使用float* 访问它;严格混叠的东西不会双向。或者对于 MSVC 兼容。)
【解决方案2】:

通常你只需要:

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

即联合本身不需要额外的__attribute__ ((aligned (16)))

这在我曾经使用过的几乎所有编译器上都可以正常工作,除了当时的 gcc 2.95.2,它在某些情况下会破坏堆栈对齐。

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 这不是编译器错误。许多机器默认在堆栈上对齐 16 字节,但一些较旧的机器不保证任何高于 8 字节的内容。
    • __m128 保证 16 字节对齐
    • 是的,确实如此。但是如果你把它放在一个结构中,它似乎只能保证 16 字节与结构的开头对齐。
    • @Octavianus:那是编译器错误。 alignof(union) 必须 >= alignof() 其最对齐的成员。
    【解决方案4】:

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

    如果您还没有,您可能需要查看Boost's version

    然后你可以建立你的工会:

    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;
    }
    

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

    【讨论】:

    • 这看起来很吸引人,但我在哪里可以找到 std::aligned_storage?谷歌一直没有帮助;它建议 type_traits 但包括它不允许我用它编译。是aligned_storage 这么新,g++ 4.4.3 不支持吗?
    • @Octavianus:可能,我手头没有 g++4.4.3。也许您应该尝试改用std::tr1::aligned_storage(在&lt;type_traits&gt;
    【解决方案5】:

    如果您需要包含 N 个这些对象的数组,请分配 vector raw[N+1],并使用 vector* const array = reinterpret_cast&lt;vector*&gt;(reinterpret_cast&lt;intptr_t&gt;(raw+1) &amp; ~15) 作为数组的基地址。这将始终对齐。

    【讨论】:

    • 我的主要问题是声明这些对象的本地实例 - 我实际上并不需要它们的数组。
    • @Octavianus:这当然可以用于 N=1,只是开销更大。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-04-03
    • 2014-03-11
    • 2011-07-05
    • 1970-01-01
    • 2011-02-11
    • 1970-01-01
    • 2013-08-10
    相关资源
    最近更新 更多