gpt4 book ai didi

c++ - 为什么这个简单的 lambda 在 std::thread 中始终比在 gcc 4.9.2 的 main 函数中运行得更快?

转载 作者:可可西里 更新时间:2023-11-01 17:58:15 27 4
gpt4 key购买 nike

以下代码片段采用一个命令行参数,该参数表示要生成的线程数以同时运行一个简单的 for 循环。

如果传递的参数为 0,则不会生成 std::thread

在 gcc 4.9.2 上,./snippet 0./snippet 1 平均花费 10%,即生成一个 std 的版本: :thread 执行循环比仅在 main 中执行循环的版本更快。

有人知道这是怎么回事吗? clang-4 根本没有表现出这种行为(带有一个 std::thread 的版本较慢),gcc 6.2 具有带有一个 std::thread 的版本运行得稍微慢一点更快(以十次试验中花费的最少时间作为测量值)。

这是片段:ScopedNanoTimer 只是一个简单的 RAII 计时器。我正在使用 -g -O3 -pthread -std=c++11 进行编译。

#include <thread>
#include <vector>

int main(int argc, char** argv) {

// setup
if (argc < 2)
return 1;
const unsigned n_threads = std::atoi(argv[1]);
const auto n_iterations = 1000000000ul / (n_threads > 0u ? n_threads : n_threads + 1);

// define workload
auto task = [n_iterations]() {
volatile auto sum = 0ul;
for (auto i = 0ul; i < n_iterations; ++i) ++sum;
};

// time and print
if (n_threads == 0) {
task();
} else {
std::vector<std::thread> threads;
for (auto i = 0u; i < n_threads; ++i) threads.emplace_back(task);
for (auto &thread : threads) thread.join();
}
return 0;
}

编辑

按照评论中的建议,我试图让编译器混淆这样一个事实,即对于 n_threads == 0 的逻辑分支,迭代次数是先验已知的。我将相关行更改为

const auto n_iterations = 1000000000ul / (n_threads > 0u ? n_threads : n_threads + 1);

我还删除了执行 10 次的外部 for 循环和所有提及的 ScopedNanoTimer。这些更改现在反射(reflect)在上面的代码片段中。

我使用上面指示的标志编译并在装有 Debian Linux、内核版本 3.16.39-1+deb8u2 和处理器 Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz、quad 的工作站上执行了几次-核。关闭所有其他程序,关闭 cpu 节流/intel speed-step/turbo-boost 并将 cpu 调控器策略设置为“性能”。互联网连接已关闭。

趋势始终是,使用 gcc-4.9.2 编译没有 std::thread 的版本比生成一个线程的版本快大约 10%。相反,clang-4 具有相反的(预期的)行为。

以下测量结果使我确信问题出在 gcc-4.9.2 次优优化中,与上下文切换或测量质量差无关。话虽如此,即使是 Godbolt 的编译器资源管理器也没有清楚地向我展示 gcc 在做什么,所以我认为这个问题没有得到回答。

使用 g++-4.9.2 进行时间+上下文切换测量

~$ g++ -std=c++11 -pthread -g -O3 snippet.cpp -o snippet_gcc
~$ for i in $(seq 1 10); do /usr/bin/time -v 2>&1 ./snippet_gcc 0 | egrep '((wall clock)|(switch))'; done
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:02.08
Voluntary context switches: 1
Involuntary context switches: 6
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:02.08
Voluntary context switches: 1
Involuntary context switches: 5
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:02.08
Voluntary context switches: 1
Involuntary context switches: 7
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:02.08
Voluntary context switches: 1
Involuntary context switches: 6
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.90
Voluntary context switches: 1
Involuntary context switches: 3
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:02.08
Voluntary context switches: 1
Involuntary context switches: 6
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:02.08
Voluntary context switches: 1
Involuntary context switches: 5
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:02.08
Voluntary context switches: 1
Involuntary context switches: 6
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:02.08
Voluntary context switches: 1
Involuntary context switches: 2
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:02.08
Voluntary context switches: 1
Involuntary context switches: 4
~$ for i in $(seq 1 10); do /usr/bin/time -v 2>&1 ./snippet_gcc 1 | egrep '((wall clock)|(switch))'; done
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.79
Voluntary context switches: 2
Involuntary context switches: 4
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.95
Voluntary context switches: 2
Involuntary context switches: 4
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.81
Voluntary context switches: 2
Involuntary context switches: 4
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.87
Voluntary context switches: 2
Involuntary context switches: 5
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.87
Voluntary context switches: 2
Involuntary context switches: 4
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.97
Voluntary context switches: 2
Involuntary context switches: 3
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.87
Voluntary context switches: 2
Involuntary context switches: 4
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.85
Voluntary context switches: 2
Involuntary context switches: 4
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.87
Voluntary context switches: 2
Involuntary context switches: 6
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.95
Voluntary context switches: 2
Involuntary context switches: 5

使用 clang++-4.0 进行时间+上下文切换测量

~$ clang++ -std=c++11 -pthread -g -O3 snippet.cpp -o snippet_clang
~$ for i in $(seq 1 10); do /usr/bin/time -v 2>&1 ./snippet_clang 0 | egrep '((wall clock)|(switch))'; done
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.67
Voluntary context switches: 1
Involuntary context switches: 6
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.67
Voluntary context switches: 1
Involuntary context switches: 4
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.67
Voluntary context switches: 1
Involuntary context switches: 5
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.67
Voluntary context switches: 1
Involuntary context switches: 4
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.67
Voluntary context switches: 1
Involuntary context switches: 7
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.67
Voluntary context switches: 1
Involuntary context switches: 3
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.67
Voluntary context switches: 1
Involuntary context switches: 4
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.67
Voluntary context switches: 1
Involuntary context switches: 4
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.67
Voluntary context switches: 1
Involuntary context switches: 3
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.67
Voluntary context switches: 1
Involuntary context switches: 4
~$ for i in $(seq 1 10); do /usr/bin/time -v 2>&1 ./snippet_clang 1 | egrep '((wall clock)|(switch))'; done
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.67
Voluntary context switches: 2
Involuntary context switches: 6
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.67
Voluntary context switches: 2
Involuntary context switches: 6
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.67
Voluntary context switches: 2
Involuntary context switches: 5
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.67
Voluntary context switches: 2
Involuntary context switches: 4
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.67
Voluntary context switches: 2
Involuntary context switches: 4
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.67
Voluntary context switches: 2
Involuntary context switches: 5
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.67
Voluntary context switches: 2
Involuntary context switches: 2
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.67
Voluntary context switches: 2
Involuntary context switches: 3
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.67
Voluntary context switches: 2
Involuntary context switches: 4
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:01.67
Voluntary context switches: 2
Involuntary context switches: 7

最佳答案

我认为您可能是不良测试样本的受害者。我试图重现此行为,并且在为每个选项运行每个选项 10 次左右之后,我发现我收到的时间具有相对较高的差异。我使用/usr/bin/time -v 运行了更多测试,发现程序的执行时间与程序经历的非自愿上下文切换的次数密切相关。

Option 0: No threads
time, context switches
20.32, 1806
20.09, 2139
21.01, 1916
21.13, 1873
21.15, 1847
18.67, 1617
19.06, 1692
17.94, 1546
21.40, 1867
18.64, 1629

Option 1: Threads
time, context switches
19.68, 1750
19.60, 1740
19.35, 1783
19.60, 1726
19.95, 1823
20.42, 1800
19.54, 1745
19.40, 1699
19.36, 1703

我认为您可能只是在操作系统的可变工作负载期间运行了基准测试。从我上面的时间数据可以看出,超过 20 秒的时间都是在操作系统高负载期间收集的。同样,在低负载期间收集的时间少于 19 秒。

从逻辑上讲,我明白为什么分派(dispatch)线程的循环应该运行得更慢。创建线程的开销相对于循环的操作来说是很高的,它只是简单地增加一个数字。这会导致运行程序所需的用户时间增加。问题是,与整个循环的执行时间相比,这种用户时间的增加可能可以忽略不计。在程序的整个生命周期中,您只会创建 10 个额外的线程,并且在这些线程中执行计算与在主线程中简单地执行这些计算应该没有什么区别。在整个程序过程中,您正在执行数十亿次其他操作,这些操作隐藏了用户时间的增加。如果您真的想对线程的创建时间进行基准测试,您可以编写一个程序来创建大量线程,而不会做太多其他事情。您还应该小心地在后台进程尽可能少的环境中运行此类基准测试。

这可能不是问题的全部,但我认为考虑仍然很重要。

关于c++ - 为什么这个简单的 lambda 在 std::thread 中始终比在 gcc 4.9.2 的 main 函数中运行得更快?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44264397/

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