Исследования проводились на 8-ядерной машине (2CPU x 4core) Intel(R) Xeon(R) CPU E5530 @ 2.40GHz под управлением операционной системы Microsoft Windows XP Professional x64 Edition Service Pack 2. Все примеры компилировались на Microsoft Visual Studio 2008 Version 9.0.30729.1 SP в конфигурацией Release, OpenMP, с полной оптимизацией, без inline-ов.

для особо любопытствующих представлю «шапку» кода с вспомогательной функцией:

#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <memory.h>
#include <math.h>
#include <omp.h>

static const int iLoopLen = 10000000;
static const int iNumThreads = 8;
static const int iFactor = 100;
static const int iArrLen = 100000;

void print_time_delta(DWORD tcStart, DWORD tcEnd)
{
    const double dTime = (tcEnd-tcStart) * .001;
    printf("%g s\n", dTime);
}

и перейду к делу. Предположим есть у вас большой цикл, совершающий полезное деяние (10 миллионов повторений, прошу заметить)

void f1(double * pdArr)
{
    for(int i=0; i<iLoopLen; ++i) {
        const int index = i%iArrLen;
        pdArr[index] += fabs(i*.001-20.) * 5. + 3.;
    }
}

и хочется вам его распараллелить.

void f2(double * pdArr)
{
#pragma omp parallel for num_threads(iNumThreads)
    for(int i=0; i<iLoopLen; ++i) {
        const int index = i%iArrLen;
        const double dVal = fabs(i*.001-20.) * 5. + 3.;
#pragma omp critical
        {
            pdArr[index] += dVal;
        }
    }
}

Дело благое, и пользователь должен только сказать Вам спасибо, когда программа съест всю мощь его мультиядерной машины и отработает быстрее. Вот только быстрее-ли? Давайте проверять… Для этого используется следующая функция:

int _tmain(int argc, _TCHAR* argv[])
{
    double dArr[iArrLen];

    printf("*** Test1: 1 thread ");
    {
        const DWORD tcStart = GetTickCount();
        for(int i=0; i<iFactor; ++i) {
            f1( dArr );
        }
        const DWORD tcEnd = GetTickCount();
        print_time_delta(tcStart, tcEnd);
    }

    printf("*** Test2: 8 threads (critical) ");
    {
        const DWORD tcStart = GetTickCount();
        for(int i=0; i<iFactor; ++i) {
            f2( dArr );
        }
        const DWORD tcEnd = GetTickCount();
        print_time_delta(tcStart, tcEnd);
    }
}

И получаем результат: последовательная (однопоточная) версия отрабатывает за 4.344 секунды, а параллельная (8-поточная) за … 223 секунды! Т.е. в 50 раз медленнее.
Опытный программист конечно же уже знает в чём дело, а я расскажу почему, и продолжу изыскания, в следующем выпуске.

Спасибо, что делитесь с друзьями


Метки: ,