【问题标题】:Why is the same C program sometimes much faster为什么同一个 C 程序有时会快得多
【发布时间】:2016-10-21 03:11:14
【问题描述】:

好吧,这将是一个没有细节的问题,因为我不知道如何更好地解释。对不起。我有一个内存密集型 C 程序(很多指针)。我有一个源代码,它是我用 gcc -O2 编译的。我在 Ubuntu Linux 上。在程序开始和结束时,会调用 clock() 来测量经过的时间。此外,我正在使用 time 命令来检查时间。问题是同一个程序有时在不改变任何东西的情况下会快 20% 以上(或慢)。

$ date; time ./cudd-example-8queens
pon jun 20 00:49:05 CEST 2016
CPU TIME = 6.46
real    0m6.475s
user    0m6.405s
sys 0m0.067s

$ date; time ./cudd-example-8queens
pon jun 20 00:49:16 CEST 2016
CPU TIME = 8.03
real    0m8.051s
user    0m7.995s
sys 0m0.048s

$ date; time ./cudd-example-8queens
pon jun 20 00:49:33 CEST 2016
CPU TIME = 6.48
real    0m6.490s
user    0m6.445s
sys 0m0.040s

$ date; time ./cudd-example-8queens
pon jun 20 00:49:42 CEST 2016
CPU TIME = 6.45
real    0m6.469s
user    0m6.424s
sys 0m0.040s

$ date; time ./cudd-example-8queens
pon jun 20 00:49:56 CEST 2016
CPU TIME = 8.04
real    0m8.058s
user    0m7.982s
sys 0m0.068s

我的问题是:如何解释这种差异,即这额外的 1.5 秒(有时甚至更糟)花在了哪里?它必须是具有内存访问权限的东西,但是如何检查呢?

编辑:我已经安装了 perf,这里有两个结果(我已经更新它们以显示从 cpupower 获得的信息)。关于目标,我正在比较科学算法,这对我来说很重要,例如一个比另一个快 10%。

$ date; cpupower -c all frequency-info -f; perf stat -B ./cudd-example-8queens 
pon jun 20 12:39:21 CEST 2016
analyzing CPU 0:
1300000
analyzing CPU 1:
1300000
analyzing CPU 2:
1300000
analyzing CPU 3:
1300000
clock() TIME = 6.70
clock_gettime() TIME = 6.70

 Performance counter stats for './cudd-example-8queens':

       6705,796274 task-clock (msec)         #    0,999 CPUs utilized          
               104 context-switches          #    0,016 K/sec                  
                 3 cpu-migrations            #    0,000 K/sec                  
             30861 page-faults               #    0,005 M/sec                  
       17295862806 cycles                    #    2,579 GHz                    
   <not supported> stalled-cycles-frontend 
   <not supported> stalled-cycles-backend  
        7361712951 instructions              #    0,43  insns per cycle        
        1228059232 branches                  #  183,134 M/sec                  
          64491733 branch-misses             #    5,25% of all branches        

       6,709414218 seconds time elapsed

$ date; cpupower -c all frequency-info -f; perf stat -B ./cudd-example-8queens 
pon jun 20 12:39:30 CEST 2016
analyzing CPU 0:
1300000
analyzing CPU 1:
1300000
analyzing CPU 2:
1300000
analyzing CPU 3:
1300000
clock() TIME = 8.43
clock_gettime() TIME = 8.43

 Performance counter stats for './cudd-example-8queens':

       8441,824238 task-clock (msec)         #    0,999 CPUs utilized          
               145 context-switches          #    0,017 K/sec                  
                 3 cpu-migrations            #    0,000 K/sec                  
             30863 page-faults               #    0,004 M/sec                  
       13958245339 cycles                    #    1,653 GHz                    
   <not supported> stalled-cycles-frontend 
   <not supported> stalled-cycles-backend  
        7360082448 instructions              #    0,53  insns per cycle        
        1227803521 branches                  #  145,443 M/sec                  
          64517871 branch-misses             #    5,25% of all branches        

       8,446645648 seconds time elapsed

EDIT2:我的英特尔 NUC 有英特尔酷睿 i5-4250U CPU。因此,使用“cpupower frequency-set”的建议很有希望,但不幸的是它没有任何帮助。此外,我使用“clock()”和“clock_gettime(CLOCK_PROCESS_CPUTIME_ID)”得到完全相同的结果,这些结果也被 perf 的“task-clock (msec)”确认。

【问题讨论】:

  • 使用像 perf 这样的体面分析工具,否则您将无法了解全部内容。
  • @l'L'l 所说的……另外,您处于多用户、多任务环境中。为单个任务计时可能几乎没有意义。 ..然后有例如/dev/urandom 如果你要使用它的状态...
  • 在测量程序的速度时,elapsed time 几乎没有用,因为它包括了其他进程运行的时间。更好的方法是调用:clock_gettime(),参数为:CLOCK_PROCESS_CPUTIME_ID,它获取当前进程的进程时间。注意:始终检查返回值-1,因为这表明调用失败。注意:程序的第一行应该是:#define _POSIX_C_SOURCE (199309L)
  • 还要确保通过sudo cpupower frequency-set -g performance禁用CPU频率缩放
  • 谢谢,但不幸的是 clock_gettime() 和 cpupower 没有帮助(见 EDIT2)。

标签: c linux performance memory-management profiling


【解决方案1】:

现代 CPU 具有动态变化的频率,您不仅应该测量挂钟时间(天文时间),还应该测量 CPU 周期数。 perf stat(实际上,perf stat -e task-clock,cycles,instructions 就足够了)显示您的意思是 CPU 核心频率,而程序在cycles 的行中运行,如果有 cpu-clock/task-clock 事件来测量壁时间(周期除以时间获得GHz):

 #### cycles                    #    1,653 GHz    

 #### cycles                    #    2,579 GHz

这是 Intel Turbo Boost (2),https://en.wikipedia.org/wiki/Intel_Turbo_Boost(AMD 有 https://en.wikipedia.org/wiki/AMD_Turbo_Core)。两者都非常快,所以cpupower -c all frequency-info运行时,实际频率较低(1.3);但是当您的程序负载很高时,CPU 会在 几微秒内将其频率调整到更高的水平。

有时可以在 BIOS 中将其关闭以获得更统一的测量结果:http://www.intel.com/content/www/us/en/support/processors/000005641.html

如何启用或禁用英特尔® 睿频加速技术? - 英特尔® 睿频加速技术通常默认启用。您只能通过 BIOS 中的开关禁用和启用该技术。没有其他用户可控制的设置。

或者你可以尝试一些神奇的 MSR 写入(不要将随机值写入随机 msr regs,它可能会破坏某些东西,或者挂起 PC):https://askubuntu.com/questions/619875/disabling-intel-turbo-boost-in-ubuntu Maythux 的回答:“wrmsr -pC 0x1a0 0x4000850089

来自perf stat 的其他行:7361-7360 百万条指令,1228-1227 百万个分支 6400 万个分支错误预测表明程序是相同的并且执行了相同的代码(没有外部随机)。您也可以尝试perf stat -d(更好的是从stat -d中选择一些工作硬件事件并在perf stat -e cpu-clock,....中手动列出它们)来检查运行之间的缓存事件差异。

【讨论】:

  • 在 BIOS 中启用 Turbo 后,您可以通过一些 MSR 写入魔法从 Linux 将其关闭 - askubuntu.com/questions/619875/…
  • 这可能就是我要找的答案!禁用涡轮增压(使用'wrmsr')和频率缩放(使用'cpupower'),现在我得到了更值得信赖的结果。在众多测试程序中,最好的运行是 9.81s,而最差的是 10.74s。在这两种情况下,我的周期 = 1,293 GHz。对于最快的运行,我的分支 = 124,906 M/sec 和 L1-dcache-loads = 214,174 M/sec,而对于最慢的运行,我的分支 = 114,328 M/sec 和 L1-dcache-loads = 194,013 M/sec。我将这种差异归因于程序本身(例如哈希表)而不是系统。
  • 我还尝试使用命令 'echo "0" > /sys/devices/system/cpu/cpufreq/boost' 禁用 Turbo Boost,因为我系统上的频率缩放驱动程序是 acpi-cpufreq。不成功。
  • meolic,不确定是否可以使用 echo "0" &gt; /sys/devices/system/cpu/cpufreq/boost 禁用增强功能。这里有该文件的文档:kernel.org/doc/Documentation/cpu-freq/boost.txt。 Intel 的 CPU 有几个频率缩放选项:speedstep EIST 和某些版本的 TubroBoost(ark.intel.com/products/75028/… - 增强的 Intel SpeedStep 技术是的)。而且你的 CPU 是移动的,它的基本频率很低:1.3。您可以始终在perf stat 下运行测试以检查真实频率...
【解决方案2】:

操作系统同时运行多个任务并对其进行管理,它会从一个任务到另一个任务进行上下文切换。因此,每次更改的不是您的代码的时间,而是在您的程序执行之间被操作系统调用的其他程序。

【讨论】:

  • 我需要一个证据来证明这种说法。而且我认为从 perf 获得的结果不支持这一点。我错了吗?
猜你喜欢
  • 1970-01-01
  • 2020-01-22
  • 2016-10-15
  • 2011-06-18
  • 2018-07-22
  • 2012-09-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多