سؤال

أحتاج إلى فكرة عن كيفية كتابة تنفيذ منصة C ++ عبر بعض المشكلات القابلة للتوازي بطريقة ما حتى أتمكن من الاستفادة من SIMD (SSE ، SPU ، إلخ) إذا كان ذلك متاحًا. بالإضافة إلى أنني أريد أن أكون قادرًا في وقت التشغيل للتبديل بين SIMD وليس SIMD.

كيف تقترح علي أن أتعامل مع هذه المشكلة؟(بالطبع لا أريد تنفيذ المشكلة عدة مرات لجميع الخيارات الممكنة)

أستطيع أن أرى كيف قد لا تكون هذه مهمة سهلة للغاية مع C ++ ، لكنني أعتقد أنني أفتقد شيئًا ما. حتى الآن تبدو فكرتي هكذا ... سيكون الفصل الدراسي مجموعة من حقل واحد. باستخدام Cstreams متعددة يمكنني تحقيق الخدمية (هيكل المصفوفات). ثم باستخدام عدد قليل من المسلسلات ، يمكنني مزيفة من وظيفة lambda التي يجب تنفيذها على 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 مسؤولاً عن زيادة المؤشر الحالي للتيارات بالإضافة إلى تحديد جسم Functors مع 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 مرة واحدة ، وأن الحلقة تكون حول المسلح الرئيسي.

هل كانت مفيدة؟

المحلول 2

إذا كان شخص ما مهتمًا ، فهذا هو الرمز القذر الذي جئت إليه لاختبار فكرة جديدة أتيت معها أثناء قراءة المكتبة التي نشرها بول.

شكرا بول!

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

نصائح أخرى

قد ترغب في إلقاء نظرة على مصدر مكتبة MacStl لبعض الأفكار في هذا المجال: www.pixelglow.com/macstl/

قد ترغب في إلقاء نظرة على محاولتي في SIMD/NON-SIMD:

  • vrep, ، فئة قاعدة تمثل مع تخصصات لـ SIMD (لاحظ كيف تميز بين عوامات SSE فقط ، و SSE2 ، والتي أدخلت ناقلات عدد صحيح.).

  • أكثر فائدة v4f, v4i فصول الخ (فئات فرعية عبر الوسيطة v4).

بالطبع هو أكثر توجها نحو ناقلات 4 عناصر ل RGBA/XYZ اكتب الحسابات من الخدمية ، لذلك سوف ينفد تمامًا من البخار عندما يأتي AVX 8 اتجاه ، لكن المبادئ العامة قد تكون مفيدة.

النهج الأكثر إثارة للإعجاب في تقسيم SIMD الذي رأيته هو إطار تتبع RTFACT Ray: الشرائح, ورق. تستحق نظرة. يرتبط الباحثون ارتباطًا وثيقًا بـ Intel (يستضيف Saarbrucken الآن معهد Intel Visual Computing) حتى تتمكن من التأكد من التحجيم إلى AVX وكان Larrabee في أذهانهم.

إنتل CT تبدو مكتبة "موازاة البيانات" واعدة للغاية.

لاحظ أن المثال المحدد يقرر ما يجب تنفيذه في وقت الترجمة (نظرًا لأنك تستخدم المعالج المسبق) ، في هذه الحالة يمكنك استخدام تقنيات أكثر تعقيدًا لتحديد ما تريد تنفيذه بالفعل ؛ على سبيل المثال ، إرسال علامة: http://cplusplus.co.il/2010/01/03/tag-dispatching/باتباع المثال الموضح هناك ، يمكن أن يكون التنفيذ السريع مع SIMD ، والبطء بدونه.

هل فكرت في استخدام الحلول الحالية مثل liboil؟ ينفذ الكثير من عمليات SIMD الشائعة ويمكن أن تقرر في وقت التشغيل ما إذا كنت تريد استخدام رمز SIMD/غير SIMD (باستخدام مؤشرات الوظيفة المعينة بواسطة وظيفة التهيئة).

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top