Ужасная производительность - простая проблема накладных расходов, или есть программный недостаток? - PullRequest
5 голосов
/ 16 мая 2009

У меня есть то, что я понимаю как относительно простую конструкцию OpenMP. Проблема в том, что программа работает примерно в 100-300 раз быстрее с 1 потоком по сравнению с 2 потоками. 87% программы расходуется на gomp_send_wait() и еще 9,5% на gomp_send_post.

Программа дает правильные результаты, но мне интересно, есть ли ошибка в коде, которая вызывает конфликт ресурсов, или это просто то, что накладные расходы на создание потока радикально не стоят для цикла размера чанка 4. p колеблется от 17 до 1000, в зависимости от размера молекулы, которую мы моделируем.

Мои числа для наихудшего случая, когда p равно 17, а размер фрагмента 4. Производительность одинакова, использую ли я статическое, динамическое или управляемое планирование. При p=150 и размере чанка 75 программа все еще работает в 75-100 раз медленнее, чем последовательная.

...
    double e_t_sum=0.0;
    double e_in_sum=0.0;

    int nthreads,tid;

    #pragma omp parallel for schedule(static, 4) reduction(+ : e_t_sum, e_in_sum) shared(ee_t) private(tid, i, d_x, d_y, d_z, rr,) firstprivate( V_in, t_x, t_y, t_z) lastprivate(nthreads)
    for (i = 0; i < p; i++){
        if (i != c){
            nthreads = omp_get_num_threads();               
            tid = omp_get_thread_num();

            d_x = V_in[i].x - t_x; 
            d_y = V_in[i].y - t_y;
            d_z = V_in[i].z - t_z;


            rr = d_x * d_x + d_y * d_y + d_z * d_z;

            if (i < c){

                ee_t[i][c] = energy(rr, V_in[i].q, V_in[c].q, V_in[i].s, V_in[c].s);
                e_t_sum += ee_t[i][c]; 
                e_in_sum += ee_in[i][c];    
            }
            else{

                ee_t[c][i] = energy(rr, V_in[i].q, V_in[c].q, V_in[i].s, V_in[c].s);
                e_t_sum += ee_t[c][i]; 
                e_in_sum += ee_in[c][i];    
            }

            // if(pid==0){printf("e_t_sum[%d]: %f\n", tid, e_t_sum[tid]);}

        }
    }//end parallel for 


        e_t += e_t_sum;
        e_t -= e_in_sum;            

...

Ответы [ 6 ]

6 голосов
/ 10 мая 2011

Во-первых, я не думаю, что оптимизация вашего последовательного кода в этом случае поможет ответить на вашу дилемму OpenMP. Не беспокойся об этом.

ИМО есть три возможных объяснения замедления:

  1. Это можно легко объяснить замедлением. Элементы массива ee_t приводят к ложному разделению в строке кэша. Ложное совместное использование происходит, когда ядра в конечном итоге записывают в одну и ту же строку кэша, не потому, что они на самом деле обмениваются данными, но когда то, что записывают ядра, просто оказывается в одной и той же строке кэша (именно поэтому это называется false совместное использование ). Я могу объяснить больше, если вы не нашли ложный обмен на Google. Выравнивание строки кэша элементов ee_t может сильно помочь.

  2. Накладные расходы на нерестовые работы выше, чем выгода параллелизма. Вы пробовали менее 8 ядер? Как производительность на 2 ядрах?

  3. Общее количество итераций мало, скажем, в качестве примера мы берем 17. Если вы разделите его на 8 ядер, у него будут проблемы с дисбалансом нагрузки (особенно потому, что некоторые из ваших итераций практически не выполняют никакой работы (когда i == c). По крайней мере одно ядро ​​должно будет выполнить 3 итерации, в то время как все остальные будут do 2. Это не объясняет замедление, но, безусловно, является одной из причин, почему ускорение не так высоко, как вы могли бы ожидать. Поскольку ваши итерации имеют разную длину, я бы использовал динамическое расписание с размером порции 1 или использование openmp guided. Поэкспериментируйте с размером чанка, слишком маленький чанк также приведет к замедлению.

Дайте мне знать, как оно идет.

2 голосов
/ 17 мая 2009

Кажется, вы считаете, что если вы запускаете последовательный код в многопоточном режиме, он должен работать лучше. Это не дано. И это часто не соответствует действительности. Распараллеливание цикла для выполнения в нескольких потоках или нескольких процессорах не всегда приводит к повышению производительности. В большинстве случаев требуется некоторая реструктуризация. В вашем случае код даже не является хорошим серийным кодом.

Любая книга по оптимизации последовательного кода имеет правило № 1 для циклов: удалите все условные операции. Тесты стоят. Некоторые компиляторы (кстати, вы никогда не говорите, какую ОС / компилятор / процессор вы используете ... это имеет значение) могут попытаться оптимизировать работу над условным кодом. Некоторые компиляторы (например, компилятор Sun's C) даже позволяют запускать программу в режиме «сбора», где она генерирует информацию о профиле времени выполнения о том, как часто используются ветки условного выражения, а затем позволяет перекомпилировать в режиме, в котором используются собранные данные оптимизировать сгенерированный код. (См. Параметр -xprofile)

Первое правило для оптимизации параллельного кода - сначала делайте лучшую последовательную оптимизацию, какую только можете. Затем распараллелить петли.

Перемещая условные выражения вне цикла и, как предлагает Metiu, переписывая код в виде двух отдельных циклов, вы предоставляете оптимизатору лучший источник для работы. Последовательный код работает лучше, а распараллеленный код смущающе параллелен.

Тем не менее, результаты могут отличаться в зависимости от ОС / компилятора / платформы.

См. Использование OpenMP и Программирование приложений Solaris

1 голос
/ 18 июня 2009

Во-первых, попробуйте увеличить размер куска. Создание потока сопряжено с дополнительными затратами, и для каждого потока - новая работа, и размер зерна должен быть достаточно большим, чтобы его переполнить.

Еще одна возможность: Реализация сокращения в GOMP может быть очень плохой (на что указывают данные вашего профиля), и она генерирует сообщения после каждого блока, а не накапливается в каждом потоке, а затем собирает их в конце. Попробуйте выделить e_t_sum и e_in_sum как массивы с nthreads элементами в каждом и добавить к e_t_sum[tid] в цикле, а затем выполнить цикл по ним для вычисления общей суммы после завершения параллельного цикла.

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

Другая возможность: Возможно, вы испытываете ложное распространение в своих обновлениях элементов ee_t. Убедитесь в выравнивании этого массива и попробуйте размеры порций, кратные размеру вашей строки кэша. Один тонкий намек на эту патологию - это часть цикла, где i > c занимает непропорционально больше времени, чем часть, где i < c.

1 голос
/ 16 мая 2009

Метиу прав. Вы не можете ожидать хорошей производительности от цикла, в котором есть условные операторы. Это просто плохое кодирование. Даже для скалярной производительности.

Ваш босс должен понимать, что OpenMP и распараллеливание вообще не волшебство. Чтобы получить хорошую производительность из распараллеленного кода, сначала необходимо оптимизировать основной код для скалярной производительности.

Тесты не должны быть удалены. Цикл должен быть реструктурирован. И скалярная производительность также выиграет.

1 голос
/ 16 мая 2009

Я полагаю, что вы должны попытаться переместить все эти ветви (то есть, если) внутри цикла, и сделать это в двух отдельных циклах, один для i c. Это может принести большую пользу даже однопоточному коду, но даст вам больше параллелизма, даже если, как вы сказали, издержки на создание потока могут быть больше, чем преимущества для малого n.

0 голосов
/ 22 мая 2009

Это похоже на проблему с реализацией openmp компилятора GNU. Попробуйте другой компилятор. У Intel есть компилятор Linux, который вы сможете скачать и попробовать здесь.

Еще одна вещь, которую я заметил, это то, что первые частные переменные, которые у вас есть, кажутся совершенно ненужными. Создание личных копий массива V_in может привести к значительным накладным расходам, что может стать вашей проблемой.

Я бы сказал, что это одна из тех 2 проблем, которая является вашей проблемой.

...