Pergunta

Preciso de uma ideia de como escrever uma implementação de plataforma cruzada C++ de alguns problemas paralelizáveis ​​de forma que eu possa aproveitar as vantagens do SIMD (SSE, SPU, etc), se disponível.Assim como quero poder, em tempo de execução, alternar entre SIMD e não SIMD.

Como você me sugeriria abordar esse problema?(Claro que não quero implementar o problema várias vezes para todas as opções possíveis)

Posso ver como isso pode não ser uma tarefa muito fácil com C++, mas acredito que estou faltando alguma coisa.Até agora minha ideia é assim...Uma classe cStream será um array de um único campo.Usando vários cStreams, posso obter SoA (Estrutura de Arrays).Então, usando alguns Functors, posso falsificar a função Lambda que preciso executar em todo o 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)));
}

Onde for_each será responsável por incrementar o ponteiro atual dos streams bem como embutir o corpo dos functores com SIMD e sem SIMD.

algo assim:

// 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>());
}

Observe que se o SIMD estiver habilitado é verificado uma vez e que o loop gira em torno do functor principal.

Foi útil?

Solução 2

Se alguém estiver interessado, este é o código sujo que venho para testar uma nova ideia que tive enquanto lia sobre a biblioteca que Paul postou.

Obrigado Paulo!

// 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;
}

Outras dicas

Você pode consultar a fonte da biblioteca MacSTL para obter algumas idéias nesta área: www.pixelglow.com/macstl/

Você pode querer dar uma olhada na minha tentativa de SIMD/não SIMD:

  • vrep, uma classe base modelada com especializações para SIMD (observe como ela distingue entre SSE somente flutuante e SSE2, que introduziu vetores inteiros).

  • Mais útil v4f, v4i classes etc (subclassificadas via intermediário v4).

É claro que é muito mais voltado para vetores de 4 elementos para RGB/xyz tipo de cálculo diferente do SoA, portanto, perderá completamente o fôlego quando o AVX de 8 vias for lançado, mas os princípios gerais podem ser úteis.

A abordagem mais impressionante para dimensionamento SIMD que já vi é a estrutura de rastreamento de raios RTFact: diapositivos, papel.Vale a pena dar uma olhada.Os pesquisadores estão intimamente associados à Intel (Saarbrucken agora hospeda o Intel Visual Computing Institute), então você pode ter certeza de que a expansão para AVX e Larrabee estava em suas mentes.

da Intel CT A biblioteca de modelos de "paralelismo de dados" também parece bastante promissora.

Observe que o exemplo dado decide o que executar em tempo de compilação (já que você está usando o pré-processador), neste caso você pode usar técnicas mais complexas para decidir o que realmente deseja executar;Por exemplo, envio de tags: http://cplusplus.co.il/2010/01/03/tag-dispatching/Seguindo o exemplo mostrado lá, você poderia ter a implementação rápida com SIMD e a lenta sem.

Você já pensou em usar soluções existentes como liboil?Ele implementa muitas operações SIMD comuns e pode decidir em tempo de execução se deseja usar código SIMD/não SIMD (usando ponteiros de função atribuídos por uma função de inicialização).

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top