首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么这个简单的lambda在std::线程内部的运行速度总是快于gcc 4.9.2的主函数?

为什么这个简单的lambda在std::线程内部的运行速度总是快于gcc 4.9.2的主函数?
EN

Stack Overflow用户
提问于 2017-05-30 14:00:58
回答 1查看 318关注 0票数 8

下面的代码段接受一个命令行参数,该参数表示要生成的线程数,以同时运行简单的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编译。

代码语言:javascript
复制
#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所在的逻辑分支,迭代次数是先验的。我把相关的行改为

代码语言:javascript
复制
const auto n_iterations = 1000000000ul / (n_threads > 0u ? n_threads : n_threads + 1);

我还删除了外部for循环,并执行了10次,并提到了所有ScopedNanoTimer。这些更改现在反映在上面的片段中。

我使用上面所示的标志进行编译,并在Debian linux的工作站上执行了几次,内核版本为3.16.39-1+Debian 8u 2,处理器Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz,四核。所有其他程序都关闭了,cpu节流/英特尔速度步/涡轮增压被关闭,cpu调控器策略被设置为“性能”。互联网连接被关闭。

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

下面的测量使我相信问题在gcc-4.9.2次优优化中,而与上下文切换和测量质量差无关。这么说,即使是戈德波特的编译器探险家也没有清楚地告诉我gcc在做什么,所以我认为这个问题没有答案。

用g++-4.9.2测量Time+context开关

代码语言:javascript
复制
~$ 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

++-4.0测量Time+context开关

代码语言:javascript
复制
~$ 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
EN

回答 1

Stack Overflow用户

发布于 2017-05-30 18:54:54

我想你可能是一个糟糕的测试样本的受害者。我试图再现这种行为,在对每个选项运行了大约10次之后,我发现我收到的时间的方差相对较高。我使用/usr/bin/time -v运行了更多的测试,发现程序的执行时间与程序所经历的非自愿上下文切换的数量有很好的关联。

代码语言:javascript
复制
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秒以下的时间是在低负荷时收集的。

从逻辑上讲,我明白为什么调度线程的循环运行得更慢。创建线程的开销相对于循环的操作而言很高,循环只会增加一个数字。这将导致运行程序所需的用户时间增加.问题是,与整个循环的执行时间相比,用户时间的增加可能是微不足道的。在程序生命的过程中,您只创建了10个额外的线程,在这些线程中执行计算与简单地主要执行这些计算几乎没有什么区别。在整个程序过程中,您正在执行数十亿其他操作,这些操作隐藏了用户时间的增加。如果您真的想对线程的创建时间进行基准测试,那么您将编写一个程序来创建大量线程,并且不会做太多其他工作。您还应该小心地在具有尽可能少的后台进程的环境中运行这些基准测试。

这可能不是问题的全部,但我相信仍须加以考虑。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/44264397

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档