SIMD или не SIMD — кроссплатформенность

Мне нужна идея, как написать кросс-платформенную реализацию нескольких параллелизуемых задач на С++ таким образом, чтобы я мог использовать преимущества SIMD (SSE, SPU и т. д.), если они доступны. А также я хочу иметь возможность во время выполнения переключаться между SIMD и не SIMD.

Как бы вы предложили мне решить эту проблему? (Конечно, я не хочу решать проблему несколько раз для всех возможных вариантов)

Я понимаю, что это может быть не очень простой задачей с C++, но я считаю, что что-то упускаю. Пока моя идея выглядит так... Класс cStream будет массивом из одного поля. Используя несколько cStreams, я могу достичь SoA (структуры массивов). Затем, используя несколько функторов, я могу подделать лямбда-функцию, которую мне нужно выполнить для всего cStream.

// 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 включен, он проверяется один раз и что цикл находится вокруг основного функтора.


person Aleks    schedule 23.01.2010    source источник
comment
Проверьте библиотеку libsimdpp — она в значительной степени делает то, что вы просите. Вам нужно написать свои алгоритмы только один раз: один и тот же исходный код можно скомпилировать несколько раз с разными параметрами компилятора (пространства имен заботятся об ODR), связать с одним и тем же исполняемым файлом, и библиотека автоматически выберет наилучшую реализацию для целевого процессора. (отказ от ответственности: я автор)   -  person user12    schedule 31.10.2013


Ответы (6)


Вы можете посмотреть исходный код библиотеки MacSTL для некоторых идей в этой области: www.pixelglow.com/ macstl/

person Paul R    schedule 23.01.2010
comment
В MacSTL есть много шаблонов. Мне нужно время, чтобы понять, как это реализовано. Но, читая об этом, я придумал идею, которая, кажется, работает. Я опубликую какой-нибудь грязный код в качестве нового ответа, если кому-то еще интересно... - person Aleks; 23.01.2010
comment
Мне было бы интересно увидеть любой код, который вы придумали. У меня уже есть частичное решение для такого рода проблем, но, к сожалению, оно проприетарное (IP принадлежит моему работодателю). - person Paul R; 23.01.2010

Если кому-то интересно, это грязный код, с которым я пришел, чтобы проверить новую идею, с которой я пришел, читая о библиотеке, которую опубликовал Пол.

Спасибо, Пол!

// 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;
}
person Aleks    schedule 23.01.2010

Возможно, вы захотите взглянуть на мою попытку SIMD/non-SIMD:

  • vrep, шаблонный базовый класс со специализацией для SIMD (обратите внимание, как он различает SSE только с плавающей запятой и SSE2, который представил целочисленные векторы).

  • Более полезный v4f, v4i и т. д. ( подклассы через промежуточную v4 ).

Конечно, он гораздо больше ориентирован на 4-элементные векторы для rgba/xyz, чем SoA, поэтому полностью выдохнутся, когда появится 8-way AVX, но общие принципы могут быть полезны.

person timday    schedule 23.01.2010
comment
Это интересный подход, мне действительно нужен SoA в моем случае. Но можно было бы попытаться сделать специализацию шаблона. - person Aleks; 23.01.2010

Самый впечатляющий подход к SIMD-масштабированию, который я видел, — это инфраструктура трассировки лучей RTFact: слайды, документ< /а>. Стоит посмотреть. Исследователи тесно связаны с Intel (сейчас в Саарбрюкене находится Институт визуальных вычислений Intel), поэтому вы можете быть уверены, что они думали о перспективном масштабировании на AVX и Larrabee.

Библиотека шаблонов Intel Ct "параллелизм данных" также выглядит многообещающе.

person timday    schedule 23.01.2010

Обратите внимание, что данный пример решает, что выполнять во время компиляции (поскольку вы используете препроцессор), в этом случае вы можете использовать более сложные методы, чтобы решить, что вы действительно хотите выполнить; Например, отправка тегов: http://cplusplus.co.il/2010/01/03/tag-dispatching/ Следуя показанному здесь примеру, вы можете иметь быструю реализацию с SIMD, а медленную — без.

person rmn    schedule 23.01.2010
comment
Есть 3 проблемы. 1. Принимает решение во время компиляции 2. Требуется несколько реализаций 3. Решение основано на входных данных, где я хочу, чтобы одни и те же данные использовались в SIMD и не SIMD - person Aleks; 23.01.2010

Думали ли вы об использовании существующих решений, таких как liboil? Он реализует множество стандартных SIMD-операций и может решать во время выполнения, использовать ли код SIMD или не-SIMD. (используя указатели функций, назначенные функцией инициализации).

person AndiDog    schedule 23.01.2010
comment
Мне все еще нужно проверить, как эта библиотека использует указатели функций для переключения между SIMD/не-SIMD, но я не вижу, как эти указатели функций будут встроены. Другая проблема, которую я заметил, заключается в том, что все арифметические функции реализованы в собственном цикле. for(i=0;i‹(n&(~0x3));i+=4){ ... } for(;i‹n;i++){ ... } И для чего-то вроде A + B + C нужно дважды пройтись по всем элементам - person Aleks; 23.01.2010