Почему поток C ++ / будущие накладные расходы такие большие - PullRequest
0 голосов
/ 01 июня 2018

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

Может кто-нибудь помочь мне понять, почему возникают (значительные) накладные расходы при запуске работника в отдельном потоке?

Редактировать : Изначально я дважды тестировал WorkerFuture, исправил это и теперь получаю ту же (лучшую) производительность в случаях отсутствия потока и откладывания асинхронных операций, а также значительные издержки при использовании дополнительного потока.

Команда для компиляции (linux): g ++ -std = c ++ 11 main.cpp -o main -O3 -pthread

Вот вывод (время в миллисекундах):

Thread     : 4000001 size in 1861 ms
Async      : 4000001 size in 1836 ms
Defer async: 4000001 size in 1423 ms
No thread  : 4000001 size in 1455 ms

Код:

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

struct Data
{
    int data;
};

struct Tree
{
    Data data;
    long long total;
    std::vector<Tree *> children;

    long long Size()
    {
        long long size = 1;
        for (auto c : children)
            size += c->Size();
        return size;
    }

    ~Tree()
    {
        for (auto c : children)
            delete c;
    }
};

int
GetRandom(long long size)
{
    static long long counter = 0;
    return counter++ % size;
}

void
Worker_(Tree *root)
{
    std::vector<Tree *> nodes = {root};
    Tree *it = root;
    while (!it->children.empty())
    {
        it = it->children[GetRandom(it->children.size())];
        nodes.push_back(it);
    }
    for (int i = 0; i < 100; ++i)
        nodes.back()->children.push_back(new Tree{{10}, 1, {}});
    for (auto t : nodes)
        ++t->total;
}

long long
Worker(long long iterations)
{
    Tree root = {};
    for (long long i = 0; i < iterations; ++i)
        Worker_(&root);
    return root.Size();
}

void ThreadFn(long long iterations, long long &result)
{
    result = Worker(iterations);
}

long long
WorkerThread(long long iterations)
{
    long long result = 0;
    std::thread t(ThreadFn, iterations, std::ref(result));
    t.join();
    return result;
}

long long
WorkerFuture(long long iterations)
{
    std::future<long long> f = std::async(std::launch::async, [iterations] {
        return Worker(iterations);
    });

    return f.get();
}

long long
WorkerFutureSameThread(long long iterations)
{
    std::future<long long> f = std::async(std::launch::deferred, [iterations] {
        return Worker(iterations);
    });

    return f.get();
}

int main()
{
    long long iterations = 40000;

    auto t1 = std::chrono::high_resolution_clock::now();
    auto total = WorkerThread(iterations);
    auto t2 = std::chrono::high_resolution_clock::now();
    std::cout << "Thread     : " << total << " size in " << std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count() << " ms\n";

    t1 = std::chrono::high_resolution_clock::now();
    total = WorkerFuture(iterations);
    t2 = std::chrono::high_resolution_clock::now();
    std::cout << "Async      : " << total << " size in " << std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count() << " ms\n";

    t1 = std::chrono::high_resolution_clock::now();
    total = WorkerFutureSameThread(iterations);
    t2 = std::chrono::high_resolution_clock::now();
    std::cout << "Defer async: " << total << " size in " << std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count() << " ms\n";

    t1 = std::chrono::high_resolution_clock::now();
    total = Worker(iterations);
    t2 = std::chrono::high_resolution_clock::now();
    std::cout << "No thread  : " << total << " size in " << std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count() << " ms\n";
}

Ответы [ 2 ]

0 голосов
/ 01 июня 2018

Кажется, что проблема вызвана динамическим управлением памятью.Когда задействованы несколько потоков (даже если основной поток ничего не делает), среда выполнения C ++ должна синхронизировать доступ к динамической памяти (куче), что порождает некоторые издержки.Я провел несколько экспериментов с GCC, и решение вашей проблемы заключается в использовании некоторой масштабируемой библиотеки распределителя памяти.Например, когда я использовал tbbmalloc, например,

export LD_LIBRARY_PATH=$TBB_ROOT/lib/intel64/gcc4.7:$LD_LIBRARY_PATH
export LD_PRELOAD=libtbbmalloc_proxy.so.2

, вся проблема исчезла.

0 голосов
/ 01 июня 2018

Причина проста.Вы ничего не делаете параллельно.Когда дополнительный поток что-то делает, основной поток ничего не делает (ожидает завершения работы потока).

В случае с потоком у вас есть дополнительные действия (обработка потока и синхронизация), поэтому у вас есть компромисс.

Чтобы увидеть какую-либо выгоду, нужно сделать как минимум две вещи одновременно.

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