Увеличивается количество потоков, но программа не работает быстрее C ++ OpenMP Selection Sort - PullRequest
0 голосов
/ 15 ноября 2018

Я пытаюсь написать алгоритм выбора сортировки, используя C ++ OpenMP.Я написал код, он сортирует, но когда я увеличиваю количество потоков, увеличивается время выполнения алгоритма SelectionSort ... Я только начал использовать OpenMP, так что я не очень хорош в этом: я не могу использовать OpenMPreduction, потому что я использую IDE VisualStudio Community 2017. Спасибо за помощь;)

#include "pch.h"
#include <iostream>
#include <omp.h>
#include <random>
#include <ctime>
#include <iomanip>
#include <algorithm>

using namespace std;

const int n = 10000;                            // count of numbers in vector

// ------------------- FUNCTIONS HEADERS -----------------------
vector<int> FillVector();
vector<int> SelectionSort(vector<int> data, int num_th);
void PrintArray(vector<int> data);

int main()
{
    std::vector<int> test_1(n);
    std::vector<int> test_2(n);
    std::vector<int> test_3(n);
    std::vector<int> test_4(n);

    cout << test_1.size() << endl;          // size of vector

    test_1 = FillVector();

    std::copy(std::begin(test_1), std::end(test_1), std::begin(test_2));    // copy vector test_1 to test_2
    std::copy(std::begin(test_1), std::end(test_1), std::begin(test_3));    // copy vector test_1 to test_3
    std::copy(std::begin(test_1), std::end(test_1), std::begin(test_4));    // copy vector test_1 to test_4

    // Testing times of sorting with different threads count


    // Number of Threads: 2
    int num_th = 2;
    clock_t begin = clock();
    test_1 = SelectionSort(test_1, num_th);     // sort vector test_1
    clock_t end = clock();
    double elapsed_secs = double(end - begin) / CLOCKS_PER_SEC;
    cout << elapsed_secs << endl;

    // Number of Threads: 4
    num_th = 4;
    begin = clock();
    test_2 = SelectionSort(test_2, num_th);     // sort vector test_2
    end = clock();
    elapsed_secs = double(end - begin) / CLOCKS_PER_SEC;
    cout << elapsed_secs << endl;

    // Number of Threads: 8
    num_th = 8;
    begin = clock();
    test_3 = SelectionSort(test_3, num_th);     // sort vector test_3
    end = clock();
    elapsed_secs = double(end - begin) / CLOCKS_PER_SEC;
    cout << elapsed_secs << endl;

    // Number of Threads: 16
    num_th = 16;
    begin = clock();
    test_4 = SelectionSort(test_4, num_th);     // sort vector test_4
    end = clock();
    elapsed_secs = double(end - begin) / CLOCKS_PER_SEC;
    cout << elapsed_secs << endl;

    return 0;
}

// ----------------- METHODS --------------------------------

// Fill vector with random integers
vector<int> FillVector() {
    vector<int> temp(n);

    srand(time(NULL));
    for (int i = 0; i < n; i++)
        temp[i] = rand() % n + 1;

    return temp;
}

// EXECUTE parallel Selection Sort usin OpenMP
vector<int> SelectionSort(vector<int> data, int num_th)
{
    // start parallel method (OpenMP)
    //VEIKIA !!!!!!
    vector<int> temp(n);
    std::copy(std::begin(data), std::end(data), std::begin(temp));      // make a temporary copy of array

    omp_set_num_threads(num_th);                                        // set threads number
    for (int i = 0; i < n - 1; i++)
    {
        int min_index = i;
#pragma omp parallel                                                    // start OpenMP parallel code block
        for (int j = i + 1; j < n; j++)
            if (temp[j] < temp[min_index])
                min_index = j;
        std::swap(temp[i], temp[min_index]);                            // std swap method
    }

    return temp;
}

// PRINT vector as 2D array
void PrintArray(vector<int> data)
{
    int rows, elem = 20;                                                // declare rows variable and column count

    if (n % elem != 0)                                                  // calculate rows count
        rows = n / elem + 1;
    else
        rows = n / elem;

    int iii = 0;
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < elem; j++) {
            if (iii != n) {
                cout << setw(3) << left << data[iii] << " ";
                iii++;
            }
            else
                break;
        }
        cout << endl;
    }
    cout << endl;
}

Результат (при n = 5000):

размер: 5000
потоков2: 5,607
темы 4: 8,421
темы 8: 10,979
темы 16: 27,989

ОТКРЫТОЕ изображение

Ответы [ 3 ]

0 голосов
/ 15 ноября 2018

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

#pragma omp parallel for (int j = i + 1; j < n; j++) if (temp[j] < temp[min_index]) min_index = j;
Первая проблема, которую я вижу, заключается в том, что вы используете обычный параллельный интерфейс.Эта команда запускает один и тот же раздел кода для каждого доступного потока (для потоков num_th).Это определенно не даст вам никакого улучшения скорости и может привести к тому, что min_index может содержать индекс значения, который не является минимальным из-за того, что несколько потоков могут одновременно обращаться к одной и той же переменной, что означает, что они не знают, будет ли другой поток перезаписывать переменнуюсразу после индекса имеет меньшее значение или нет.

Это можно исправить, используя #pragma omp parallel for, который делит интервал [i + 1, n) на num_thr частей.Но одновременный доступ к переменной немного сложнее.В этом случае лучшим решением было бы использовать сокращение omp, но, как вы сказали, вы не можете его использовать.Тогда есть и другие варианты, такие как атомная переменная , критическая секция (что вам точно не поможет) или метод сравнения и обмена , но я сомневаюсь, что онидаст вам существенное улучшение скорости вашего алгоритма.

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

0 голосов
/ 15 ноября 2018

Расширяя ответ от @Ramade: Проблемный кусок кода:

    for (int i = 0; i < n - 1; i++)
    {
        int min_index = i;
#pragma omp parallel                                                    // start OpenMP parallel code block
        for (int j = i + 1; j < n; j++)
            if (temp[j] < temp[min_index])
                min_index = j;
        std::swap(temp[i], temp[min_index]);                            // std swap method
    }

Что вам нужно:

  • a #pragma omp for для фактического распределения внутреннего цикла

  • некоторая схема сокращения, чтобы каждый поток не обращался к общей переменной [min_index]

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

Например:

omp_set_num_threads(num_th);                                        // set threads number

// Spawn all threads once
int min_index_all=0;
int min_value_all=INT_MAX;
#pragma omp parallel shared(temp,min_index_all,min_value_all)
{
    //these are private variables
    int min_index,min_value;

    for (int i = 0; i < n - 1; i++)
        min_index = i;
        min_value = temp[min_index];

        // OpenMP distributed loop
        // nowait because once the loop is complete the thread can do the reduction part immediately
        // static schedule because we don't expect a large unbalance of workload between threads
        #pragma omp parallel schedule(static) nowait
        for (int j = i + 1; j < n; j++)
            if (temp[j] < temp[min_index]){
                min_index = j;
                min_value = temp[min_index];
            }

        // reduction part in a critical section
        #pragma omp critical
        {
            if (min_value<min_value_all){
                min_index_all = min_index;
                min_value_all = min_value;
            }
        }

        // explicit barrier: wait for all threads to have finished the reduction
        #pragma omp barrier

        // swap done by a single thread
        // + initialization of shared variables for next iteration
        #pragma omp single
        {
            std::swap(temp[i], temp[min_index]);                            // std swap method
            min_index_all = i+1;
            min_value_all = INT_MAX;
        }

        // implicit barrier after omp single
        // all threads will wait there until the single section is done
        // then they will move together to next iteration of the outer loop
    }// end for i loop
}

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

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

#define SOME_VALUE 20000 // To be tweaked 
int i;
for (i = 0; i < n - 1 - SOME_VALUE; i++){
  //parallel version of the code
}

i = (n - 1 - SOME_VALUE < 0) ? 0 : n-1-SOME_VALUE;
for (; i < n - 1; i++){
  // finish with a serial version of the code
}
0 голосов
/ 15 ноября 2018

Сначала подумайте об алгоритме сортировки выбора. Он постоянно сканирует (под) массив, чтобы найти наименьший элемент, перемещает его вперед. Этот процесс принципиально итеративный, т.е. Вы должны сканировать от 0 до n, прежде чем сканировать от 1 до n. В вашем случае, даже если у вас несколько потоков, вы все равно должны сканировать линейно от начальной позиции до конечной позиции по порядку, поэтому ваши дополнительные потоки не помогают.

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

...