gpt4 book ai didi

c++ - 为什么使用 std::async 的并发性比使用 std::thread 更快?

转载 作者:行者123 更新时间:2023-12-04 11:06:50 29 4
gpt4 key购买 nike

我正在阅读关于并发的“现代 C++ 编程手册,第 2 版”的第 8 章,偶然发现了一些让我感到困惑的东西。
作者使用std::thread实现了不同版本的parallel map和reduce函数和 std::async .实现非常接近;例如,心脏parallel_map功能是

// parallel_map using std::async
...
tasks.emplace_back(std::async(
std::launch::async,
[=, &f] {std::transform(begin, last, begin, std::forward<F>(f)); }));
...

// parallel_map using std::thread
...
threads.emplace_back([=, &f] {std::transform(begin, last, begin, std::forward<F>(f)); });
...
完整代码可见 herestd::threadtherestd::async .
让我感到困惑的是,书中报告的计算时间为 std::async 提供了显着且一致的优势。执行。此外,作者承认这一事实是显而易见的,但没有提供任何理由的暗示:

If we compare this [result with async] with the results from the parallel version using threads, we will find that these are faster execution times and that the speedup is significant, especially for the fold function.


我在我的电脑上运行了上面的代码,尽管差异没有书中那么引人注目,但我发现 std::async实现确实比 std::thread快一。 (作者后来还带来了这些算法的标准实现,它们甚至更快)。在我的计算机上,该代码以四个线程运行,这与我的 CPU 的物理内核数相对应。
也许我错过了什么,但为什么 std::async 很明显应该比 std::thread 运行得更快在这个例子中?我的直觉是 std::async作为线程的更高级别的实现,它应该至少花费与线程相同的时间,如果不是更多的话——显然我错了。这些发现是否如书中所建议的那样一致,其解释是什么?

最佳答案

我原来的解释是不正确的。 Refer to @OznOg's answer below.
修改后的答案:
我创建了一个简单的基准测试,它使用 std::asyncstd::thread做一些小任务:

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

__thread volatile int you_shall_not_optimize_this;

void work() {
// This is the simplest way I can think of to prevent the compiler and
// operating system from doing naughty things
you_shall_not_optimize_this = 42;
}

[[gnu::noinline]]
std::chrono::nanoseconds benchmark_threads(size_t count) {
std::vector<std::optional<std::thread>> threads;
threads.resize(count);

auto before = std::chrono::high_resolution_clock::now();

for (size_t i = 0; i < count; ++i)
threads[i] = std::thread { work };

for (size_t i = 0; i < count; ++i)
threads[i]->join();

threads.clear();

auto after = std::chrono::high_resolution_clock::now();

return after - before;
}

[[gnu::noinline]]
std::chrono::nanoseconds benchmark_async(size_t count, std::launch policy) {
std::vector<std::optional<std::future<void>>> results;
results.resize(count);

auto before = std::chrono::high_resolution_clock::now();

for (size_t i = 0; i < count; ++i)
results[i] = std::async(policy, work);

for (size_t i = 0; i < count; ++i)
results[i]->wait();

results.clear();

auto after = std::chrono::high_resolution_clock::now();

return after - before;
}

std::ostream& operator<<(std::ostream& stream, std::launch value)
{
if (value == std::launch::async)
return stream << "std::launch::async";
else if (value == std::launch::deferred)
return stream << "std::launch::deferred";
else
return stream << "std::launch::unknown";
}

// #define CONFIG_THREADS true
// #define CONFIG_ITERATIONS 10000
// #define CONFIG_POLICY std::launch::async

int main() {
std::cout << "Running benchmark:\n"
<< " threads? " << std::boolalpha << CONFIG_THREADS << '\n'
<< " iterations " << CONFIG_ITERATIONS << '\n'
<< " async policy " << CONFIG_POLICY << std::endl;

std::chrono::nanoseconds duration;
if (CONFIG_THREADS) {
duration = benchmark_threads(CONFIG_ITERATIONS);
} else {
duration = benchmark_async(CONFIG_ITERATIONS, CONFIG_POLICY);
}

std::cout << "Completed in " << duration.count() << "ns (" << std::chrono::duration_cast<std::chrono::milliseconds>(duration).count() << "ms)\n";
}
我已经按如下方式运行了基准测试:
$ g++ -Wall -Wextra -std=c++20 -pthread -O3 -DCONFIG_THREADS=false -DCONFIG_ITERATIONS=10000 -DCONFIG_POLICY=std::launch::deferred main.cpp -o main && ./main
Running benchmark:
threads? false
iterations 10000
async policy std::launch::deferred
Completed in 4783327ns (4ms)
$ g++ -Wall -Wextra -std=c++20 -pthread -O3 -DCONFIG_THREADS=false -DCONFIG_ITERATIONS=10000 -DCONFIG_POLICY=std::launch::async main.cpp -o main && ./main
Running benchmark:
threads? false
iterations 10000
async policy std::launch::async
Completed in 301756775ns (301ms)
$ g++ -Wall -Wextra -std=c++20 -pthread -O3 -DCONFIG_THREADS=true -DCONFIG_ITERATIONS=10000 -DCONFIG_POLICY=std::launch::deferred main.cpp -o main && ./main
Running benchmark:
threads? true
iterations 10000
async policy std::launch::deferred
Completed in 291284997ns (291ms)
$ g++ -Wall -Wextra -std=c++20 -pthread -O3 -DCONFIG_THREADS=true -DCONFIG_ITERATIONS=10000 -DCONFIG_POLICY=std::launch::async main.cpp -o main && ./main
Running benchmark:
threads? true
iterations 10000
async policy std::launch::async
Completed in 293539858ns (293ms)
我用 strace 重新运行了所有的基准测试附加并累积了系统调用:
# std::async with std::launch::async
1 access
2 arch_prctl
36 brk
10000 clone
6 close
1 execve
1 exit_group
10002 futex
10028 mmap
10009 mprotect
9998 munmap
7 newfstatat
6 openat
7 pread64
1 prlimit64
5 read
2 rt_sigaction
20001 rt_sigprocmask
1 set_robust_list
1 set_tid_address
5 write

# std::async with std::launch::deferred
1 access
2 arch_prctl
11 brk
6 close
1 execve
1 exit_group
10002 futex
28 mmap
9 mprotect
2 munmap
7 newfstatat
6 openat
7 pread64
1 prlimit64
5 read
2 rt_sigaction
1 rt_sigprocmask
1 set_robust_list
1 set_tid_address
5 write

# std::thread with std::launch::async
1 access
2 arch_prctl
27 brk
10000 clone
6 close
1 execve
1 exit_group
2 futex
10028 mmap
10009 mprotect
9998 munmap
7 newfstatat
6 openat
7 pread64
1 prlimit64
5 read
2 rt_sigaction
20001 rt_sigprocmask
1 set_robust_list
1 set_tid_address
5 write

# std::thread with std::launch::deferred
1 access
2 arch_prctl
27 brk
10000 clone
6 close
1 execve
1 exit_group
2 futex
10028 mmap
10009 mprotect
9998 munmap
7 newfstatat
6 openat
7 pread64
1 prlimit64
5 read
2 rt_sigaction
20001 rt_sigprocmask
1 set_robust_list
1 set_tid_address
5 write
我们观察到 std::async使用 std::launch::deferred 明显更快但其他一切似乎都没有那么重要。
我的结论是:
  • 当前的 libstdc++ 实现没有利用 std::async 的事实。每个任务不需要一个新线程。
  • 当前的 libstdc++ 实现在 std::async 中做了某种锁定。那个std::thread不做。
  • std::asyncstd::launch::deferred节省设置和销毁成本,并且在这种情况下要快得多。

  • 我的机器配置如下:
    $ uname -a
    Linux linux-2 5.12.1-arch1-1 #1 SMP PREEMPT Sun, 02 May 2021 12:43:58 +0000 x86_64 GNU/Linux
    $ g++ --version
    g++ (GCC) 10.2.0
    Copyright (C) 2020 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions. There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    $ lscpu # truncated
    Architecture: x86_64
    Byte Order: Little Endian
    CPU(s): 8
    Model name: Intel(R) Core(TM) i7-4770K CPU @ 3.50GHz
    原答案: std::thread是操作系统提供的线程对象的包装器,创建和销毁它们的成本非常高。 std::async类似,但任务和操作系统线程之间没有一对一的映射。这可以通过线程池来实现,其中线程被重用于多个任务。
    所以 std::async小任务多的话更好, std::thread如果您有一些长时间运行的任务,则更好。
    此外,如果您有真正需要并行发生的事情,那么 std::async可能不太适合。 ( std::thread 也不能做出这样的保证,但这是你能得到的最接近的。)

    也许澄清一下,在你的情况下 std::async节省了创建和销毁线程的开销。
    (取决于操作系统,你也可能因为运行大量线程而损失性能。操作系统可能有一个调度策略,它试图保证每个线程每隔一段时间执行一次,因此调度程序可以决定去给单个线程的处理时间更小,从而为线程之间的切换创造了更多的开销。)

    关于c++ - 为什么使用 std::async 的并发性比使用 std::thread 更快?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67034861/

    29 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com