Использование std :: asyn c и передача аргументов в векторе функции и сбор результатов - PullRequest
0 голосов
/ 04 апреля 2020

Я хочу сделать эту функцию для параллельного вычисления:

#include <iostream>
#include <vector>

int compute_something(int i, int j) {
    return i*j;
}

int main() {

    auto params = std::vector<int>(1000,5);
    std::vector<int> results;
    for (auto i : params)
        results.push_back(compute_something(i,4));

    return 0;
}

Я передаю аргументы из списка в функцию и хочу, чтобы результаты возвращались по порядку. Я хочу использовать асинхронное распараллеливание, так как compute_something () займет разное время, в зависимости от ввода. Кроме того, входной вектор намного больше, чем количество ядер.

1 Ответ

2 голосов
/ 04 апреля 2020

В C ++ 17 стандартные алгоритмы доступны в параллельной версии. Вы задаете политику выполнения (std::seq), параллельную (std::par) или параллельную и векторизованную (std::par_unseq), и она будет выполнять многопоточность для вас в фоновом режиме.

Таким образом, для того, что вы хотите сделать, вы можете использовать std::transform с лямбда-функцией для захвата операции, которую вы хотите выполнить над каждым элементом вашего входного вектора, и результаты помещаются в results vector (размер должен быть таким же):

#include <execution>
#include <algorithm>
#include <vector>

int compute_something(int i, int j) {
    return i * j;
}

int main()
{
    auto params = std::vector<int>(1000, 5);
    std::vector<int> results(1000, 0);
    std::transform(std::execution::par_unseq, params.begin(), params.end(),
        results.begin(), [](int i) { return compute_something(i, 4); }
    );
}

Конечно, возможно встраивать вычисления в лямбду для такого простого вычисления, как в compute_something. Затем код становится следующим:

std::transform(std::execution::par_unseq, params.begin(), params.end(),
        results.begin(), [](int i) { return i * 4; }

Не все компиляторы реализовали политику выполнения. Так что, если ваш компилятор не поддерживает его, вы можете сделать это другим способом: используйте std::async и обработайте входной вектор кусками. Для этого вам нужно определить новую функцию, которая принимает итераторы и возвращает вектор результата. Затем вы можете объединить результаты в конце.

Пример:

#include <future>
#include <vector>

using Iter = std::vector<int>::iterator;

std::vector<int> parallel_compute(Iter beg, Iter end)
{
    auto size = std::distance(beg, end);
    std::vector<int> results;
    results.reserve(size);

    for (Iter it = beg; it != end; ++it)
    {
        results.push_back(*it * 4);
    }

    return results;
}

int main()
{
    const int Size = 1000;
    const int Half = Size / 2;
    auto params = std::vector<int>(Size, 5);

    auto fut1 = std::async(launch::async, parallel_compute, params.begin(), params.begin()+ Half);
    auto fut2 = std::async(launch::async, parallel_compute, params.begin()+ Half, params.end());

    auto res1 = fut1.get();
    auto res2 = fut2.get();

    std::vector<int> results;
    results.insert(results.end(), res1.begin(), res1.end());
    results.insert(results.end(), res2.begin(), res2.end());
}

Политика launch::async обеспечит создание двух потоков. Однако я бы не стал создавать слишком много потоков - по одному на ядро ​​- разумная стратегия. Создание потоков и управление ими вносит некоторые накладные расходы и может привести к обратным результатам, если вы создадите слишком много.

...