Основные и различные выходы OpenMP

#c #parallel-processing #openmp

Вопрос:

Я только начал изучать OpenMP и, как большинство новичков в параллельном программировании, писал простой скрипт для распараллеливания решения интеграла. Ниже приведен код, который я написал:

         num_chunks = 1000;
        int threads_assigned = 0;
        int num_threads = 10;// omp_get_num_threads();
        sum_arr = (double *) malloc(sizeof(double) * num_threads);
        memset(sum_arr, 0, num_threads);
        int sums_each_thread = num_chunks / num_threads;
        printf("num of threads = %dn", num_threads);
        sum = 0.0;
        omp_set_num_threads(num_threads);
        chunk_size = 1.0/(double) num_chunks;
        double start_time = omp_get_wtime();
        #pragma omp parallel
        {
                int idx = omp_get_thread_num();
                if (idx == 0) threads_assigned = omp_get_num_threads();
                int sums_count;
                int loop_size = idx * sums_each_thread;
                for (sums_count = loop_size; sums_count < loop_size   sums_each_thread; sums_count  ) {
                        printf("starting thread no %dn", idx);
                        // double x = (idx * chunk_size)   (chunk_size / 2.0); simplified version in the next line
                        double x = chunk_size * (sums_count   0.5);
                        double y = 4.0 / (1   x*x);
                        sum_arr[idx - 1]  = (y * chunk_size);
                        sum  = y;
                }
                printf("ending thread no %dn", idx);
        }
        double end_time = omp_get_wtime();
        printf("sum = %f, in time %f(s)n", sum * chunk_size, end_time - start_time);
        int new_idx = 0;
        sum = 0.0;
        for(new_idx = 0; new_idx < num_threads; new_idx  ) {
                sum  = sum_arr[new_idx];
        }
        printf("arr sum = %f, with total assigned threads = %dn", sum, threads_assigned);
 

Я рассчитал площадь по:

1 ) вычисление суммы каждой области прямоугольника.

2 ) вычисление суммы всех высот (скалярной суммы), а затем кратной размеру фрагмента.

Математически оба подхода дают один и тот же результат, но в данном случае приведенного выше кода подход номер 2 всегда показывает правильный результат, а подход номер 1 терпит неудачу.

Кто-нибудь может объяснить причину?

Комментарии:

1. Ошибка: memset(sum_arr, 0, num_threads); . Должно быть sizeof(double)*num_threads . Теперь, почему вы вместо этого не использовали calloc?

2. это будет очень медленно из-за «ложного обмена» sum_arr . Потоки обращаются к одной и той же строке кэша, что приводит к дорогостоящей синхронизации

3. Хорошо. @Lundin, исправление, которое вы предложили, не помогает с моим вопросом. И я новичок в C, чтобы просто использовать то, что я узнал быстрее всего

4. @tstanisl эти вещи, я полагаю, на потом. Пожалуйста, сосредоточьтесь на моем вопросе. Спасибо

5. @MuhammadHasan Это ошибка, но не ответ на ваш вопрос OpenMP. Следовательно, это было опубликовано как комментарий, а не как ответ. Однако ошибку все еще нужно исправить.

Ответ №1:

В вашем коде есть две ошибки:

Один из них-доступ к недопустимой записи в arr_sum том, где к элементу -1 обращается главный поток. Заменять

 sum_arr[idx - 1]  = (y * chunk_size);
 

с

 sum_arr[idx]  = (y * chunk_size);
 

Другой проблемой является модификация sum без синхронизации.
Извлечение предыдущего sum и сохранение обновленного значения может чередоваться с аналогичной операцией в другом потоке. Это испортит результат. Этот эффект должен быть заметен после удаления printf("starting thread no %dn", idx); из внутреннего цикла. Чтобы исправить это, просто используйте атомарные обновления.

 #pragma omp atomic
sum  = y;
 

Теперь суммы, вычисленные обоими способами, в порядке.

Однако программа плохо масштабируется. Одной из причин является «ложный общий доступ» arr_sum , когда потоки обращаются к одной и той же строке кэша, что приводит к дорогостоящей синхронизации на уровне процессора.

Другая проблема заключается в использовании атомарных инструкций в critical sum = y , которые страдают от тех же самых проблем.


Вероятно, вам нужно использовать функцию сокращения OpenMP:

     #pragma omp parallel for reduction( :sum)
    for (int i = 0; i < num_chunks;   i) {
        double x = chunk_size * (i   0.5);
        double y = 4.0 / (1   x*x);
        sum  = y;
    }
 

Теперь он отлично масштабируется и во много (~100) раз быстрее, чем исправленный цикл из вопроса OP. Это настолько быстро, что num_chunks > 10e7 требуется, чтобы цикл занимал значительную часть времени выполнения программы.

Комментарии:

1. Да. @tstanisl спасибо. Основной проблемой был индекс массива. Этот код был написан через пару минут в учебнике, и к тому времени требовалось запускать только без какой-либо синхронизации.

2. @МухаммадХасан, я рад, что смог помочь. «Атомарная» часть также важна. В противном случае результат может быть поврежден. Просто удалите «printf» из внутреннего цикла и запустите программу без #pragma omp atomic

3. да, согласен. но я избежал этого с помощью массива. Я только что создал sum var, чтобы протестировать 2 метода, и мне было лень создавать еще один массив.