Pregunta

Necesito un poco de idea de cómo escribir una aplicación C ++ multiplataforma de algunos problemas paralelizables de una manera para que pueda tomar ventaja de SIMD (SSE, SPU, etc) si está disponible. Tan bien como yo quiero ser capaz en tiempo de ejecución para cambiar entre SIMD y no SIMD.

¿Cómo sugieren que me acerco a este problema? (Por supuesto que no quiero poner en práctica el problema varias veces para todas las opciones posibles)

Puedo ver cómo esto podría no ser tarea muy fácil con C ++, pero creo que me falta algo. Hasta el momento mi idea se parece a esto ... A cStream clase será array de un solo campo. El uso de múltiples cStreams puedo lograr SoA (Estructura de matrices). Luego, utilizando un par de funtores puedo fingir función lambda que necesito para ser ejecutado en todo el 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)));
}

Cuando for_each será responsable de incrementar el puntero actual de las corrientes, así como inlining cuerpo de los funtores con SIMD y sin SIMD.

algo así:

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

Tenga en cuenta que si está habilitado el SIMD se comprueba una vez que el bucle es de alrededor del funtor principal.

¿Fue útil?

Solución 2

Si alguien está interesado este es el código sucia vengo con poner a prueba una nueva idea que vine con al leer acerca de la biblioteca que Pablo ha escrito.

Gracias Pablo!

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

Otros consejos

Es posible que desee ver en la fuente para la biblioteca MacSTL para algunas ideas en esta área: www.pixelglow .com / macstl /

Es posible que desee echar un vistazo a mi intento de SIMD / no SIMD:

  • VREP , una clase base con plantilla con especializaciones para SIMD (nota cómo se distingue entre los flotadores de sólo SSE, y SSE2, que introdujo número entero vectores.).

  • Más útil v4f , V4I etc clases (subclase vía intermedia v4 ).

Por supuesto que es mucho más orientado hacia vectores 4-elemento de RGBA / XYZ cálculos tipo que SOA, por lo que será completamente perdido fuelle cuando 8 vías AVX llega, pero los principios generales podría ser útil.

El enfoque más impresionante para SIMD-escalamiento que he visto es el RTFact de trazado de rayos marco: diapositivas , papel . Bien vale la pena un vistazo. Los investigadores están estrechamente asociados con Intel (Saarbrücken ahora alberga el Intel Visual Computing Institute) para que pueda estar seguro de escalamiento hacia adelante en AVX y Larrabee estaba en sus mentes.

Ct "paralelismo de datos de la biblioteca" se ve bastante prometedor plantilla demasiado .

Tenga en cuenta que el ejemplo dado decide lo que se ejecute en tiempo de compilación (ya que usted está usando el preprocesador), en este caso, puede utilizar las técnicas más complejas para decidir lo que realmente desea ejecutar; Por ejemplo, la etiqueta de Despacho: http://cplusplus.co.il/2010 / 01/03 / etiqueta de reexpedición / Siguiendo el ejemplo mostrado allí, usted podría tener la implementación rápida sea con SIMD, y el lento sin él.

¿Usted ha pensado en utilizar las soluciones existentes como liboil ? Implementa gran cantidad de operaciones SIMD común y pueden decidir en tiempo de ejecución si se debe utilizar SIMD no SIMD código / (el uso de punteros de función asignados por una función de inicialización).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top