我有一个用 C++ 编写的循环,它针对大整数数组的每个元素执行。在循环内,我屏蔽整数的一些位,然后找到最小值和最大值。我听说,如果我使用 SSE 指令进行这些操作,与使用按位 AND 和 if-else 条件编写的普通循环相比,它的运行速度会快得多。我的问题是我应该遵循这些 SSE 说明吗?另外,如果我的代码在不同的处理器上运行会发生什么?它仍然有效吗?或者这些指令是特定于处理器的?

有帮助吗?

解决方案

  1. SSE 指令是特定于处理器的。您可以在维基百科上查找哪个处理器支持哪个SSE版本。
  2. SSE 代码是否会更快取决于许多因素:首先当然是问题是内存限制还是CPU限制。如果内存总线是瓶颈,SSE 就没有多大帮助。尝试简化整数计算,如果这会使代码更快,那么它可能受 CPU 限制,并且您很有可能加快速度。
  3. 请注意,编写 SIMD 代码比编​​写 C++ 代码困难得多,而且生成的代码更难更改。始终保持 C++ 代码为最新,您需要将其作为注释并检查汇编代码的正确性。
  4. 考虑使用像 IPP 这样的库,它实现针对各种处理器优化的常见低级 SIMD 操作。

其他提示

SIMD,其中SSE是一个例子,允许你做对多个数据块的相同的操作。所以,你不会得到任何优势,使用SSE作为直更换为整数操作,你只会得到好处,如果你不能同时做多个数据项的操作。这涉及加载是在存储器中连续一些数据值,在做必要的处理,然后步进到阵列中的下一个值的集合。

问题:

1如果代码路径是依赖于正被处理的数据,SIMD变得更难实现。例如:

a = array [index];
a &= mask;
a >>= shift;
if (a < somevalue)
{
  a += 2;
  array [index] = a;
}
++index;

是不容易做到,因为SIMD:

a1 = array [index] a2 = array [index+1] a3 = array [index+2] a4 = array [index+3]
a1 &= mask         a2 &= mask           a3 &= mask           a4 &= mask
a1 >>= shift       a2 >>= shift         a3 >>= shift         a4 >>= shift
if (a1<somevalue)  if (a2<somevalue)    if (a3<somevalue)    if (a4<somevalue)
  // help! can't conditionally perform this on each column, all columns must do the same thing
index += 4

2如果数据不是contigous然后将数据加载到SIMD指令是麻烦的

3的代码是特定的处理器。 SSE是仅在IA32(英特尔/ AMD),而不是所有IA32 CPU支持SSE。

您需要分析的算法和数据,看它是否可以SSE'd并且需要知道SSE是如何工作的。有大量的文档在英特尔的网站。

这种问题是其中一个良好的低电平分析器是必不可少一个很好的例子。 (喜欢的东西VTune™可视化),它可以给你的在您的热点躺在一个更明智的想法。

我的猜测,从你的描述是,你的热点将可能是使用的if / else从最小值/最大值计算得到的分支预测失败。因此,使用SIMD内部函数应允许您使用最小/最大说明,但是,它可能是值得只是想用一个网点最小/最大caluculation代替。这可能实现的大部分涨幅痛苦少。

像这样:

inline int 
minimum(int a, int b)
{
  int mask = (a - b) >> 31;
  return ((a & mask) | (b & ~mask));
}

如果您使用SSE指令,你显然仅限于支持这些处理器。 这意味着86,其历史可以追溯到奔腾2左右(不记得确切他们被介绍时,但它是一个很久以前)

SSE2,其中,据我记忆所及,是一个提供整数运算,是较为近期(奔3?虽然首款AMD Athlon处理器不支持他们)

在任何情况下,你必须使用这些指令中的两个选项。无论是写在汇编代码整个块(可能是一个坏主意。这使得它几乎是不可能的编译器优化你的代码,这是非常难的人写出高效的汇编程序)。

可替换地,使用可用的内在与您的编译器(如果没有记错,他们通常在xmmintrin.h定义)

但同样,性能可能不会提高。 SSE代码构成它处理数据的附加要求。主要是,一要牢记的是,数据必须在128位边界对齐。还应该有很少或加载到相同的寄存器中的值之间没有依赖关系(128位SSE寄存器可以容纳4个整数。将第一和第二个一起不是最佳的。但是,增加所有四个整数到相应的4-整数中另一个寄存器将是快速)

这可能是很有诱惑力使用该包装所有低级别的SSE摆弄出库,但也可能毁了任何潜在的性能优势。

我不知道好上证所的整数运算的支持是怎么了,这样也可以是能限制性能的因素。 SSE主要针对的是加快浮点运算。

如果您打算使用Microsoft Visual C ++,你应该阅读:

http://www.codeproject.com/KB/recipes/sseintro.aspx

我们已经在 SSE 中实现了一些图像处理代码,与您所描述的类似,但在字节数组上。与 C 代码相比,加速效果相当可观,具体取决于超过 4 倍的精确算法,即使对于英特尔编译器也是如此。但是,正如您已经提到的,您有以下缺点:

  • 可移植性。该代码可以在所有类似 Intel 的 CPU 上运行,AMD 也可以运行,但不能在其他 CPU 上运行。这对我们来说不是问题,因为我们控制目标硬件。切换编译器甚至切换到 64 位操作系统也可能是一个问题。

  • 你的学习曲线很陡峭,但我发现在掌握原理后编写新算法并不难。

  • 可维护性。大多数 C 或 C++ 程序员不了解汇编/SSE。

我给你的建议是,只有当你确实需要性能改进,并且你无法在像英特尔IPP这样的库中找到解决你的问题的函数,并且如果你可以忍受可移植性问题时,才使用它。

我可以从我的遭遇告诉SSE带来了超过代码的一个普通的C版本一个巨大的(4倍以上)的加速比(无内联汇编,使用没有内在),但手工优化汇编程序可以击败编译器生成的组件,如果编译器无法弄清楚什么打算程序员(我相信的,编译器不会覆盖所有可能的代码组合,他们永远不会)。 哦,并且编译器不能每次布局中的数据,它运行在最快的可能速度。 但你需要一个加速过英特尔编译器多少的遭遇(如果可能)。

SSE指令最初只是英特尔的芯片,但最近(自速龙?)AMD支持它们为好,所以如果你对SSE指令集做的代码,你应该可以移植到大多数x86特效。

话虽这么说,它可能不值得你花时间学习SSE编码,除非你已经很熟悉汇编程序在x86上的 - 一个更容易的选择可能是检查你的编译器文档,看看是否有选项让编译器自动生成SSE代码为您服务。一些编译器做的非常好矢量化的循环在这种方式。 (你可能不会感到惊讶,英特尔编译器做了一个很好的工作:)

写一段代码,可以帮助编译器知道你在做什么。 GCC将理解和优化SSE代码如这样:

typedef union Vector4f
{
        // Easy constructor, defaulted to black/0 vector
    Vector4f(float a = 0, float b = 0, float c = 0, float d = 1.0f):
        X(a), Y(b), Z(c), W(d) { }

        // Cast operator, for []
    inline operator float* ()
    { 
        return (float*)this;
    }

        // Const ast operator, for const []
    inline operator const float* () const
    { 
        return (const float*)this;
    }

    // ---------------------------------------- //

    inline Vector4f operator += (const Vector4f &v)
    {
        for(int i=0; i<4; ++i)
            (*this)[i] += v[i];

        return *this;
    }

    inline Vector4f operator += (float t)
    {
        for(int i=0; i<4; ++i)
            (*this)[i] += t;

        return *this;
    }

        // Vertex / Vector 
        // Lower case xyzw components
    struct {
        float x, y, z;
        float w;
    };

        // Upper case XYZW components
    struct {
        float X, Y, Z;
        float W;
    };
};

只是不要忘了对你的编译参数-msse -msse2!

虽然这是事实,SSE是特定于某些处理器(SSE可能是相对安全的,SSE2以我的经验要少得多),您可以检测在运行时的CPU,并动态加载代码根据目标CPU上。

SIMD内在(如SSE2)可以加速这种事了,但采取专门知识正确使用。他们是对齐和流水线延时非常敏感;粗心的使用可以使性能甚至不如它本来没有他们。你会从简单地使用缓存预取,以确保所有的整数都在L1的时间让你对他们的工作得到了更简单,更直接的加速。

除非你的函数需要一个吞吐量每秒更好的1亿个整数,SIMD可能是不值得的麻烦你。

只是为了简单地增加什么不同SSE版本是可用在不同的CPU之前已经说了:这可以检查通过查看CPUID指令(例如参见英特尔的使用说明)返回各自的功能标志

有一个在嵌入式汇编为C / C ++,这里是一个 DDJ文章。除非你是100%肯定你的程序将在兼容的平台上运行,你应该遵循很多人在这里给出的建议。

我同意与先前的海报。好处是可以非常大,但得到它可能需要大量的工作。这些说明Intel的文档超过4K页。你可能想从Ocali公司签出EasySSE(C ++包装库超过内在+实施例)自由。

我想我这个EasySSE隶属关系是明确的。

我不建议这样做你自己,除非你是用汇编相当熟练。利用上证所,更可能,需要您的数据进行仔细整理,如 Skizz 点出,和效益常常是值得怀疑的最好的。

有可能会是更好的为你写的非常小的循环,并保持你的数据非常严密组织,只是依靠编译器做这个给你。无论是英特尔C编译器和GCC(自4.1)可以自动向量化你的代码,并可能会做一个更好的工作比你。 (只是-ftree-矢量化添加到您的CXXFLAGS。)

修改:我要提到的另一件事是,有几个编译器支持的组装内在的,它可能会,国际海事组织,更容易比ASM()或__asm {使用}语法。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top