2010-01-23 21 views
11

Necesito alguna idea sobre cómo escribir una implementación multiplataforma C++ de algunos problemas paralelizables de forma que pueda aprovechar SIMD (SSE, SPU, etc.) si está disponible. Además, quiero poder en el tiempo de ejecución cambiar entre SIMD y no SIMD.SIMD o no SIMD - multiplataforma

¿Cómo me sugerirías para abordar este problema? (Por supuesto que no quiero implementar el problema varias veces para todas las opciones posibles)

Veo que esta tarea puede no ser muy fácil con C++, pero creo que me falta algo. Hasta ahora mi idea se parece a esto ... Una clase cStream será una matriz de un solo campo. Usando múltiples cStreams puedo lograr SoA (Estructura de matrices). Luego, usando algunos Funtores puedo falsificar la función Lambda que necesito que se ejecute 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))); 
} 

Dónde 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 el SIMD está activado se comprueba una vez que el bucle es de alrededor del funtor principal.

+2

Consulte la biblioteca [libsimdpp] (https://github.com/p12tic/libsimdpp): prácticamente hace lo que usted solicita. Solo necesita escribir sus algoritmos una vez: el mismo código fuente se puede compilar varias veces con diferentes opciones de compilación (los espacios de nombres se ocupan de ODR), vinculados en el mismo ejecutable y la biblioteca seleccionará automáticamente la mejor implementación para el procesador de destino. (descargo de responsabilidad: soy el autor) – user12

Respuesta

2

Si alguien está interesado este es el código sucia vengo con poner a prueba una nueva idea que llegué con tiempo leyendo sobre la biblioteca que Paul publicó.

Gracias 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; 
} 
1

Observe que el ejemplo dado decide qué ejecutar en tiempo de compilación (ya que está usando el preprocesador), en este caso puede usar técnicas más complejas para decidir qué desea ejecutar; Por ejemplo, Despacho de etiqueta: http://cplusplus.co.il/2010/01/03/tag-dispatching/ Siguiendo el ejemplo que se muestra allí, puede hacer que la implementación rápida sea con SIMD, y la lenta sin.

+0

Hay 3 problemas. 1. Toma la decisión durante el tiempo de compilación 2. Requiere implementaciones múltiples 3. Desiosion se basa en los datos de entrada, donde quiero que los mismos datos se utilicen en SIMD y no SIMD – Aleks

2

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

+1

Hay muchas plantillas en MacSTL. Necesitaré tiempo para descubrir cómo se implementa. Pero al leer sobre esto, se me ocurre una idea que parece funcionar. Voy a publicar un código sucio como una nueva respuesta si alguien más tiene curiosidad ... – Aleks

+0

Me interesaría ver cualquier código que se te ocurra. Ya tengo una solución parcial para este tipo de problema, pero desafortunadamente es patentada (el IP pertenece a mi empleador). –

2

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

  • vrep, una clase base con plantilla con especializaciones para SIMD (tenga en cuenta la forma en que distingue entre los flotadores de sólo SSE y SSE2, que introdujeron vectores enteros).

  • Más útil v4f, v4i etc clases (subclasificado mediante intermedio v4).

Por supuesto que es mucho más orientado hacia los vectores de 4 elementos para 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ían ser útiles.

+0

Es un enfoque interesante Realmente necesito SoA en mi caso. Pero podría tratar de hacer especialización de plantillas también. – Aleks

2

El enfoque más impresionante para SIMD-escala que he visto es el marco RTFact ray-tracing: slides, paper. Vale la pena echarle un vistazo.Los investigadores están estrechamente asociados con Intel (Saarbrucken ahora alberga el Intel Visual Computing Institute) por lo que puede estar seguro de que se ampliará el AVX y Larrabee estaba en sus mentes.

La biblioteca de plantillas de "paralelismo de datos" de Intel Ct parece bastante prometedora.

0

¿Ha pensado en usar soluciones existentes como liboil? Implementa lots of common SIMD operations y puede decidir en tiempo de ejecución si se usa el código SIMD/no SIMD (usando los punteros de función asignados por una función de inicialización).

+0

Todavía necesito comprobar cómo estas bibliotecas utilizan punteros de función para cambiar entre SIMD/no SIMD, pero no veo cómo se insertarán estos punteros de función. El otro problema que noté es que todas las funciones aritméticas se implementan con su propio bucle. for (i = 0; i <(n Y (~ 0x3)); i + = 4) {} ... para (; i Aleks

Cuestiones relacionadas