Увеличение времени выполнения с увеличением количества потоков OMP - PullRequest
0 голосов
/ 13 апреля 2020

Я распараллеливаю следующий блок, чтобы вычислить число смертельных несчастных случаев в неделю для набора данных переменного размера (0,5M, 1M, 2M и т. Д. c.), Хранящегося внутри localRows, который является вектором , Общие переменные local_lethAccPerWeek, local_accAndPerc, local_boroughWeekAcc - это массивы, хранящиеся непрерывно (например, int local_lethAccPerWeek[NUM_YEARS][NUM_WEEKS_PER_YEAR] = {};).

    // [2] Data processing
    procBegin = MPI_Wtime();

    cout << "[Proc. " + to_string(myrank) + "] Started processing dataset..." << endl;
    omp_set_num_threads(num_omp_threads);
    int cfIndex, brghIndex;

// Every worker will compute in the final datastructure the num of lethal accidents for its sub-dataset and then Reduce it to allow the master to collect final results
#pragma omp parallel for default(shared) schedule(dynamic) private(cfIndex, brghIndex)
    for (int i = 0; i < my_num_rows; i++)
    {
        int lethal = (localRows[i].num_pers_killed > 0) ? 1 : 0;
        string borough = string(localRows[i].borough);
        int week, year, month = 0;

        if (lethal || !borough.empty())
        {
            week = getWeek(localRows[i].date);
            year = getYear(localRows[i].date);
            month = getMonth(localRows[i].date);

            // If I'm week = 1 and month = 12, this means I belong to the first week of the next year.
            // If I'm week = (52 or 53) and month = 01, this means I belong to the last week of the previous year.
            if (week == 1 && month == 12)
                year++;
            else if ((week == 52 || week == 53) && month == 1)
                year--;

            year = year - 2012;
            week = week - 1;
        }

        /* Query1 */
        if (lethal)
#pragma omp atomic
            local_lethAccPerWeek[year][week]++;

        /* Query2 */
        for (int k = 0; k < localRows[i].num_contributing_factors; k++)
        {
            cfIndex = cfDictionary.at(string(localRows[i].contributing_factors[k]));
#pragma omp critical(query2)
            {
                (local_accAndPerc[cfIndex].numAccidents)++;
                (local_accAndPerc[cfIndex].numLethalAccidents) += lethal;
            }
        }

        /* Query3 */
        if (!borough.empty()) // if borough is not specified we're not interested
        {
            brghIndex = brghDictionary.at(borough);
#pragma omp critical(query3)
            {
                local_boroughWeekAcc[brghIndex][year][week].numAccidents++;
                local_boroughWeekAcc[brghIndex][year][week].numLethalAccidents += lethal;
            }
        }
    }

    procDuration = MPI_Wtime() - procBegin;

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

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

1 Ответ

1 голос
/ 13 апреля 2020

Несколько примечаний здесь:

  1. Вы используете schedule(dynamic). Это означает, что каждая отдельная итерация l oop отправляется в другой поток в порядке очереди. Это добавляет много накладных расходов, особенно если my_num_rows велико. Лучше работать с кусками итераций, скажем, N итераций каждая, поэтому попробуйте изменить условие расписания на schedule(dynamic,N).

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

Atomi c обновления общих переменных выполняются намного медленнее, чем когда они выполняются одним потоком, потому что строка кэша L1 / L2, содержащая значение, постоянно становится недействительной и перезагружается из иерархии кэша. В последовательной программе строки кэша остаются горячими, и компилятор может даже применить оптимизацию регистра, если это одно значение (последнее не применимо к вашему случаю, поскольку вы увеличиваете элементы массива).

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

Что вы можете сделать, это отсортировать и сгруппировать localRows по району и дате, затем распределить вычисления по группам. Это предотвратит истинные и ложные проблемы с совместным использованием при обновлении агрегатов в 1 и 3 кварталах. Что касается способствующих факторов во втором квартале, если их не так много, используйте сокращение OpenMP.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...