Question

Je dois faire une idée comment écrire un C ++ croix mise en œuvre de la plate-forme de quelques problèmes parallélisables d'une manière que je peux profiter de SIMD (SSE, SPU, etc.) le cas échéant. Aussi bien que je veux être en mesure au moment de la course pour basculer entre SIMD et non SIMD.

Comment me suggérez-vous pour aborder ce problème? (Bien sûr, je ne veux pas mettre en œuvre le problème plusieurs fois pour toutes les options possibles)

Je peux voir comment cela pourrait ne pas être la tâche très facile avec C ++, mais je crois que je manque quelque chose. Jusqu'à présent, mon idée ressemble à ceci ... Une cstream de classe sera tableau d'un seul champ. En utilisant plusieurs cStreams je peux obtenir SoA (Structure des tableaux). Ensuite, en utilisant quelques Foncteurs je peux fausse fonction Lambda que je dois être exécuté sur l'ensemble 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)));
}

Où for_each sera responsable pour incrémenter le pointeur actuel des cours d'eau ainsi que inline corps de foncteurs avec SIMD et sans SIMD.

quelque chose comme ceci:

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

Notez que si le SIMD est activé est vérifiée une fois que la boucle est autour du foncteur principal.

Était-ce utile?

La solution 2

Si quelqu'un est intéressé c'est le code sale que je viens avec pour tester une nouvelle idée que je suis venu avec en lisant sur la bibliothèque que Paul a écrit.

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

Autres conseils

Vous pouvez regarder la source de la bibliothèque de MacSTL pour quelques idées dans ce domaine: www.pixelglow .com / macstl /

Vous pouvez jeter un coup d'oeil à ma tentative de SIMD / non SIMD:

  • vrep , une classe de base basé sur un modèle avec des spécialisations pour SIMD (noter la façon dont il établit une distinction entre les flotteurs uniquement SSE et SSE2, qui a introduit des vecteurs de nombres entiers.).

  • V4F , V4i etc classes (sous-classé par l'intermédiaire d'intermédiaire v4 ).

Bien sûr, il est beaucoup plus orienté vers des vecteurs 4 éléments pour rgba / les calculs de type de xyz que SoA, il en sera complètement à court de vapeur lorsque 8 voies AVX se présente, mais les principes généraux pourraient être utiles.

L'approche la plus impressionnante SIMD mise à l'échelle que j'ai vu est le cadre de ray-tracing RTFact: diapositives , papier . Il vaut bien un coup d'oeil. Les chercheurs sont étroitement associés à Intel (Saarbrucken accueille désormais l'Intel Visual Computing Institute) afin que vous puissiez être sûr mise à l'échelle vers l'avant sur AVX et Larrabee était sur leur esprit.

Ct bibliothèque de modèles "parallélisme de données" semble trop très prometteur .

Notez que l'exemple donné décide d'exécuter au moment de la compilation (puisque vous utilisez le préprocesseur), dans ce cas, vous pouvez utiliser des techniques plus complexes pour décider ce que vous voulez réellement exécuter; Par exemple, Tag expédition: http://cplusplus.co.il/2010 / 01/03 / tag-dispatching / Suivant l'exemple montré là, vous pourriez avoir la mise en œuvre rapide soit avec SIMD, et la lente sans.

Avez-vous pensé à utiliser des solutions existantes comme liboil ? Il met en œuvre beaucoup d'opérations SIMD commun et peuvent décider à l'exécution si vous souhaitez utiliser SIMD / non-SIMD Code (en utilisant des pointeurs de fonction affectés par une fonction d'initialisation).

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top