对类使用 C 内在函数和内存对齐困难

Posted

技术标签:

【中文标题】对类使用 C 内在函数和内存对齐困难【英文标题】:Using C intrinsics and memory alignment difficulties with classes 【发布时间】:2009-10-28 19:30:28 【问题描述】:

好的,所以我刚刚开始在我的代码中使用 C 内在函数,并且我创建了一个类,它的简化如下所示:

class _Vector3D

public:
_Vector3D() 

    aVals[0] = _mm_setzero_ps();
    aVals[1] = _mm_setzero_ps();
    aVals[2] = _mm_setzero_ps();

~_Vector3D() 
private:
__m128 aVals[3];
;

到目前为止一切顺利。但是当我用 _Vector3D 成员创建第二个类时,我遇到了问题:

class RayPacket

public:
RayPacket() orig = _Vector3D(); dir = _Vector3D(); power = _mm_setzero_ps();
~RayPacket() 

RayPacket(_Vector3D origins, _Vector3D directions, float pow)

    orig = origins;
    dir = directions;
    power = _mm_set_ps1(pow);


_Vector3D orig;
_Vector3D dir;
__m128 power;
;

我收到以下错误:

错误 C2719: 'origins': 带有 __declspec(align('16')) 的形参不会对齐

指向构造函数重载:

RayPacket(_Vector3D origins, _Vector3D directions, float pow)

那么我是不是走错了路?我应该改用结构还是让它与类一起使用?

【问题讨论】:

你为什么突然从 __m128 切换到浮动? 您的标识符有问题。标识符以下划线开头,后跟大写字母,包含两个连续下划线的标识符由实现保留。使用它们会导致问题。 请注意 - 您似乎对 SSE 有一点误解。你打电话给_Vector3D 应该是一个3x4 矩阵。每个 __m128 是一组 4 个浮点数。 谢谢 Martin,我正在更改标识符... 首先,它是 3 个 4D 向量的集合。其次,当它实际上是向量的集合(又名矩阵)时,我认为将其命名为 Vector3D 是不合理的。 【参考方案1】:

此答案基于文档和猜测,而不是实际知识。当心!

documentation for __m128 说:

_m128 [原文如此] 类型的变量自动在 16 字节边界上对齐。

因此,通过在您的类中使用 __m128 成员,这会强制编译器在 16 字节边界上对齐您的类的实例。隐式地将__declspec(align(16)) 添加到您的类中,但这是not allowed on function parameters,因为编译器很难(不可能?)在堆栈帧内强制对齐。

作为一种解决方法,尝试通过引用传递构造函数参数:

RayPacket(_Vector3D const &origins, _Vector3D const &directions, float pow)

【讨论】:

【参考方案2】:

我认为问题在于编译器无法保证在堆栈上创建_Vector3D 对象以传递给构造函数时堆栈指针将正确对齐。

在 32 位系统上,堆栈指针通常保证是 4 字节对齐的(有时是 8 字节对齐的),而在 64 位系统上,我认为堆栈指针通常完全保证是 8 字节对齐的,所以编译器不知道何时调用堆栈将正确对齐的构造函数。您可能需要传递指针或引用。

请注意malloc() 和朋友们,为块返回的保证对齐有时不能保证能够处理这样的特殊类型。在这种情况下,平台将具有特殊的分配功能来分配这些对象。

有关 MSVC (http://msdn.microsoft.com/en-us/library/aa290049.aspx) 的详细信息,请参阅以下内容:

堆栈对齐

在两个 64 位平台上,每个堆栈帧的顶部都是 16 字节对齐的。虽然这使用的空间比需要的多,但它保证了编译器可以以所有元素对齐的方式将所有数据放在堆栈上。

x86 编译器使用不同的方法来对齐堆栈。默认情况下,堆栈是 4 字节对齐的。虽然这样节省空间,但您可以看到有些数据类型需要 8 字节对齐,而且为了获得良好的性能,有时需要 16 字节对齐。在某些情况下,编译器可以确定动态 8 字节堆栈对齐是有益的——尤其是当堆栈上有双精度值时。

编译器有两种方法。首先,当用户在编译和链接时指定时,编译器可以使用链接时代码生成 (LTCG) 来生成完整程序的调用树。有了这个,它可以确定调用树中 8 字节堆栈对齐将是有益的区域,并确定动态堆栈对齐获得最佳回报的调用站点。当函数在堆栈上具有双精度值时使用第二种方法,但由于某种原因尚未对齐 8 字节。编译器应用启发式(随着编译器的每次迭代而改进)来确定函数是否应该动态地 8 字节对齐。

注意动态 8 字节堆栈对齐在性能方面的一个缺点是帧指针省略 (/Oy) 有效地被关闭。寄存器 EBP 必须用于引用动态 8 字节堆栈的堆栈,因此不能作为函数中的通用寄存器使用。

上面链接的文章还包含一些关于特殊堆函数的信息,如果您需要,这些函数可提供高于标准 malloc() 的对齐保证。

【讨论】:

【参考方案3】:

尝试通过 const 引用传递 _Vector3D,如下所示:

RayPacket( const _Vector3D& origins, const _Vector3D& directions, float pow );
这会将指针而不是值放在调用堆栈上。

【讨论】:

以上是关于对类使用 C 内在函数和内存对齐困难的主要内容,如果未能解决你的问题,请参考以下文章

内存对齐:C/C++编程中的重要性和技巧

C++ 类内存模型和对齐

C/C++获得对齐的内存的跨平台解决方案

C/C++获得对齐的内存的跨平台解决方案

C++11 中的动态对齐内存分配

4096个字节是4K对齐了吗?