【问题标题】:SIMD or not SIMD - cross platformSIMD 与否 SIMD - 跨平台
【发布时间】:2011-01-08 12:25:30
【问题描述】:

我需要了解如何编写一些可并行化问题的 C++ 跨平台实现,以便在可用的情况下利用 SIMD(SSE、SPU 等)。以及我希望能够在运行时在 SIMD 和非 SIMD 之间切换。

您建议我如何解决这个问题? (当然我不想针对所有可能的选项多次实现这个问题)

我知道这对于 C++ 来说可能不是一件容易的事,但我相信我遗漏了一些东西。到目前为止,我的想法看起来像这样...... 类 cStream 将是单个字段的数组。使用多个 cStreams 我可以实现 SoA(数组结构)。然后使用一些 Functor,我可以伪造需要在整个 cStream 上执行的 Lambda 函数。

// just for example I'm not expecting this code to compile
cStream a; // something like float[1024]
cStream b;
cStream c;

void Foo()
{
    for_each(
        AssignSIMD(c, MulSIMD(AddSIMD(a, b), a)));
}

for_each 将负责增加流的当前指针,以及用 SIMD 和不使用 SIMD 内联函子的主体。

类似这样的:

// just for example I'm not expecting this code to compile
for_each(functor<T> f)
{
#ifdef USE_SIMD
    if (simdEnabled)
        real_for_each(f<true>()); // true means use SIMD
    else
#endif
        real_for_each(f<false>());
}

请注意,如果 SIMD 已启用,则检查一次并且循环围绕主函子。

【问题讨论】:

  • 查看 libsimdpp 库 - 它几乎可以满足您的要求。您只需要编写一次算法:可以使用不同的编译器选项(命名空间负责 ODR)多次编译相同的源代码,链接到相同的可执行文件,并且库将自动为目标处理器选择最佳实现。 (免责声明:我是作者)

标签: c++ metaprogramming functor simd


【解决方案1】:

请注意,给定的示例决定在编译时执行什么(因为您使用的是预处理器),在这种情况下,您可以使用更复杂的技术来决定您实际想要执行的内容;例如,标签调度:http://cplusplus.co.il/2010/01/03/tag-dispatching/ 按照那里显示的示例,您可以使用 SIMD 实现快速实现,而没有实现则慢。

【讨论】:

  • 有3个问题。 1. 在编译时做出决定 2. 需要多个实现 3. Desiosion 基于输入数据,我希望在 SIMD 和非 SIMD 中使用相同的数据
【解决方案2】:

您可能想查看 MacSTL 库的源代码以了解这方面的一些想法:www.pixelglow.com/macstl/

【讨论】:

  • MacSTL 中有很多模板。我需要时间来弄清楚它是如何实现的。但是在阅读它时,我想出了似乎可行的想法。如果其他人好奇,我会发布一些肮脏的代码作为新答案......
  • 我很想看看你想出的任何代码。对于这类问题,我已经有了部分解决方案,但不幸的是它是专有的(IP 属于我的雇主)。
【解决方案3】:

如果有人感兴趣,这是我用来测试我在阅读 Paul 发布的库时带来的新想法的脏代码。

谢谢保罗!

// This is just a conceptual test
// I haven't profile the code and I haven't verified if the result is correct
#include <xmmintrin.h>


// This class is doing all the math
template <bool SIMD>
class cStreamF32
{
private:
    void*       m_data;
    void*       m_dataEnd;
    __m128*     m_current128;
    float*      m_current32;

public:
    cStreamF32(int size)
    {
        if (SIMD)
            m_data = _mm_malloc(sizeof(float) * size, 16);
        else
            m_data = new float[size];
    }
    ~cStreamF32()
    {
        if (SIMD)
            _mm_free(m_data);
        else
            delete[] (float*)m_data;
    }

    inline void Begin()
    {
        if (SIMD)
            m_current128 = (__m128*)m_data;
        else
            m_current32 = (float*)m_data;
    }

    inline bool Next()
    {
        if (SIMD)
        {
            m_current128++;
            return m_current128 < m_dataEnd;
        }
        else
        {
            m_current32++;
            return m_current32 < m_dataEnd;
        }
    }

    inline void operator=(const __m128 x)
    {
        *m_current128 = x;
    }
    inline void operator=(const float x)
    {
        *m_current32 = x;
    }

    inline __m128 operator+(const cStreamF32<true>& x)
    {
        return _mm_add_ss(*m_current128, *x.m_current128);
    }
    inline float operator+(const cStreamF32<false>& x)
    {
        return *m_current32 + *x.m_current32;
    }

    inline __m128 operator+(const __m128 x)
    {
        return _mm_add_ss(*m_current128, x);
    }
    inline float operator+(const float x)
    {
        return *m_current32 + x;
    }

    inline __m128 operator*(const cStreamF32<true>& x)
    {
        return _mm_mul_ss(*m_current128, *x.m_current128);
    }
    inline float operator*(const cStreamF32<false>& x)
    {
        return *m_current32 * *x.m_current32;
    }

    inline __m128 operator*(const __m128 x)
    {
        return _mm_mul_ss(*m_current128, x);
    }
    inline float operator*(const float x)
    {
        return *m_current32 * x;
    }
};

// Executes both functors
template<class T1, class T2>
void Execute(T1& functor1, T2& functor2)
{
    functor1.Begin();
    do
    {
        functor1.Exec();
    }
    while (functor1.Next());

    functor2.Begin();
    do
    {
        functor2.Exec();
    }
    while (functor2.Next());
}

// This is the implementation of the problem
template <bool SIMD>
class cTestFunctor
{
private:
    cStreamF32<SIMD> a;
    cStreamF32<SIMD> b;
    cStreamF32<SIMD> c;

public:
    cTestFunctor() : a(1024), b(1024), c(1024) { }

    inline void Exec()
    {
        c = a + b * a;
    }

    inline void Begin()
    {
        a.Begin();
        b.Begin();
        c.Begin();
    }

    inline bool Next()
    {
        a.Next();
        b.Next();
        return c.Next();
    }
};


int main (int argc, char * const argv[]) 
{
    cTestFunctor<true> functor1;
    cTestFunctor<false> functor2;

    Execute(functor1, functor2);

    return 0;
}

【讨论】:

    【解决方案4】:

    您可能想看看我在 SIMD/非 SIMD 方面的尝试:

    • vrep,一个专门针对 SIMD 的模板化基类(请注意它如何区分仅浮点 SSE 和引入整数向量的 SSE2。)。

    • 更有用的v4fv4i 等类(通过中间v4 子类化)。

    当然,对于rgba/xyz 类型的计算,它比 SoA 更适合 4 元素向量,因此当 8 路 AVX 出现时将完全失去动力,但一般原则可能有用。

    【讨论】:

    • 这是一种有趣的方法,我确实需要 SoA。但也可以尝试做模板专业化。
    【解决方案5】:

    我见过的最令人印象深刻的 SIMD 缩放方法是 RTFact 光线追踪框架:slidespaper。很值得一看。研究人员与英特尔密切相关(萨尔布吕肯现在是英特尔视觉计算研究所的所在地),因此您可以确定向前扩展至 AVX 和 Larrabee 是他们的想法。

    英特尔的Ct“数据并行”模板库看起来也很有前途。

    【讨论】:

      【解决方案6】:

      您是否考虑过使用现有的解决方案,例如 liboil?它实现了lots of common SIMD operations,可以在运行时决定是否使用SIMD/非SIMD代码(使用初始化函数分配的函数指针)。

      【讨论】:

      • 我仍然需要检查这个库是如何使用函数指针在 SIMD/非 SIMD 之间切换的,但我看不到这些函数指针将如何内联。我注意到的另一个问题是所有算术函数都是用它们自己的循环实现的。 for(i=0;i
      猜你喜欢
      • 2014-05-15
      • 2016-05-03
      • 1970-01-01
      • 2015-10-23
      • 2015-12-17
      • 2012-10-09
      • 2012-02-21
      • 2018-09-25
      • 1970-01-01
      相关资源
      最近更新 更多