【问题标题】:SSE and C++ containersSSE 和 C++ 容器
【发布时间】:2011-07-10 02:52:50
【问题描述】:

下面的代码段错误有明显的原因吗?

#include <vector>
#include <emmintrin.h>

struct point {
    __m128i v;

  point() {
    v = _mm_setr_epi32(0, 0, 0, 0);
  }
};

int main(int argc, char *argv[])
{
  std::vector<point> a(3);
}

谢谢

编辑:我在 linux/i686 上使用 g++ 4.5.0,我可能不知道我在这里做什么,但因为即使是以下段错误

int main(int argc, char *argv[])
{
  point *p = new point();
}

我真的认为这一定是和对齐问题。

【问题讨论】:

  • 如果v 没有正确对齐,那么明显可能出错的事情。但它是由vector 动态分配的,因此不会出现堆栈错位问题。尽管如此,我还是会在尝试分配之前打印出v 的地址,以确保。
  • 顺便说一句,当您看到段错误时,最好提及您正在使用的编译器、版本和平台。我会尝试重现您的错误,但在不知道具体细节的情况下我无法重现。
  • 32 位堆分配器的对齐方式不超过 8。您需要使用 vector 的分配器类型参数并使用可以在 16 处对齐的分配器。检查您的 CRT 是否为 1 或做饭过度分配你自己的。

标签: c++ sse memory-alignment allocator


【解决方案1】:

如果v 没有正确对齐,则很明显可能会出错。

但它是由vector 动态分配的,因此不会出现堆栈错位问题。

然而,正如phooji 正确指出的那样,“模板”或“原型”值被传递给std::vector 构造函数,该构造函数将被复制到向量的所有元素中。就是std::vector::vector这个参数会被放到栈上,可能会错位。

一些编译器有一个编译指示来控制函数内的堆栈对齐(基本上,编译器会浪费一些额外的空间来使所有局部变量正确对齐)。

根据 Microsoft 文档,Visual C++ 2010 should set up 8 byte stack alignment automatically for SSE typeshas done so since Visual C++ 2003

对于 gcc 我不知道。


在 C++0x 下,new point() 返回未对齐的存储是严重的违规行为。 [basic.stc.dynamic.allocation] 说(措辞来自草案 n3225):

分配函数尝试分配请求的存储量。如果成功的话,应该 返回存储块的起始地址,其字节长度至少应为 请求的大小。从 分配函数。连续调用分配的存储的顺序、连续性和初始值 分配函数未指定。返回的指针应适当对齐,以便可以转换 指向具有基本对齐要求(3.11)的任何完整对象类型的指针,然后使用 访问分配的存储中的对象或数组(直到通过调用显式释放存储 相应的释放函数)。

[basic.align] 说:

此外,动态存储的运行时分配请求 请求的对齐不能被接受应被视为分配失败。

你能试试更新版本的 gcc 吗?

【讨论】:

  • 感谢您的评论,确实 gcc 4.6 C++0x 支持 status 报告对齐支持 (N2341) 尚未实现。
  • 奇怪的是这是公认的答案,因为它是错误的......问题不在于堆栈对齐;这是堆对齐。他没有打电话给new point(当然可以); std::vector 类以一定大小调用 ::operator new,并且返回的内存块在 32 位 GCC 上仅对齐 8 字节(即使使用 GCC 7.3,我尝试过的最新版本)。
  • @Nemo:您没有排除堆栈对齐问题。在任何特定系统上,您可能需要同时检查两者,其中任何一个都可能出错。你说你在 i686 gcc 上测试了::operator new(),得到了 8 字节对齐,但是你传递了什么参数?
  • 我没有测试::operator new。我试图在__m128i 的向量上使用 push_back`。 (更准确地说:包含这种类型的单个成员的结构向量。)我在段错误处反汇编以找到与 8 字节对齐堆地址的对齐存储。这些类型在堆栈上的错位是极不可能的;这将是一个严重的编译器错误,不要介意这些类型是在 SSE 寄存器中传递的。这个问题只是 malloc/new 在 32 位和 64 位 Linux 平台上具有不同的对齐保证,而 GCC 使用对齐的加载/存储来移动这些类型。
  • @Nemo:堆上的错位是一个严重的工具链错误——我引用了适用的标准规则。所以你不能断定其中一个或另一个更有可能基于此。
【解决方案2】:

您使用的vector 构造函数实际上是这样定义的:

explicit vector ( size_type n, const T& value= T(), const Allocator& = Allocator() );

(参见例如http://www.cplusplus.com/reference/stl/vector/vector/)。

换句话说,一个元素是默认构造的(即,您调用构造函数时的默认参数值),然后通过复制第一个元素来创建其余元素。我的猜测是您需要一个 point 的复制构造函数,它可以正确处理 __m128i 值的(非)复制。

更新:当我尝试使用 Visual Studio 2010 (v. 10.0.30319.1) 构建您的代码时,我收到以下构建错误:

error C2719: '_Val': formal parameter with __declspec(align('16')) won't be aligned c:\program files\microsoft visual studio 10.0\vc\include\vector 870 1   meh

这表明 Ben 在这方面是正确的,因为这是一个对齐问题。

【讨论】:

  • __m128i 应该是 blittable,我认为这不是问题所在。即使不是,point 的自动生成的复制构造函数也会调用 __m128i 的正确复制构造函数。
  • @Ben:看起来你在金钱上可能是对的:对齐。更新中。
  • @Ben Voigt:可能——该错误指向向量的resize 重载之一。
  • @Ben:这是因为标准库中定义的new 运算符没有考虑对齐指令,如@phooji 的VS2010 错误消息所示。
  • @rwong:标准要求分配函数返回对齐的内存。
【解决方案3】:

要求 SSE 内在函数在内存中是 16 字节对齐的。当您在堆栈上分配__m128 时,没有问题,因为编译器会自动正确对齐它们。 std::vector&lt;&gt; 的默认分配器处理动态内存分配,不会产生对齐的分配。

【讨论】:

    【解决方案4】:

    编译器的 STL 实现中的默认分配器分配的内存可能未对齐。这将取决于特定的平台和编译器供应商。

    通常默认分配器使用运算符new,它通常不保证超出字长(32 位或64 位)的对齐。为了解决这个问题,可能需要实现一个使用_aligned_malloc的自定义分配器。

    另外,一个简单的解决方法(虽然不是一个令人满意的解决方法)是将值分配给本地 __m128i 变量,然后使用未对齐指令将该变量复制到结构中。示例:

    struct point {
        __m128i v;
        point() {
            __m128i temp = _mm_setr_epi32(0, 0, 0, 0);
            _mm_storeu_si128(&v, temp);
        }
    };
    

    【讨论】:

    • 我已经测试了您提出的修复,它产生的编译器错误与我的 MS VS 版本的原始代码相同。
    • 这确实不会崩溃。谢谢
    • 不幸的是,您永远无法使用 v 进行 SSE2 操作。但我不认为矢量元素是未对齐的。
    • 您需要一个访问器函数来对v 执行loadu。正如您所说,这不是一个令人满意的解决方案,因为对齐存储更便宜。
    猜你喜欢
    • 1970-01-01
    • 2012-12-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多