سؤال

أنا جديدة على البرمجة بشكل عام لذا يرجى الحفاظ على ذلك في الاعتبار عند الإجابة على سؤالي.

لدي برنامج يأخذ كبيرة 3D array (1 مليار العناصر) و يلخص العناصر على مختلف محاور لإنتاج مجموعة 2D من إسقاط كل جانب من البيانات.المشكلة هنا هو أن ذاكرة الوصول العشوائي المكثف حسب البرنامج باستمرار جلب المعلومات من ذاكرة الوصول العشوائي ، سواء في القراءة والكتابة.

السؤال هو, وأنا اكتساب أي زيادة الأداء إذا كنت multithread البرنامج أو كنت في نهاية المطاف الوقوع في رام الوصول إلى عنق الزجاجة ؟ عندما أقول خاصية تعدد ، أعني فقط خاصية تعدد لمدة 2 أو 4 محاور ، لا أكثر.

إذا كان ذلك يساعد الحالي تكوين الكمبيوتر هو 2.4 ghz core2 quad, 1033 fsb, 4gb ram في 667mhz.

شكرا مقدما ،

-Faken

تحرير:

يبدو لي أن الناس هنا هم أكثر اهتماما في هذا السؤال الذي كان متوقعا في البداية.سوف توسيع سؤال آخر بعض رمز لأولئك الذين يهتمون.

أولا الخلفية قليلا على لي حتى نفهم أين أنا قادم من.أنا الهندسة الميكانيكية لطلاب الدراسات العليا من بعض كيف تمكنت من اختيار الموضوع الذي تقريبا لا علاقة الهندسة الميكانيكية.لقد اتخذت 1 دورة تمهيدية جافا (القسري) حوالي 5 سنوات و لم يمس البرمجة حتى قبل نحو شهر عندما بدأت رسالتي بشكل جدي.لقد اتخذت أيضا (اضطر مرة أخرى, لا زلت لا أعرف لماذا) دورة في الالكترونيات وهندسة الكمبيوتر, تعاملنا مع التحكم الصغرى (8-بت) ، الداخلية ، وبعض ASM الترميز بالنسبة لهم.بخلاف ذلك, أنا أعرف شيئا عن البرمجة.

هنا هو رمز:

int dim = 1000;
int steps = 7 //ranges from 1 to  255

for (int stage = 1; stage < steps; stage++)
for (int j = 0; j < dim; j++)
    for (int i = 0; i < dim; i++)
    {
        sum = 0;
        for (int k = 0; k < dim; k++)
            if (partMap[(((i * dim) + k) * dim) + j] >= stage)
                sum++;

        projection[(j*dim) + i] = sum;
    } 

هذا المقطع من التعليمات البرمجية تعمل على محور z فقط.البيانات الرئيسية ، وذلك بسبب الطريقة التي تم بناؤها ، غريب معالجة النظام ولكن كنت لا داعي للقلق بشأن ذلك.هناك أيضا رمز آخر من أجل القيام إسقاطات أخرى جوانب المكعب ولكن يفعلون أشياء مختلفة جدا.

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

المحلول

ليس هناك سوى طريقة واحدة لتحسين كود:معرفة ما تفعله هذا هو بطيء, و لا أقل من ذلك.حالة خاصة من "فعل أقل من ذلك" هو أن تفعل شيئا آخر بدلا من ذلك بأنه أسرع.

لذلك أولا وقبل كل شيء ، هذا ما أفعله على نشر التعليمات البرمجية:

#include <fstream>
#include <sstream>
using std::ios_base;

template<typename Iterator, typename Value>
void iota(Iterator start, Iterator end, Value val) {
    while (start != end) {
        *(start++) = val++;
    }
}

int main() {

    const int dim = 1000;
    const int cubesize = dim*dim*dim;
    const int squaresize = dim*dim;
    const int steps = 7; //ranges from 1 to  255
    typedef unsigned char uchar;

    uchar *partMap = new uchar[cubesize];
    // dummy data. I timed this separately and it takes about
    // a second, so I won't worry about its effect on overall timings.
    iota(partMap, partMap + cubesize, uchar(7));
    uchar *projection = new uchar[squaresize];

    for (int stage = 1; stage < steps; stage++) {
        for (int j = 0; j < dim; j++) {
                for (int i = 0; i < dim; i++)
                {
                        int sum = 0;
                        for (int k = 0; k < dim; k++)
                            if (partMap[(((i * dim) + k) * dim) + j] >= stage)
                                sum++;

                        projection[(j*dim) + i] = sum;
                }
        }

        std::stringstream filename;
        filename << "results" << stage << ".bin";
        std::ofstream file(filename.str().c_str(), 
            ios_base::out | ios_base::binary | ios_base::trunc);
        file.write((char *)projection, squaresize);
    }

    delete[] projection;
    delete[] partMap;
}

(تحرير:لاحظت فقط أن "الإسقاط" يجب أن يكون مجموعة من الباحث, لا تحلق.بلدي سيئة.هذا سوف تحدث فرقا في بعض الأوقات, ولكن نأمل أن لا كبير جدا من واحد.)

ثم نسخت result*.bin إلى gold*.bin, حتى يمكنني التحقق من بلدي التغييرات المستقبلية على النحو التالي:

$ make big -B CPPFLAGS="-O3 -pedantic -Wall" && time ./big; for n in 1 2 3 4 5
6; do diff -q results$n.bin gold$n.bin; done
g++  -O3 -pedantic -Wall   big.cpp   -o big

real    1m41.978s
user    1m39.450s
sys     0m0.451s

حسنا, حتى 100 ثانية في هذه اللحظة.

لذا المضاربة أنه التمشي من خلال مليار البند مجموعة البيانات التي البطيء ، دعونا نحاول فقط أن يمر مرة واحدة بدلا من مرة واحدة في كل مرحلة:

    uchar *projections[steps];
    for (int stage = 1; stage < steps; stage++) {
         projections[stage] = new uchar[squaresize];
    }

    for (int j = 0; j < dim; j++) {
            for (int i = 0; i < dim; i++)
            {
                    int counts[256] = {0};
                    for (int k = 0; k < dim; k++)
                            counts[partMap[(((i * dim) + k) * dim) + j]]++;

                    int sum = 0;
                    for (int idx = 255; idx >= steps; --idx) {
                        sum += counts[idx];
                    }
                    for (int stage = steps-1; stage > 0; --stage) {
                        sum += counts[stage];
                        projections[stage][(j*dim) + i] = sum;
                    }
            }
    }

    for (int stage = 1; stage < steps; stage++) {
        std::stringstream filename;
        filename << "results" << stage << ".bin";
        std::ofstream file(filename.str().c_str(),
            ios_base::out | ios_base::binary | ios_base::trunc);
        file.write((char *)projections[stage], squaresize);
    }

    for (int stage = 1; stage < steps; stage++) delete[] projections[stage];
    delete[] partMap;

انها أسرع قليلا:

$ make big -B CPPFLAGS="-O3 -pedantic -Wall" && time ./big; for n in 1 2 3 4 5
6; do diff -q results$n.bin gold$n.bin; done
g++  -O3 -pedantic -Wall   big.cpp   -o big

real    1m15.176s
user    1m13.772s
sys     0m0.841s

الآن ، steps صغير جدا في هذا المثال ، لذلك نحن نفعل الكثير من العمل لا لزوم لها مع "التهم" مجموعة.دون حتى التنميط ، أعتقد أن العد إلى 256 مرتين (مرة واحدة لمسح مجموعة مرة واحدة باختصار) مهم جدا مقارنة مع العد إلى 1000 (لتشغيل على طول العمود).لذلك دعونا تغيير ذلك:

    for (int j = 0; j < dim; j++) {
            for (int i = 0; i < dim; i++)
            {
                    // steps+1, not steps. I got this wrong the first time,
                    // which at least proved that my diffs work as a check
                    // of the answer...
                    int counts[steps+1] = {0};
                    for (int k = 0; k < dim; k++) {
                        uchar val = partMap[(((i * dim) + k) * dim) + j];
                        if (val >= steps) 
                            counts[steps]++;
                        else counts[val]++;
                    }

                    int sum = counts[steps];
                    for (int stage = steps-1; stage > 0; --stage) {
                        sum += counts[stage];
                        projections[stage][(j*dim) + i] = sum;
                    }
            }
    }

الآن نحن فقط استخدام العديد من الدلاء ونحن بحاجة فعلا.

$ make big -B CPPFLAGS="-O3 -pedantic -Wall" && time ./big; for n in 1 2 3 4 5
6; do diff -q results$n.bin gold$n.bin; done
g++  -O3 -pedantic -Wall   big.cpp   -o big

real    0m27.643s
user    0m26.551s
sys     0m0.483s

مرحى.الرمز هو ما يقرب من 4 مرات أسرع من النسخة الأولى ، ينتج نفس النتائج.كل ما قمت به هو تغيير ما في النظام الرياضيات يتم:لم ينظر حتى في متعدد خيوط أو الجلب المسبق حتى الآن.و أنا لم محاولة أي التقنية العالية حلقة الأمثل تركها إلى مترجم.لذلك يمكن اعتبار هذا بداية لائق.

ومع ذلك فإنه ما زال يتلقى أمر من حجم أكثر من 1s التي ذرة يعمل في.لذلك ربما يكون هناك مكاسب كبيرة لا تزال تجد.احد الفرق الرئيسي هو أن ذرة يعمل على 1d مجموعة في ترتيب تسلسلي ، بدلا من القفز عنه في كل مكان.كما قلت في أول الجواب ، يجب أن تهدف دائما استخدام ترتيب تسلسلي على المكعب.

لذا دعونا من سطر واحد تغيير, تبديل i و j الحلقات:

            for (int i = 0; i < dim; i++)
    for (int j = 0; j < dim; j++) {

هذا لا يزال لا ترتيب تسلسلي ، لكنه يعني نحن مع التركيز على مليون بايت شريحة من مكعب في وقت واحد.الحديث وحدة المعالجة المركزية على الأقل 4MB ذاكرة التخزين المؤقت ، وذلك مع قليل من الحظ سوف فقط ضرب الذاكرة الرئيسية لأي جزء من المكعب مرة واحدة في البرنامج بأكمله.حتى مع أفضل محلة نحن يمكن أن تقلل من حركة المرور في L1 cache, جدا, ولكن الذاكرة الرئيسية هو الأبطأ.

كم الفرق في ذلك ؟

$ make big -B CPPFLAGS="-O3 -pedantic -Wall" && time ./big; for n in 1 2 3 4 5
6; do diff -q results$n.bin gold$n.bin; done
g++  -O3 -pedantic -Wall   big.cpp   -o big

real    0m8.221s
user    0m4.507s
sys     0m0.514s

ليس سيئا.في الواقع, هذا التغيير وحده يجلب رمز الأصلي من 100s 20s.لذلك هذا هو المسؤول عن عامل من 5 ، وكل شيء آخر فعلته هو المسؤول عن عامل آخر من 5 (أعتقد الفرق بين 'المستخدم' و 'الحقيقي' في الغالب تمثل حقيقة بلدي الفيروسات قيد التشغيل ، التي لم تكن في وقت سابق.'المستخدم' هو كم من الوقت البرنامج المحتلة وحدة المعالجة المركزية, 'الحقيقية' يشمل الوقت الذي يقضيه مع وقف التنفيذ ، إما في انتظار الإدخال/الإخراج أو إعطاء عملية أخرى وقت تشغيل).

طبعا أمنياتي نوع يعتمد على حقيقة أن كل ما نقوم به مع القيم الموجودة في كل عمود هو تبادلي و النقابي.والحد من عدد من الدلاء عملت فقط لأن القيم الكبيرة كلها على نفس المعاملة.هذا قد لا يكون صحيحا بالنسبة لجميع العمليات الخاصة بك, لذلك عليك أن تبدو في الحلقة الداخلية من كل واحد بدوره إلى معرفة ما يجب القيام به مع ذلك.

و رمز هو قليلا أكثر تعقيدا.بدلا من تشغيل مرور البيانات به "وكذا" لكل مرحلة ، نحن الحوسبة جميع المراحل في نفس الوقت في شوط واحد على البيانات.إذا كنت تبدأ في فعل صف و عمود الحسابات في مسار واحد ، كما أوصيت في أول الجواب, هذا سوف تزداد سوءا.عليك أن تبدأ في كسر الشفرة في وظائف للحفاظ للقراءة.

وأخيرا الكثير من كسب الأداء جاء من الأمثل لحقيقة أن "الخطوات" صغير.مع steps=100, أنا الحصول على:

$ make big -B CPPFLAGS="-O3 -pedantic -Wall" && time ./big; for n in 1 2 3 4 5
6; do diff -q results$n.bin gold$n.bin; done
g++  -O3 -pedantic -Wall   big.cpp   -o big

real    0m22.262s
user    0m10.108s
sys     0m1.029s

هذا ليس سيئا للغاية.مع الخطوات=100 رمز الأصلي ربما يستغرق حوالي 1400 ثانية ، على الرغم من أنني لن تشغيله إلى إثبات ذلك.ولكن من الجدير بالذكر أنه لم اتخذت تماما بعيدا الوقت الاعتماد على "خطوات" ، sub-الخطية.

نصائح أخرى

خاصية تعدد متعددة النوى يمكن أن تقلل من الوقت اللازم المبلغ عبر محاور ، ولكن الأمر يتطلب عناية خاصة.هل يمكن فعلا الحصول على أكبر يعزز الأداء من بعض التغييرات التي يمكن أن تجعل الخاصة بك خيط واحد من التعليمات البرمجية:

  1. كنت فقط بحاجة الى العديد من المواضيع لتتناسب مع عدد من النوى المتاحة لك.هذا هو وحدة المعالجة المركزية مكثفة العملية المواضيع من غير المرجح أن تكون في انتظار I/O.

  2. فإن الافتراض أعلاه قد لا تصمد إذا كان الصفيف بأكمله لا يصلح في ذاكرة الوصول العشوائي.إذا أجزاء من مجموعة هي المقسم في بعض المواضيع سوف تكون في انتظار الترحيل عمليات كاملة.في هذه الحالة, هذا البرنامج قد تستفيد من وجود المزيد من المواضيع من النوى.كثيرة جدا, ومع ذلك ، والأداء سوف تنخفض بسبب تكلفة سياق التحول.قد يكون لديك تجربة مع عدد مؤشرات الترابط.القاعدة العامة هو للحد من عدد من مفاتيح السياق بين استعداد المواضيع.

  3. إذا كان الصفيف بأكمله لا يصلح في ذاكرة الوصول العشوائي ، كنت ترغب في تقليل الترحيل!ترتيب كل موضوع بالوصول إلى الذاكرة المسائل ، وكذلك ذاكرة الوصول إلى نمط من كافة مؤشرات الترابط قيد التشغيل.إلى أقصى حد ممكن, كنت أريد أن أنتهي من جزء واحد من مجموعة قبل الانتقال إلى المرحلة التالية ، أبدا إلى العودة إلى منطقة مغطاة.

  4. كل نواة ستستفيد من الحاجة إلى الوصول منفصل تماما المنطقة من الذاكرة.كنت ترغب في تجنب الوصول إلى الذاكرة التأخير الناجم عن أقفال حافلة الخلاف.على الأقل واحد من أبعاد المكعب ، التي ينبغي أن تكون واضحة:تعيين كل موضوع مع جزء من المكعب.

  5. كل نواة ستستفيد أيضا من الوصول إلى المزيد من البيانات من ذاكرة التخزين المؤقت(s), بدلا من جلب الرسائل من ذاكرة الوصول العشوائي.هذا يعني ترتيب الحلقات مثل أن الداخلية الحلقات الوصول قريب الكلمات بدلا من تخطي عبر الصفوف.

  6. أخيرا, اعتمادا على أنواع البيانات في مجموعة ، تعليمات SIMD من Intel/AMD (SSE في مختلف الأجيال) يمكن أن تساعد في تسريع جوهر واحد الأداء عن طريق جمع خلايا متعددة في وقت واحد.VC++ بعض بنيت في دعم.

  7. إذا كان لديك لتحديد أولويات العمل الخاص بك, قد ترغب أولا تقليل القرص الترحيل ، ثم التركيز على تحسين ذاكرة الوصول إلى الاستفادة من وحدة المعالجة المركزية المخابىء ثم التعامل مع خاصية تعدد.

كيف يعمل الكود الخاص بك. هل يذهب مثل هذا؟

for each row: add up the values
for each column: add up the values
for each stack: add up the values

إذا كان الأمر كذلك، فقد ترغب في قراءة "محلية المرجع". اعتمادا على كيفية تخزين بياناتك، قد تجد أنه أثناء قيامك بمكدويد، يجب سحب خط ذاكرة التخزين المؤقت بالكامل لكل قيمة، لأن القيم هي في أي مكان بالقرب من بعضها البعض في الذاكرة. في الواقع، مع مليار قيم، يمكنك سحب الأمور على طول الطريق من القرص. الوصول المتسلسل مع خطوة طويلة (المسافة بين القيم) هو أسوأ استخدام ممكن لذاكرة التخزين المؤقت. حاول التنميط، وإذا كنت ترى أن إضافة المداخن يستغرق وقتا أطول من إضافة الصفوف، فهذا بالتأكيد هو السبب.

أعتقد أنك يمكن أن تشبع حافلة الذاكرة (*)، وفي هذه الحالة ستساعد مؤشرات التردد في حالة فقط إذا كانت Core2 Quad تستخدم حافلات مختلفة من النوى المختلفة. ولكن إذا كنت لا تشبع عرض النطاق الترددي للحافلة، فلن تتمكن من الحصول على أفضل أداء بهذه الطريقة حتى بمجرد تخطيط متعدد الخيط. سيكون لديك 4 النوى قضاء كل وقتهم المتوقفة على مخبأ يفتقد بدلا من واحد.

إذا كنت ذاكرة التخزين المؤقت للذاكرة، فستكون هدفك هو زيارة كل صفحة / خط الذاكرة عدة مرات قدر الإمكان. لذلك سأحاول أشياء مثل الركض على البيانات مرة واحدة، مضيفا كل قيمة إلى ثلاثة مجاميع مختلفة كما تذهب. إذا كان ذلك يعمل بشكل أسرع في جوهر واحد، فنحن في العمل. الخطوة التالية هي أنه مع مكعب 1000x1000x1000، لديك 3 ملايين مجاميع أثناء التنقل. هذا لا يناسب ذاكرة التخزين المؤقت أيضا، لذلك عليك أن تقلق بشأن نفس ذاكرة التخزين المؤقت تفوت مشاكل الكتابة كما تفعل.

تريد التأكد من أنه أثناء تشغيله على طول صف واحد من 1000 قيم مجاورة في ذاكرة الوصول العشوائي، أضف المجموع إلى الصف الذي يشاركون فيه جميعا، فأنت أيضا إضافة إلى إجماليات مجاورة للأعمدة والمكدس (والتي لا تخزنها). لذلك يجب تخزين "مربع" مجاميع العمود بالطريقة المناسبة، كما يجب أن "المربع" من المداخن. بهذه الطريقة تتعامل مع 1000 من قيم مليار بقيمة فقط عن طريق سحب حوالي 12 كيلو من الذاكرة في ذاكرة التخزين المؤقت (4K مقابل 1000 قيم، بالإضافة إلى 4K للحصول على مجاميع العمود 1000، بالإضافة إلى 4K لمدة 1000 مجاميع مكدس). كما ضد ذلك، فأنت تفعل المزيد من المتاجر مما كنت تريد التركيز على 1 المجموع في وقت واحد (مما قد يكون في سجل).

لذلك لا أعيد أي شيء، لكنني أعتقد أنه يستحق النظر في ترتيب الوصول إلى الذاكرة، سواء كنت مؤشر ترابط متعدد أو لا. إذا كان بإمكانك إجراء المزيد من عمل وحدة المعالجة المركزية مع وحدة المعالجة المركزية أثناء الوصول إلى كمية صغيرة نسبيا نسبيا من الذاكرة، فسوف تسرع الإصدار الوحيد المفرد ولكن أيضا وضع نفسك في شكل أفضل بكثير لمتعددة الخيوط، لأن النوى مشاركة ذاكرة التخزين المؤقت محدودة، الذاكرة الحافلة، وذاكرة الوصول العشوائي الرئيسية.

(*) حساب مغلف: في استعراض عشوائي عشوائي خارج الإنترنت، فإن أعلى النطاق الترددي FSB المقدر لمعالجات Core2 التي وجدتها حتى الآن هي متطرفة في 12 جيجابايت / ثانية، مع قناتين في 4x199 ميجا هرتز كل منها). حجم خط ذاكرة التخزين المؤقت هو 64 بايت، وهو أقل من خطوتك. لدرجة أن تلخيص عمود أو كومة الطريق السيئ، فإن الاستيلاء على 64 بايت لكل قيمة، لن يشبع فقط الحافلة إذا كان يفعل 200 مليون قيم في الثانية. أعتقد أنه لا شيء مثل هذا سريع (10-15 ثانية للشيء بأكمله)، أو لن تطلب كيفية تسريعه.

لذلك كان تخميني الأول ربما هو الطريق. ما لم يدخل برنامج التحويل البرمجي الخاص بك أو وحدة المعالجة المركزية بعضا من جلب ذكي للغاية، لا يمكن أن يستخدم جوهر واحد 2 قنوات و 4 عمليات نقل متزامنة لكل دورة. بالنسبة لهذه المسألة، لا يمكن أن تستخدم 4 النوى قناتين و 4 عمليات نقل متزامنة. قد يكون عرض النطاق الترددي الفعال للحافلات لسلسلة الطلبات أقل بكثير من الحد الفعلي، وفي هذه الحالة كنت تريد أن ترى تحسينات جيدة من متعدد الخيوط ببساطة لأن لديك 4 نوى تسأل عن 4 خطوط تخزين مؤقت مختلفة، والتي يمكن أن تكون كلها تم تحميلها في وقت واحد دون مزعجة FSB أو وحدة تحكم ذاكرة التخزين المؤقت. لكن الكمون لا يزال القاتل، وهكذا إذا كنت تستطيع تحميل أقل من خط ذاكرة التخزين المؤقت لكل قيمة لخصي، فستفعل أفضل بكثير.

من المستحيل أن أقول، بشكل عام، لأنك لم تحدد مدى سرعة وحدة المعالجة المركزية الخاصة بك وذاكرة الوصول العشوائي الخاصة بك. فرص جيدة هي أنها ستحسن الأمور، لأنني لا أستطيع أن أتخيل كيف حتى 4 تلخيص المواضيع بالتوازي من شبع ذاكرة الوصول العشوائي بحيث يصبح عنق الزجاجة (وليس وحدة المعالجة المركزية).

يقول بلدي الأمعاء سترى تحسينات متواضعة. ومع ذلك، فإن التنبؤ بنتائج التحسينات هي علاقة خطأ غير موجودة.

جربه ويقايز النتائج.

إذا كان الأمر كذلك، وهذه كبيرة إذا تم ترميزها بشكل مناسب، فستكون بالتأكيد سترى سرعة. الآن كما أشار أحد أساتذي دائما، غالبا ما يحاول الناس أخذ خوارزمية وخيطها وفي النهاية أبطأ. هذا غالبا بسبب المزامنة غير الفعالة. لذلك في الأساس إذا كنت تشعر مثل الخوض في خيوط (أنا بصراحة لن أقترح ذلك إذا كنت جديدا في البرمجة)

في حالتك الخاصة، يمكن أن تكون المزامنة واضحة تماما. هذا هو القول، يمكنك تعيين كل مؤشر ترابط إلى رباعي المصفوفة ثلاثي الأبعاد الكبيرة، حيث يتم ضمان كل مؤشر ترابط الوصول إلى مساحة معينة من مصفوفات الإدخال والإخراج، وبالتالي لا توجد حاجة حقيقية إلى "حماية" البيانات من الوصول المتعدد / يكتب.

باختصار، في هذه الخيوط البسيطة المحددة قد تكون سهلة للغاية، ولكن في المزامنة العامة عند القيام به بشكل سيئ يمكن أن تسبب في استغرق البرنامج وقتا أطول. انها حقا كل هذا يتوقف.

multithreading. ستجعل الشفرة فقط بشكل أسرع إذا كانت الحسابات يمكن تقسيمها إلى قطع يمكن عملها بشكل مستقل ومزامن.


تعديل

قلت ما سبق (إنه استجابة تلقائية تقريبا) لأنني أرى العديد من المطورين يقضون الكثير من الوقت في رمز متعددة لا زيادة في الأداء على الإطلاق. بالطبع، ثم ينتهي بهم المطاف بنفس (أو حتى أداء أبطأ) ومضاعفات إضافية لإدارة مؤشرات الترابط متعددة.

نعم، يظهر بعد قراءة سؤالك مرة أخرى ومراعاة حالتك المحددة التي ستستفيد منها من الصدد.

RAM سريع جدا، لذلك أعتقد أنه سيكون من الصعب للغاية تشبع عرض النطاق الترددي للذاكرة إلا إذا كان لديك العديد من المواضيع.

أعتقد أنه حتى إذا كان قد يؤدي مؤشرات التركيب إلى زيادة الأداء، فهذه الطريقة الخاطئة لالتقاط التحسين. النوى المتعددة هي كل الغضب لأنها الطريقة الوحيدة لمصنعي وحدة المعالجة المركزية لتوفير سرعات وحدة المعالجة المركزية أسرع بسعر قابل للتسويق - وليس بالضرورة لأنها أداة برمجة مذهلة (لا تزال هناك الكثير من النضج الذي يحتاج إلى حدوثه).

انظر دائما إلى الخوارزمية التي تستخدمها قبل كل شيء آخر. أنت تقول إن البرنامج الخاص بك هو مكثف جدا جدا - ماذا يمكنك أن تفعل لتحسين ضربات ذاكرة التخزين المؤقت؟ هل هناك طريقة لفرز صفيفك بحيث يمكن تطبيق الحسابات خطيا؟ ما هي لغة البرمجة التي تستخدمها وسوف تستفيدك لتحسين لغة المستوى الأدنى؟ هل هناك طريقة يمكنك استخدام البرمجة الديناميكية لتخزين نتائجك؟

بشكل عام، قضاء جميع مواردك في العمل نحو خوارزمية أكثر كفاءة، رياضيا وبصورة التحويل البرمجي، ثم تقلق بشأن متعدد النواة. بالطبع، قد تكون بالفعل في تلك المرحلة، وفي هذه الحالة هذا التعليق ليس مفيدا جدا؛ ص

قبل أن تذهب أبدا، يجب عليك تشغيل ملف تعريف ضد التعليمات البرمجية الخاصة بك. من المحتمل أن يكون هناك سؤال مختلف حيث يمكن العثور على ملف تعريف جيد (ربما) مجانا C ++ Profiler.

سيساعدك ذلك في تحديد أي بتات من التعليمات البرمجية التي تتناول أجزاء مهمة من وقت الحساب. قرص هنا وهناك بعد بعض التنميط يمكن أن يجعل الاختلافات الهائلة في بعض الأحيان.

الأسئلة التي تحتاج إلى إجابة للتطبيق الخاص بك معين معروفة.

الأول هو العمل parallelisable? قانون أمدال سوف تعطيك الحد الأعلى على مدى يمكنك تسريع الأمور مع خاصية تعدد.

ثانيا ، أن مؤشرات حل إدخال الكثير من النفقات العامة ؟ أقول لكم هذا البرنامج هو "ذاكرة الوصول العشوائي المكثف حسب البرنامج باستمرار جلب المعلومات من ذاكرة الوصول العشوائي ، سواء في القراءة والكتابة." لذلك تحتاج إلى تحديد ما إذا كانت القراءة/الكتابة إلى التسبب التنسيق النفقات العامة.هذا ليس سهلا.على الرغم من أن كل وحدة المعالجة المركزية يمكن الوصول إلى جهاز الكمبيوتر بالكامل من ذاكرة الوصول العشوائي (القراءة والكتابة) في أي وقت ، وبذلك يمكن أن تبطئ يصل الذاكرة-حتى من دون أقفال لأن مختلف وحدات المعالجة المركزية الاحتفاظ بهم مخابئ تحتاج إلى تنسيق ما في مخابئ مع بعضها البعض (وحدة المعالجة المركزية 1 له قيمة في ذاكرة التخزين المؤقت وحدة المعالجة المركزية 2 تحديثات قيمة في ذاكرة الوصول العشوائي وحدة المعالجة المركزية 2 أن أقول وحدة المعالجة المركزية 1 لإبطال ذاكرة التخزين المؤقت).و إذا كنت بحاجة أقفال (وهو ما يقرب من ضمان كما كنت على حد سواء "القراءة والكتابة" الذاكرة) ثم سوف تحتاج إلى تجنب الخلاف قدر الإمكان.

الثالث هل الذاكرة ملزمة ؟ "ذاكرة الوصول العشوائي المكثف." ليست نفس الشيء مثل "الذاكرة ملزمة." إذا كنت حاليا وحدة المعالجة المركزية ملزمة ثم خاصية تعدد تسريع الامور.إذا كنت حاليا في الذاكرة ثم لا بد خاصية تعدد قد إبطاء الأمور (إذا كان مؤشر ترابط واحد سريع جدا للذاكرة ، ثم ماذا سيحدث مع العديد من المواضيع؟).

الرابع هل أنت بطيئة لبعض سبب آخر ؟ إذا كنت newجي أو mallocing الكثير من الذاكرة في خوارزمية الخاص بك قد تكون رؤية النفقات العامة من ذلك وحدها. و على العديد من المنصات سواء new و malloc لا تحمل خاصية تعدد حسنا, لذا إذا كنت بطيئة الآن لأن malloc هو سيء ، مؤشرات البرنامج سوف يكون أبطأ بسبب malloc سوف يكون أسوأ.

عموما ، ومع ذلك ، من دون رؤية التعليمات البرمجية الخاصة بك ، وأتوقع أن تكون وحدة المعالجة المركزية لا بد وأتوقع خاصية تعدد لتسريع الامور تقريبا بقدر قانون أمدال توحي في الواقع.قد ترغب في النظر في قانون الزواج أو إنتل خيوط اللبنات أو مكتبة أو نوع الخيط انتظار أن تفعل ذلك.

على الرغم من أن هذا من شأنه أن يكون صعبا للغاية بالنسبة لك إذا كنت جديدا في البرمجة، فإن طريقة قوية للغاية لتسريع الأمور ستكون استخدام قوة GPU. ليس فقط VRAM أسرع بكثير من ذاكرة الوصول العشوائية المعتادة، يمكن ل GPU أيضا تشغيل التعليمات البرمجية الخاصة بك بالتوازي في حوالي 128 أو أكثر من النوى. بالطبع، لهذا الكمية من البيانات، ستحتاج إلى الحصول على VRAM كبيرة جدا.

إذا قررت التحقق من هذا الاحتمال، فيجب عليك البحث عن NVIDIA CUDA. لم أفحصها بنفسي، لكنها مخصصة للمشاكل مثل هذا.

إذا كنت تقسم بياناتك بشكل صحيح، فستحصل على زيادة في الأداء. إذا قمت بالتحقق من استخدام وحدة المعالجة المركزية الخاصة بك الآن، فسيكون جوهر واحد عند 100٪ ويجب أن يكون 3 آخرون من 0٪

كل هذا يتوقف على مدى جودة بناء المواضيع الخاصة بك واستخدام الذاكرة.

أيضا، لا تتوقع تحسن X4. X4 هو الحد الأقصى الذي يمكن تحقيقه، سيكون دائما أقل من ذلك اعتمادا على الكثير من العوامل.

يحتوي نظام الكمبيوتر الخاص بك عادة على بعض العناصر التي تحد من الأداء الخشن. أي جزء هو عناصر الحد، يعتمد على الوضع الملموس. عادة واحدة من العوامل التالية قد تكون سبب مشاكل أداءك.

  • عرض النطاق الترددي I / O القرص: في معظم تطبيقات المؤسسات، يتطلب حجم حجم البيانات الهائل الذي تتم معالجته في بعض قاعدة البيانات. قد يتباطأ باستثناء هذه البيانات من قبل كليهما: أقصى سرعة النقل، ولكن في كثير من الأحيان سيكون سبب أكبر تأثير كبير من القرص الصغير يصل إلى قراءة بعض الكتل هنا وهناك. سترى أن وقت الكمون لرؤساء الأقراص يتحرك وحتى الوقت الذي يتطلب فيه القرص أن يحد دوران كامل للتطبيق الخاص بك. منذ فترة طويلة قد قضيت مشكلة حقيقية في استخدام بعض تثبيت Sun E430 المتوسع الذي كان يفوقه بواسطة NextSttation الصغير ... كان FSYNC ثابت () ING من قاعدة البيانات الخاصة بي التي تم تباطؤها بواسطة الأقراص وليس التخزين المؤقت وصول الكتابة (لسبب وجيه) وبعد عادة يمكنك تسريع نظامك عن طريق إضافة أقراص إضافية للحصول على المزيد من I / O في الثانية. إن تكريس محركات الأقراص الخاصة بك إلى مهام محددة قد تكون أفضل في بعض الحالات.

  • كوارة الشبكة: ما يقرب من كل ما يؤثر على سرعة التطبيق قال على الأقراص ما يعادل الشبكة I / O.

  • ذاكرة الوصول العشوائي: إذا كانت ذاكرة الوصول العشوائي الخاصة بك ليست كبيرة بما يكفي لتخزين صورة التطبيق الكاملة تحتاج إلى تخزينها على أقراص خارجية. لذلك يسبب تباطؤ القرص I / O مرة أخرى.

  • سرعة معالجة وحدة المعالجة المركزية (إما عدد صحيح أو النقطة العائمة): قوة معالجة وحدة المعالجة المركزية هي العامل التالي هو الحد الأقصى للمهام المكثفة وحدة المعالجة المركزية. وحدة المعالجة المركزية لديها حد سرعة مادية لا يمكن تخلص منها. الطريقة الوحيدة لتسريعها هي إضافة المزيد من وحدة المعالجة المركزية.

قد تساعدك هذه الحدود في العثور على إجابة لمشكلتك المحددة.

هل تحتاج ببساطة إلى مزيد من قوة المعالجة ونظامك يحتوي على أكثر من وحدة المعالجة المركزية أو الأساسية؟ في هذه الحالة، سيؤدي عدد مؤشرات متعددة إلى تحسين أدائك.

هل تلاحظ شبكة كبيرة أو كائن القرص؟ إذا رأيت هذا، فقد يرمي وحدة المعالجة المركزية القيمة الخاصة بك دورات وحدة المعالجة المركزية في انتظار بعض I / O بطيء. إذا كان موضوع موضوع واحد نشط، فقد يجد هذا الموضوع جميع البيانات المطلوبة للمعالجة في الذاكرة ويمكن أن تلتقط هذه دورات وحدة المعالجة المركزية التي يضيعها خلاف ذلك.

لذلك تحتاج إلى مراقبة طلبك الحالي. حاول إطالة عرض النطاق الترددي للذاكرة للبيانات المنكطة حولها. إذا كان التطبيق نشطا في وحدة المعالجة المركزية واحدة أقل من 100٪، فقد تكون قد وصلت إلى حدود النطاق الترددي للذاكرة. في هذه الحالة، لن تفعل الخيط الإضافي غير جيد بالنسبة لك لأن هذا لا يمنحك عرض النطاق الترددي مور من الذاكرة.

إذا كان وحدة المعالجة المركزية عند 100٪، فحاول ذلك، لكن إلقاء نظرة على الخوارزميات. ستضيف العديد من الخيوط النفقات العامة الإضافية للمزامنة (وتعقيد، طن من التعقيد) التي قد تقلل قليلا من عرض النطاق الترددي الذاكرة. تفضل النحلات التي يمكن تنفيذها تجنب مزامنة الحبيبات الدقيقة.

إذا رأيت أوقات الانتظار الإدخال / الإخراج، فكر في التقسيم الذكي أو التخزين المؤقت ثم عن الخيوط. هناك سبب لعدم دعم جنو بناء موازي في 90 :-)

يحصلني مجال المشكلة التي وصفتها لي إلى GAV نظرة على خوارزميات ذكية أولا. حاول استخدام عمليات القراءة / الكتابة المتسلسلة على الذاكرة الرئيسية قدر الإمكان لدعم وحدة المعالجة المركزية ونظم الذاكرة الفرعية قدر الإمكان. احتفظ بالعمليات "المحلية" وترتيكات التصنيف التحريز على أنها صغيرة ووحدة قدر الإمكان لتقليل مقدار الذاكرة التي تحتاج إلى خلطها قبل التبديل إلى جوهر ثان.

القضاء على تقاسم كاذبة

هذا هو المكان الذي يتم فيه حظر النوى المتعددة على بعضها البعض في محاولة لقراءة أو تحديث عناوين الذاكرة المختلفة التي تشارك ذاكرة التخزين المؤقت للكتلة نفسها. تأمين ذاكرة التخزين المؤقت للمعالج في الكتلة، ويمكن لخيط واحد فقط الكتابة بهذه الكتلة في وقت واحد.

لدى Herb Sutter مقال جيد للغاية حول تقاسم زائف، وكيفية اكتشافها وكيفية تجنبها في خوارزمياتك الموازية.

من الواضح أنه لديه الكثير من الفصصيات الممتازة الأخرى على البرمجة المتزامنة أيضا، انظر له مقالات.

إنها مشكلة مصفوفة؟

كل من Intel و AMD لديه مكتبات محسنة للغاية لجميع أنواع مشاكل الرياضيات الثقيلة. تستخدم هذه المكتبات الخيوط، وترتيب البيانات للحصول على أفضل استخدام ذاكرة التخزين المؤقت، وجلب ذاكرة التخزين المؤقت، تعليمات متجه SSE. كل شئ.

أعتقد أن عليك أن تدفع مقابل المكتبات، لكنها تستحق المال.

إذا كان بإمكانك تقسيم الصفيف بطريقة لا تكتب / قراءة / قراءة من / من نفس المواقف في الصفيف، فيجب أن تزيد من سرعتك.

أعتقد أنه إذا كنت تعامل فقط مع البتات، فقد لا تضطر إلى صفحة أو استخدام ملف مبادلة وفي هذه الحالة، ستساعد بنعم متعدد الخيوط.

إذا لم تتمكن من تحميل كل شيء في الذاكرة في وقت واحد، فأنت بحاجة إلى أن تكون أكثر تحديدا حول حلكم - يجب أن تكون مصممة خصيصا.

على سبيل المثال: افترض أنك تحميل صفيفك في كتل أصغر (قد لا يهم الحجم كثيرا). إذا كنت قد تم تحميلها في مكعب 1000x1000x1000، فيمكنك مجموع ذلك. يمكن تخزين النتائج مؤقتا في ثلاث سهولها الثلاث، ثم تمت إضافتها إلى طائراتك الثلاثة "النتيجة النهائية" الخاصة بك، ثم يمكن إلقاء كتلة 1000 ^ 3 بعيدا أبدا قراءتها مرة أخرى.

إذا كنت تفعل شيئا كهذا، فلن تنفد من الذاكرة، فلن تؤكد على swapfile ولن تقلق للقلق بشأن أي مزامنة موضوع إلا في مناطق قليلة صغيرة جدا ومحددة (إذا كان على الإطلاق).

المشكلة الوحيدة هي التأكد من أن بياناتك في هذا التنسيق يمكنك الوصول إلى مكعب واحد 1000 ^ 3 مباشرة - دون البحث عن رأس القرص الثابت في كل مكان.

تحرير: كان التعليق صحيحا وأنا مخطئ - من المنطقي تماما.

منذ الأمس، أدركت أن المشكلة بأكملها يمكن حلها حيث تم قراءتها - كل قطعة من البيانات قراءة يمكن تلخيصها على الفور في النتائج والتخلص منها. عندما أفكر في الأمر بهذه الطريقة، فأنت على حق، لن تكون مساعدة كبيرة ما لم تتمكن الخيوط يمكن أن تقرأ تدفقين في نفس الوقت دون تصادم.

جرب هذا الرمز:

int dim = 1000;
int steps = 7 //ranges from 1 to  255

for (int stage = 1; stage < steps; stage++)
for (int k = 0; k < dim; k++)
    for (int i = 0; i < dim; i++)
    {
            sum = 0;
            for (int j = 0; j < dim; j++)
                    if (partMap[(((i * dim) + k) * dim) + j] >= stage)
                            projection[i*dim + j] ++ ;
                            // changed order of i and j
    }


transponse(projection)

لقد غيرت ترتيب الحلقات لجعل كود ذاكرة التخزين المؤقت ودية ... كنت ترغب في الحصول عليها ترتيب دفعة الأداء المغنطيسي ... أن تكون شور.

هذه هي الخطوة التي يجب عليك القيام بها قبل محاولة تشغيلها إلى Multithreading

إطلاقا. على الأقل الحصول على كل جوهر على مؤشر ترابط للعمل على مشكلتك سوف يساعد بشكل متزامن. ليس من الواضح إذا كان من الممكن أن يساعد المزيد من الخيوط، لكنه ممكن.

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