Распараллеливание кода C для двумерного вейвлет-преобразования Хаара с помощью OpenMP

Это мой первый вопрос. Я пытаюсь распараллелить с openMP функцию преобразования 2d haar в C. Я получил ее здесь и соответствующим образом изменен. Программа берет черно-белое изображение, помещает его в матрицу и вычисляет один уровень вейвлет-преобразования Хаара. В конце концов, он нормализует значения и записывает преобразованное изображение на диск.

Это результирующее изображение 1 уровня HDT

Моя проблема в том, что распараллеленная версия работает медленнее, чем последовательная. А пока я прилагаю отрывок из основной части, которую хочу распараллелить (позже я могу поместить весь окружающий код):

void haar_2d ( int m, int n, double u[] )
// m & n are the dimentions (every image is a perfect square)
//u is the input array in **(non column-major!)** row-major order</del>
int i;
int j;
int k;
double s;
double *v;

int tid, nthreads, chunk;

s = sqrt ( 2.0 );

v = ( double * ) malloc ( m * n * sizeof ( double ) );

for ( j = 0; j < n; j++ )
{
    for ( i = 0; i < m; i++ )
    {
        v[i+j*m] = u[i+j*m];
    }
}
/*
Determine K, the largest power of 2 such that K <= M.
*/
k = 1;
while ( k * 2 <= m )
{
    k = k * 2;
}

/*   Transform all columns.  */

while ( n/2 < k ) // just 1 level of transformation
{
    k = k / 2;

    clock_t begin = clock();

    #pragma omp parallel shared(s,v,u,n,m,nthreads,chunk) private(i,j,tid)
    {
        tid = omp_get_thread_num();
        printf("Thread %d starting...\n",tid);

        #pragma omp for schedule (dynamic)
        for ( j = 0; j < n; j++ )
        {
            for ( i = 0; i < k; i++ )
            {               
                v[i  +j*m] = ( u[2*i+j*m] + u[2*i+1+j*m] ) / s;
                v[k+i+j*m] = ( u[2*i+j*m] - u[2*i+1+j*m] ) / s;
            }
        }

    #pragma omp for schedule (dynamic)
    for ( j = 0; j < n; j++ )
    {
        for ( i = 0; i < 2 * k; i++ )
        {
            u[i+j*m] = v[i+j*m];
        }
    }
}//end parallel

clock_t end = clock();
double time_spent = (double)(end - begin) / CLOCKS_PER_SEC;
printf ( "Time for COLUMNS: %f ms\n", time_spent * 1000);

}//end while

// [...]code for rows
free ( v );

return;}

Сроки примерно такие:

Time for COLUMNS: 160.519000 ms // parallel
Time for COLUMNS: 62.842000 ms // serial

Я попытался переупорядочить прагмы множеством разных способов, например, с помощью статического расписания, с разделами, задачами и так далее, а также переупорядочил области данных переменных и динамическое размещение внутри параллельных областей. Я думал, что будет просто распараллелить двухуровневую версию, но вот уже два дня я борюсь. Ищу вашей помощи, ребята, я уже проверил почти все связанные вопросы здесь, но все еще не могу продолжить или, по крайней мере, понять причины. Заранее спасибо. (ЦП Intel Core i3-4005U CPU @ 1,70 ГГц × 4 потока, 2 ядра)

ОБНОВЛЕНИЕ:

1) Что касается m & n, предполагается, что однажды он будет реализовывать также прямоугольные изображения, поэтому я просто оставил это там.

2) Я понял, что u на самом деле является обычным массивом с линеаризованной матрицей внутри, то есть строка за строкой (я использую изображения PGM).

3) Memcpy - лучший вариант, поэтому теперь я использую его.

Что касается основной темы, я попытался разделить работу на n, создав задачу для каждого фрагмента, и в результате получился немного быстрее, чем последовательный код. Теперь я знаю, что входная матрица u находится в хорошем строковом порядке, 2 for, похоже, действуют соответственно, но я не уверен насчет таймингов: используя как omp_get_wtime (), так и clock (), я не знаю, как измерить ускорение. Я проводил тесты с разными размерами изображений, от 16x16 до 4096x4096, и параллельная версия кажется медленнее с clock () и быстрее с omp_get_wtime () и gettimeofday (). У вас есть предложения, как правильно с этим справиться с OpenMP или, по крайней мере, как правильно измерить ускорение?

while ( n/2 < k )
{
    k = k / 2;
    double start_time = omp_get_wtime();
    // clock_t begin = clock();
    #pragma omp parallel shared(s,v,u,n,m,nthreads,chunk) private(i,j,tid) firstprivate(k)
    {
        nthreads = omp_get_num_threads();

         #pragma omp single
         {
          printf("Number of threads = %d\n", nthreads);

          int chunk = n/nthreads;
          printf("Chunks size = %d\n", chunk);
          printf("Thread %d is starting the tasks.\n", omp_get_thread_num());

          int h;

          for(h=0;h<n;h = h + chunk){
          printf("FOR CYCLE i=%d\n", h);

            #pragma omp task shared(s,v,u,n,m,nthreads,chunk) private(i,j,tid) firstprivate(h,k)
            {
                tid = omp_get_thread_num();
                 printf("Thread %d starts at %d position\n", tid , h);

                for ( j = h; j < h + chunk; j++ )
                {
                    for ( i = 0; i < k; i++ )
                    {
                        v[i  +j*m] = ( u[2*i+j*m] + u[2*i+1+j*m] ) / s;
                        v[k+i+j*m] = ( u[2*i+j*m] - u[2*i+1+j*m] ) / s;
                    }
                }
            }// end task
        }//end launching for
        #pragma omp taskwait
        }//end single
        }//end parallel region

        // clock_t end = clock();
        // double time_spent = (double)(end - begin) / CLOCKS_PER_SEC;
        // printf ( "COLUMNS: %f ms\n", time_spent * 1000);

        double time = omp_get_wtime() - start_time;
        printf ( "COLUMNS: %f ms\n", time*1000);

    for ( j = 0; j < n; j++ )
    {
        for ( i = 0; i < 2 * k; i++ )
        {
            u[i+j*m] = v[i+j*m];
        }
    }
 }//end while

person p_koelio    schedule 12.07.2016    source источник
comment
Какой компилятор и ОС? clock() будет делать только то, что вы хотите, со средой выполнения MSVC C. Обычно используют omp_get_wtime().   -  person Z boson    schedule 12.07.2016
comment
Я использую gcc версии 5.3.1 с Ubuntu 16.04 (ядро 4.4). Я выполнил ваш совет, но правильно ли я сравниваю время, полученное с помощью omp_get_wtime () для параллельного кода, со временем, полученным с помощью clock () в последовательном коде? Спасибо   -  person p_koelio    schedule 12.07.2016


Ответы (2)


У меня есть несколько вопросов, которые меня глубоко беспокоят по поводу вашего кода.

  1. m и n - размеры (каждое изображение представляет собой идеальный квадрат)

    Тогда почему есть два параметра размера?

  2. u - входной массив в порядке по столбцам

    Это невероятно плохая идея. C использует для памяти порядок строк, поэтому индексирование по столбцам приводит к поэтапному доступу к памяти. Это очень, очень плохо для производительности. Если возможно, вам нужно это исправить.

  3. Поскольку и u, и v являются линеаризованными матрицами, то это

    for (int j = 0; j < n; j++) {
        for (int i = 0; i < m; i++) {
            v[i + j * m] = u[i + j * m];
        }
    }
    

    можно заменить вызовом memcpy.

    memcpy(v, u, m * n * sizeof(double));
    

К твоему вопросу. Причина того, что ваша версия, использующая OpenMP, медленнее, заключается в том, что все ваши потоки делают одно и то же. Это бесполезно и приводит к плохим вещам, таким как ложное совместное использование. Вам нужно использовать идентификатор каждого потока (tid в вашем коде) для разделения данных по потокам; помня, что ложное совместное использование - это плохо.

person Tim    schedule 12.07.2016
comment
Спасибо за ваши советы, я обновил код, чтобы следовать им, но я не уверен, что вы это хотели. Также я выяснил, что u - это ненормальный массив с линеаризованной матрицей строка за строкой, то есть первые n записей - это одна строка, затем вторые n записей - вторая строка и т. Д. - person p_koelio; 12.07.2016

Проблема заключалась в том, что я использовал clock () вместо omp_get_wtime () благодаря Z-бозону.

person p_koelio    schedule 27.11.2016