Правильные директивы openmp для вложенных циклов, работающих на одномерном массиве - PullRequest
0 голосов
/ 09 мая 2018

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

float output[n];
const float input[n] = ...;

for (int i = 0; i < n; ++i) {
    output[i] = 0.0f;
    for (int j = 0; j < n; ++j) {
        output[i] += some_calculation(i, input[j]);
    }
}

some_calculation не изменяет свои аргументы и не имеет внутреннего состояния, поэтому его потокобезопасен. Глядя на циклы, я понимаю, что внешний цикл является потокобезопасным, потому что разные итерации выводят в разные области памяти (разные output[i]), а общие элементы input никогда не изменяются во время выполнения цикла, но внутренний цикл не потокобезопасен, потому что имеет условие гонки на output[i], потому что он изменяется на всех итерациях.

Следовательно, я бы хотел порождать потоки и заставлять их работать для разных значений i, но вся итерация по input должна быть локальной для каждого потока, чтобы не вводить условие гонки в output[i]. Я думаю, что следующее достигает этого.

std::array<float, n> output;
const std::array<float, n> input[n];

#pragma omp parallel for
for (int i = 0; i < n; ++i) {
    output[i] = 0.0f;
    for (int j = 0; j < n; ++j) {
        output[i] += some_calculation(i, input[j]);
    }
}

Я не уверен, как это обрабатывает внутренний цикл. Потоки, работающие на разных i, должны иметь возможность выполнять цикл параллельно, но я не понимаю, позволяю ли я им без другой директивы #pragma omp. С другой стороны, я не хочу, чтобы случайно запускать потоки для разных значений j по сравнению с тем же i, потому что это приводит к состоянию гонки. Я также не уверен, что мне нужна дополнительная спецификация о том, как следует обрабатывать два массива.

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

void iterative_step(const std::array<float, n> &input, const std::array<float, n> &output) {
    // Threads have already been spawned
    #pragma omp for
    for (int i = 0; i < n; ++i) {
        output[i] = 0.0f;
        for (int j = 0; j < n; ++j) {
            output[i] += some_calculation(i, input[j]);
        }
    }

int main() {
    ...
    // spawn threads once, but not for this loop
    #pragma omp parallel
    while (...) {
        iterative_step(input, output);
    }
    ...
}

Я просматривал различные другие вопросы, но они касались разных проблем с разными условиями гонки, и я не совсем понимаю, как обобщать ответы.

1 Ответ

0 голосов
/ 09 мая 2018

Вы не хотите omp parallel в main. Используемый вами omp for будет создавать / повторно использовать потоки только для следующего цикла for (int i. Для любого конкретного значения i цикл j будет полностью выполняться в одном потоке.

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

...