Многопоточность, почему этот последовательный код быстрее, чем его параллельная версия? - PullRequest
1 голос
/ 08 марта 2020

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

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

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

#include <iostream>
#include <thread>
#include <vector>
#include <chrono>

void compute(double& res)
{
    res = 2*res;
}

void computeSerial(std::vector<double>& res, const size_t& nPoints)
{
    for (size_t i = 0; i < nPoints; i++)
    {
        compute(res[i]);
    }
}

void computeParallel(std::vector<double>& res, const size_t& nPoints)
{
    int numThreads = std::thread::hardware_concurrency() - 1;
    std::vector<std::thread*> pool(numThreads, nullptr);
    size_t nPointsComputed = 0;
    while(nPointsComputed < nPoints)
    {
        size_t firstIndex = nPointsComputed;
        for (size_t i = 0; i < numThreads; i++)
        {
            size_t index = firstIndex + i;
            if(index < nPoints)
            {
                pool[i] = new std::thread(compute, std::ref(res[index]));
            }
        }
        for (size_t i = 0; i < numThreads; i++)
        {
            size_t index = firstIndex + i;
            if(index < nPoints)
            {
                pool[i]->join();
                delete pool[i];
            }
        }
        nPointsComputed += numThreads;
    }
}

int main(void)
{
    size_t pbSize = 1000;
    std::vector<double> vSerial(pbSize, 0);
    std::vector<double> vParallel(pbSize, 0);
    for (size_t i = 0; i < pbSize; i++)
    {
        vSerial[i] = i;
        vParallel[i] = i;
    }

    int numThreads = std::thread::hardware_concurrency();
    std::cout << "Number of threads: " << numThreads << std::endl;

    std::chrono::steady_clock::time_point begin, end;

    begin = std::chrono::steady_clock::now();
    computeSerial(vSerial, pbSize);
    end = std::chrono::steady_clock::now();
    std::cout << "duration serial   = " << std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count() << "[ns]" << std::endl;

    begin = std::chrono::steady_clock::now();
    computeParallel(vParallel, pbSize);
    end = std::chrono::steady_clock::now();
    std::cout << "duration parallel = " << std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin).count() << "[ns]" << std::endl;

    return 0;
}

После компиляции с clang++ -pthread main.cc Я получаю следующий вывод:

Number of threads: 6
duration serial   = 23561[µs]
duration parallel = 12219928[µs]

Последовательная версия значительно быстрее, чем параллельная , независимо от количества двойников для вычисления.

Ответы [ 3 ]

4 голосов
/ 08 марта 2020

Запуск потока (или даже просто динамическое распределение c) - это то, что требует гораздо больше инструкций, чем вычисление двойного числа.

Вам нужно разделить свою работу на более крупные куски. .. запуск отдельного потока процессора только для вычисления 2*x никогда не будет оптимизацией.

Возможно, имеет смысл разделить задачу вычисления двойного числа 4000000 на 4 потока, каждый из которых вычисляет 1000000 в результате получается oop.

Ситуация сильно отличается для графических процессоров, где, например, можно запустить поток для каждого пикселя.

2 голосов
/ 08 марта 2020

Выглядит так, как будто вы создаете 1000 потоков, а не 6 потоков, потому что в вашей строке

nPointsComputed += numThreads;

увеличивается на число потоков, а l oop работает до nPointsComputed <1000. </p>

Вместо этого вам необходимо

  • создать пакеты из numberOfPointsPerThread = numberOfPoints / numberOfThreads,
  • , а затем создать numberOfThreads потоков, каждый из которых будет работать с пакетом размером numberOfPointsPerThread со смещением , то есть поток i работает на indices k = i * numberOfPointsPerThread, ..., (i+1)*numberOfPointsPerThread-1.

Вы должны быть осторожны, если у deviv numberOfPoints / numberOfThreads есть остаток. Используйте функцию ceil для создания больших пакетов и ограничьте последний пакет концом массива.

0 голосов
/ 09 марта 2020

Распределение / выделение потоков занимает очень много времени, потому что ядру нужно многое сделать для изменения контекста. Вот почему лучше использовать ОДИН поток в каждом ядре, и fifo, в котором вы передаете много задач, которые будут распределены между запущенными потоками.

Посмотрите на этот код: https://github.com/sancelot/thread_pool

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