Подтвердить что ты не робот

Почему std:: async медленнее по сравнению с простыми отдельными потоками?

Мне говорили несколько раз, что я должен использовать std::async для запуска огня и забыть задачи с параметром std::launch::async (так что это делает магию для нового потока исполнения).

Воодушевленные этими утверждениями, я хотел видеть, как std::async сравнивается с:

  • последовательное выполнение
  • простой отсоединенный std::thread
  • моя простая асинхронная реализация

Моя наивная реализация async выглядит следующим образом:

template <typename F, typename... Args>
auto myAsync(F&& f, Args&&... args) -> std::future<decltype(f(args...))>
{
    std::packaged_task<decltype(f(args...))()> task(std::bind(std::forward<F>(f), std::forward<Args>(args)...));
    auto future = task.get_future();

    std::thread thread(std::move(task));
    thread.detach();

    return future;
}

Ничего особенного здесь, упаковывает функтор f в std::packaged task вместе с его аргументами, запускает его на новый std::thread, который отсоединяется, и возвращается с std::future из задачи.

И теперь код измерения времени выполнения с std::chrono::high_resolution_clock:

int main(void)
{
    constexpr unsigned short TIMES = 1000;

    auto start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < TIMES; ++i)
    {
        someTask();
    }
    auto dur = std::chrono::high_resolution_clock::now() - start;

    auto tstart = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < TIMES; ++i)
    {
        std::thread t(someTask);
        t.detach();
    }
    auto tdur = std::chrono::high_resolution_clock::now() - tstart;

    std::future<void> f;
    auto astart = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < TIMES; ++i)
    {
        f = std::async(std::launch::async, someTask);
    }
    auto adur = std::chrono::high_resolution_clock::now() - astart;

    auto mastart = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < TIMES; ++i)
    {
        f = myAsync(someTask);
    }
    auto madur = std::chrono::high_resolution_clock::now() - mastart;

    std::cout << "Simple: " << std::chrono::duration_cast<std::chrono::microseconds>(dur).count() <<
    std::endl << "Threaded: " << std::chrono::duration_cast<std::chrono::microseconds>(tdur).count() <<
    std::endl << "std::sync: " << std::chrono::duration_cast<std::chrono::microseconds>(adur).count() <<
    std::endl << "My async: " << std::chrono::duration_cast<std::chrono::microseconds>(madur).count() << std::endl;

    return EXIT_SUCCESS;
}

Где someTask() - простой метод, где я немного жду, моделируя проделанную работу:

void someTask()
{
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
}

Наконец, мои результаты:

  • Последовательный: 1263615
  • Резьбовое: 47111
  • std:: sync: 821441
  • My async: 30784

Может ли кто-нибудь объяснить эти результаты? Похоже, что std::aysnc намного медленнее, чем моя наивная реализация, или просто простая и сильная отсоединенная std::thread s. Почему? После этих результатов есть какая-то причина использовать std::async?

(Обратите внимание, что я сделал этот тест с clang++ и g++ тоже, и результаты были очень похожими)

UPDATE:

После прочтения ответа Dave S я обновил свой маленький тест следующим образом:

std::future<void> f[TIMES];
auto astart = std::chrono::high_resolution_clock::now();
for (int i = 0; i < TIMES; ++i)
{
    f[i] = std::async(std::launch::async, someTask);
}
auto adur = std::chrono::high_resolution_clock::now() - astart;

Итак, std::future теперь не уничтожаются - и, таким образом, соединяются - каждый прогон. После этого изменения кода std::async производит похожие результаты для моей реализации и отсоединения std::thread s.

4b9b3361

Ответ 1

Одно из ключевых отличий заключается в том, что будущее, возвращаемое async, объединяет поток, когда будущее уничтожается или в вашем случае заменяется новым значением.

Это означает, что он должен выполнить someTask() и присоединиться к потоку, для которого требуется время. Ни один из ваших других тестов не делает этого, где они просто создают их самостоятельно.

Ответ 2

sts::async возвращает специальный std::future. Это будущее имеет ~future, который выполняет .wait().

Итак, ваши примеры принципиально разные. Медленные фактически выполняют задачи во время вашего времени. Быстрые просто останавливают задачи и забывают, как когда-либо знать, что задача выполнена. Поскольку поведение программ, которые препятствуют потокам в прошлом конце конца, непредсказуемо, следует избегать этого.

Правильный способ сравнения задач состоит в том, чтобы сохранить результирующий future при генерации, и до того, как таймер закончит либо .wait()/.join() их всех, либо не уничтожит объекты до истечения таймера. Тем не менее, этот последний случай делает версию шитья выглядела хуже, чем она есть.

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

Обратите внимание, что перемещенные фьючерсы удаляют ожидание из источника.