Какие могут быть некоторые возможные проблемы с этим использованием OpenMP? - PullRequest
2 голосов
/ 11 февраля 2012

Я пытался выяснить, как распараллелить сегмент кода в OpenMP, где внутренняя часть цикла for независима от остального.

По сути, проект имеет дело с системами частиц, но я не думаю, что это должно иметь отношение к распараллеливанию кода. Это проблема кэширования, когда цикл for разделяет потоки таким образом, чтобы частицы не кэшировались в каждом ядре эффективным образом?

Редактировать: Как уже упоминалось в ответе ниже, мне интересно, почему я не получаю ускорение.

#pragma omp parallel for
for (unsigned i = 0; i < psize-n_dead; ++i)
{
    s->particles[i].pos = s->particles[i].pos + dt * s->particles[i].vel;
    s->particles[i].vel = (1 - dt*.1) * s->particles[i].vel + dt*s->force;
    //  printf("%d", omp_get_thread_num());

}

Ответы [ 3 ]

2 голосов
/ 11 февраля 2012

В этом фрагменте кода нет проблем с корректностью, таких как скачки данных.

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

Не относится к OpenMP(и так же к проблеме параллельного ускорения), возможно улучшение производительности может быть достигнуто путем переключения с массива структур на структуру массивов, поскольку это может помочь компилятору векторизовать код (то есть использовать инструкции SIMD целевого процессора):

#pragma omp parallel for
for (unsigned i = 0; i < psize-n_dead; ++i)
{
    s->particles.pos[i] = s->particles.pos[i] + dt * s->particles.vel[i];
    s->particles.vel[i] = (1 - dt*.1) * s->particles.vel[i] + dt*s->force;
}

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

2 голосов
/ 11 февраля 2012

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

Но я думаю, что вам интересно, почему вы не получаете никакого ускорения с параллелизмом.

Так какВы упомянули, что счетчик поездок psize-n_dead будет порядка 4000.Я бы сказал, что это на самом деле довольно мало, учитывая объем работы в цикле.

Другими словами, вам не нужно много общей работы, которую стоит распараллеливать.Таким образом, поточная нагрузка, вероятно, поглощает любое ускорение, которое вы должны получить.Если возможно, вы должны попытаться распараллелить на более высоком уровне.


РЕДАКТИРОВАТЬ: Вы обновили свой комментарий, чтобы включить до 200000.

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

1 голос
/ 11 февраля 2012

Как вы уверены, что вы не получаете ускорение?

Пробуете это обоими способами - массив структур и структура массивов, скомпилированных с помощью gcc -O3 (gcc 4.6), на двухъядерном четырехъядерном процессоре.nehalem, я получаю для psize-n_dead = 200000, запустив 100 итераций для лучшей точности таймера:

Структура массивов (сообщаемое время в миллисекундах)

$ for t in 1 2 4 8; do export OMP_NUM_THREADS=$t; time ./foo; done
Took time 90.984000
Took time 45.992000
Took time 22.996000
Took time 11.998000

Массив структур:

$ for t in 1 2 4 8; do export OMP_NUM_THREADS=$t; time ./foo; done
Took time 58.989000
Took time 28.995000
Took time 14.997000
Took time 8.999000

Тем не менее, я, потому что операция такая короткая (мс), я не видел ускорения без выполнения 100 итераций из-за точности таймера.Кроме того, вам понадобится машина с хорошей пропускной способностью памяти, чтобы получить такое поведение;вы выполняете только ~ 3 FMA и другое умножение для каждых двух фрагментов данных, которые вы читаете.

Код для массива структур следует.

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>

typedef struct particle_struct {
    double pos;
    double vel;
} particle;

typedef struct simulation_struct {
    particle *particles;
    double force;
} simulation;

void tick(struct timeval *t) {
    gettimeofday(t, NULL);
}

/* returns time in seconds from now to time described by t */
double tock(struct timeval *t) {
    struct timeval now;
    gettimeofday(&now, NULL);
    return (double)(now.tv_sec - t->tv_sec) + ((double)(now.tv_usec - t->tv_usec)/1000000.);
}


void update(simulation *s, unsigned psize, double dt) {
#pragma omp parallel for
    for (unsigned i = 0; i < psize; ++i)
    {
        s->particles[i].pos = s->particles[i].pos+ dt * s->particles[i].vel;
        s->particles[i].vel = (1 - dt*.1) * s->particles[i].vel + dt*s->force;
    }
}

void init(simulation *s, unsigned np) {
    s->force = 1.;
    s->particles = malloc(np*sizeof(particle));
    for (unsigned i=0; i<np; i++) {
        s->particles[i].pos = 1.;
        s->particles[i].vel = 1.;
}

int main(void)
{
    const unsigned np=200000;
    simulation s;
    struct timeval clock;

    init(&s, np);
    tick(&clock);
    for (int iter=0;iter< 100; iter++) 
        update(&s, np, 0.75);
    double elapsed=tock(&clock)*1000.;
    printf("Took time %lf\n", elapsed);

    free(s.particles);
}
...