当前位置: 首页 > 知识库问答 >
问题:

衡量C/C性能的困难

卫景明
2023-03-14

我写了一段C代码来展示关于优化和分支预测的讨论中的一个观点。然后我注意到比我预期的更多样化的结果。我的目标是用C和C之间的通用子集编写它,这两种语言都符合标准,并且相当可移植。它在不同的Windows PC上进行了测试:

#include <stdio.h>
#include <time.h>

/// @return - time difference between start and stop in milliseconds
int ms_elapsed( clock_t start, clock_t stop )
{
    return (int)( 1000.0 * ( stop - start ) / CLOCKS_PER_SEC );
}

int const Billion = 1000000000;
/// & with numbers up to Billion gives 0, 0, 2, 2 repeating pattern 
int const Pattern_0_0_2_2 = 0x40000002; 

/// @return - half of Billion  
int unpredictableIfs()
{
    int sum = 0;
    for ( int i = 0; i < Billion; ++i )
    {
        // true, true, false, false ...
        if ( ( i & Pattern_0_0_2_2 ) == 0 )
        {
            ++sum;
        }
    }
    return sum;
}

/// @return - half of Billion  
int noIfs()
{
    int sum = 0;
    for ( int i = 0; i < Billion; ++i )
    {
        // 1, 1, 0, 0 ...
        sum += ( i & Pattern_0_0_2_2 ) == 0;
    }
    return sum;
}

int main()
{
    clock_t volatile start;
    clock_t volatile stop;
    int volatile sum;
    printf( "Puzzling measurements:\n" );

    start = clock();
    sum = unpredictableIfs();
    stop = clock();
    printf( "Unpredictable ifs took %d msec; answer was %d\n"
          , ms_elapsed(start, stop), sum );

    start = clock();
    sum = unpredictableIfs();
    stop = clock();
    printf( "Unpredictable ifs took %d msec; answer was %d\n"
          , ms_elapsed(start, stop), sum );

    start = clock();
    sum = noIfs();
    stop = clock();
    printf( "Same without ifs took %d msec; answer was %d\n"
          , ms_elapsed(start, stop), sum );

    start = clock();
    sum = unpredictableIfs();
    stop = clock();
    printf( "Unpredictable ifs took %d msec; answer was %d\n"
          , ms_elapsed(start, stop), sum );
}

用VS2010编制/英特尔酷睿2、WinXP的O2优化结果:

Puzzling measurements:
Unpredictable ifs took 1344 msec; answer was 500000000
Unpredictable ifs took 1016 msec; answer was 500000000
Same without ifs took 1031 msec; answer was 500000000
Unpredictable ifs took 4797 msec; answer was 500000000

编辑:编译器的完整开关:

/Zi/no logo/W3/WX-/O2/Oi/Oy-/GL/D " WIN32 "/D " NDEBUG "/D " _ CONSOLE "/D " _ UNICODE "/D " UNICODE "/Gm-/EHsc/GS/Gy/Fp:precise/Zc:wchar _ t/Zc:for scope/Fp " Release \ trying . PCH "/Fa " Release \ "/Fo " Release \ "/Fd " Release \ VC 100 . pdb "/Gd/analyze-/error report:queue

其他人发布了这样的…用MinGW编译,g 4.71,-O1优化英特尔酷睿2,WinXP结果:

Puzzling measurements:
Unpredictable ifs took 1656 msec; answer was 500000000
Unpredictable ifs took 0 msec; answer was 500000000
Same without ifs took 1969 msec; answer was 500000000
Unpredictable ifs took 0 msec; answer was 500000000

他还发布了-O3优化的结果:

Puzzling measurements:
Unpredictable ifs took 1890 msec; answer was 500000000
Unpredictable ifs took 2516 msec; answer was 500000000
Same without ifs took 1422 msec; answer was 500000000
Unpredictable ifs took 2516 msec; answer was 500000000

现在我有个问题。这是怎么回事?

更具体地说…一个固定的函数怎么能占用如此不同的时间?我的代码有问题吗?英特尔处理器有什么棘手的问题吗?编译器是否在做一些奇怪的事情?这是因为64位处理器上运行的32位代码吗?

感谢关注!

编辑:我接受g -O1只是在另外两个调用中重用返回值。我也承认g -O2和g -O3有缺陷,没有优化。测量速度的显著差异(450%!!!)好像还是很神秘。

我查看了VS2010生成的代码的反汇编。它确实内联了不可预测的如果3次。内联代码非常相似;html" target="_blank">循环是相同的。它没有内联noIfs。它确实滚动一点。它在一次迭代中需要4个步骤。noIfs 计算像是在不可预测时编写的如果使用 jne 跳过增量。

共有3个答案

上官华池
2023-03-14

关于Windows上的结果范围(从1016毫秒到4797毫秒):您应该知道MSVC中的时钟()返回经过的墙上时间。该标准说clock()应该返回进程花费的CPU时间的近似值,其他实现在这方面做得更好。

鉴于MSVC给出了墙时间,如果您的流程在运行测试的一次迭代时被抢占,它可能会给出一个大得多的结果,即使代码在大约相同的CPU时间内运行。

还请注意,许多Windows PC上的<code>clock()</code>分辨率非常差,通常为11-19毫秒。您已经完成了足够多的迭代,这仅为1%左右,因此我不认为这是差异的一部分,但在编写基准测试时要注意这一点。我知道您正在追求可移植性,但如果您需要在Windows上进行更好的测量,您可以使用<code>QueryPerformanceCounter</code>这几乎肯定会给您带来更好的分辨率,尽管它仍然只是经过的墙时间。

更新:在我了解到一个案例的长时间运行持续发生之后,我启动了VS2010并重现了结果。我通常在某些运行中获得大约1000毫秒,对于其他运行,我得到750毫秒,对于莫名其妙的运行,我得到5000毫秒。

观察:

  1. 在所有情况下,unpredictableIfs()代码都是内联的
  2. 删除noIfs()代码没有任何影响(因此长时间不是该代码的副作用)
  3. 将线程关联设置为单个处理器没有效果
  4. 5000毫秒的时间总是后来的情况。我注意到,后面的实例在循环开始之前有一条额外的html" target="_blank">指令:<code>lea ecx,[ecx]。我不明白为什么会有5倍的差异。除此之外,早期和后期的实例是相同的代码
  5. 从<code>开始leas)。
  6. 从<code>sum
  7. 如果删除所有<code>易失性edi作为,而不是ecx

我不确定从这一切中得出什么结论,除了易失性对MSVC具有不可预测的性能影响,因此您应该仅在必要时应用它。

更新2:我看到与使用易失性相关的一致的运行时差异,即使反汇编几乎相同。

具有挥发性:

Puzzling measurements:
Unpredictable ifs took 643 msec; answer was 500000000
Unpredictable ifs took 1248 msec; answer was 500000000
Unpredictable ifs took 605 msec; answer was 500000000
Unpredictable ifs took 4611 msec; answer was 500000000
Unpredictable ifs took 4706 msec; answer was 500000000
Unpredictable ifs took 4516 msec; answer was 500000000
Unpredictable ifs took 4382 msec; answer was 500000000

每个实例的拆解如下所示:

    start = clock();
010D1015  mov         esi,dword ptr [__imp__clock (10D20A0h)]  
010D101B  add         esp,4  
010D101E  call        esi  
010D1020  mov         dword ptr [start],eax  
    sum = unpredictableIfs();
010D1023  xor         ecx,ecx  
010D1025  xor         eax,eax  
010D1027  test        eax,40000002h  
010D102C  jne         main+2Fh (10D102Fh)  
010D102E  inc         ecx  
010D102F  inc         eax  
010D1030  cmp         eax,3B9ACA00h  
010D1035  jl          main+27h (10D1027h)  
010D1037  mov         dword ptr [sum],ecx  
    stop = clock();
010D103A  call        esi  
010D103C  mov         dword ptr [stop],eax  

无易失性:

Puzzling measurements:
Unpredictable ifs took 644 msec; answer was 500000000
Unpredictable ifs took 624 msec; answer was 500000000
Unpredictable ifs took 624 msec; answer was 500000000
Unpredictable ifs took 605 msec; answer was 500000000
Unpredictable ifs took 599 msec; answer was 500000000
Unpredictable ifs took 599 msec; answer was 500000000
Unpredictable ifs took 599 msec; answer was 500000000

    start = clock();
00321014  mov         esi,dword ptr [__imp__clock (3220A0h)]  
0032101A  add         esp,4  
0032101D  call        esi  
0032101F  mov         dword ptr [start],eax  
    sum = unpredictableIfs();
00321022  xor         ebx,ebx  
00321024  xor         eax,eax  
00321026  test        eax,40000002h  
0032102B  jne         main+2Eh (32102Eh)  
0032102D  inc         ebx  
0032102E  inc         eax  
0032102F  cmp         eax,3B9ACA00h  
00321034  jl          main+26h (321026h)  
    stop = clock();
00321036  call        esi
// The only optimization I see is here, where eax isn't explicitly stored
// in stop but is instead immediately used to compute the value for the
// printf that follows.

除了寄存器选择,我看不出有什么显著差异。

司寇书
2023-03-14

右,查看64位Linux上gcc的汇编程序代码,第一种情况是使用-O1,函数<code>UnpredictableIfs</code>确实只调用了一次,结果被重用。

不可预测如果:

movl    %eax, %ecx
andl    $1073741826, %ecx
cmpl    $1, %ecx
adcl    $0, %edx
addl    $1, %eax

NOIF:

xorl    %ecx, %ecx
testl   $1073741826, %eax
sete    %cl
addl    $1, %eax
addl    %ecx, %edx

如您所见,它并不完全相同,但它做了非常相似的事情。

洪经义
2023-03-14

对于< code>-O1,gcc-4.7.1只调用一次< code > uncompactleifs 并重新获得结果,因为它认为这是一个纯函数,所以每次调用的结果都是相同的。(我是这样做的,通过查看生成的程序集来验证。)

使用更高的优化级别时,函数是内联的,编译器不再识别它是相同的代码,因此每次在源代码中出现函数调用时都会运行它。

除此之外,我的gcc-4.7.1在使用-O1-O2时最好处理unpretableIfs(除了重用问题,两者都产生相同的代码),而noIfs-O3中处理得更好。然而,相同代码的不同运行之间的时间在这里是一致的-等于或相差10毫秒(时钟的颗粒度),因此我不知道是什么原因导致您为-O3报告的unpretableIfs时间大相径庭。

使用 -O2 时,不可预测的循环如果使用 -O1 生成的代码相同(寄存器交换除外):

.L12:
    movl    %eax, %ecx
    andl    $1073741826, %ecx
    cmpl    $1, %ecx
    adcl    $0, %edx
    addl    $1, %eax
    cmpl    $1000000000, %eax
    jne .L12

对于< code>noIfs,情况类似:

.L15:
    xorl    %ecx, %ecx
    testl   $1073741826, %eax
    sete    %cl
    addl    $1, %eax
    addl    %ecx, %edx
    cmpl    $1000000000, %eax
    jne .L15

它在哪里

.L7:
    testl   $1073741826, %edx
    sete    %cl
    movzbl  %cl, %ecx
    addl    %ecx, %eax
    addl    $1, %edx
    cmpl    $1000000000, %edx
    jne .L7

使用-O1。两个循环的运行时间相似,但<code>不可预测的ifs<code>要快一点。

使用 -O3不可预测的循环如果变得更糟,

.L14:
    leal    1(%rdx), %ecx
    testl   $1073741826, %eax
    cmove   %ecx, %edx
    addl    $1, %eax
    cmpl    $1000000000, %eax
    jne     .L14

对于< code>noIfs(包括此处的设置代码),它变得更好:

    pxor    %xmm2, %xmm2
    movq    %rax, 32(%rsp)
    movdqa  .LC3(%rip), %xmm6
    xorl    %eax, %eax
    movdqa  .LC2(%rip), %xmm1
    movdqa  %xmm2, %xmm3
    movdqa  .LC4(%rip), %xmm5
    movdqa  .LC5(%rip), %xmm4
    .p2align 4,,10
    .p2align 3
.L18:
    movdqa  %xmm1, %xmm0
    addl    $1, %eax
    paddd   %xmm6, %xmm1
    cmpl    $250000000, %eax
    pand    %xmm5, %xmm0
    pcmpeqd %xmm3, %xmm0
    pand    %xmm4, %xmm0
    paddd   %xmm0, %xmm2
    jne .L18

.LC2:
    .long   0
    .long   1
    .long   2
    .long   3
    .align 16
.LC3:
    .long   4
    .long   4
    .long   4
    .long   4
    .align 16
.LC4:
    .long   1073741826
    .long   1073741826
    .long   1073741826
    .long   1073741826
    .align 16
.LC5:
    .long   1
    .long   1
    .long   1
    .long   1

它一次计算四次迭代,因此,noIfs的运行速度几乎是当时的四倍。

 类似资料:
  • 问题内容: 谁能推荐一种工具来衡量Web App的UI级别的性能? 我并不是在专门进行负载测试,我们的应用一次最多只能容纳5个用户,我希望能够自动执行并重新运行的指标是诸如页面加载时间,从单击按钮到事件发生,滑出疼痛的反应时间等。我们正在分别衡量API性能,希望能够确定速度下降是API问题还是可以在UI中解决的问题。 理想情况下,我可以将某些东西与Selenium结合使用,单击一个按钮,然后确定预

  • 目标 在图像处理中,由于每秒要处理大量操作,因此必须使代码不仅提供正确的解决方案,而且还必须以最快的方式提供。因此,在本章中,你将学习 衡量代码的性能。 一些提高代码性能的技巧。 你将看到以下功能:cv.getTickCount,cv.getTickFrequency等。 除了OpenCV,Python还提供了一个模块time,这有助于衡量执行时间。另一个模块profile有助于获取有关代码的详细

  • 本文向大家介绍golang、python、php、c++、c、java、Nodejs性能对比,包括了golang、python、php、c++、c、java、Nodejs性能对比的使用技巧和注意事项,需要的朋友参考一下   本人在PHP/C++/Go/Py时,突发奇想,想把最近主流的编程语言性能作个简单的比较, 至于怎么比,还是不得不用神奇的斐波那契算法。可能是比较常用或好玩吧。   好了,tal

  • 下面的代码是我的程序的一部分。它不执行,它说C++禁止将字符串常量转换为字符*。我如何修复这个错误?

  • variable是某个值的占位符。 所有变量都有一些与之关联的类型,它们表示可以分配的值的“类型”。 C提供了丰富的变量 - 类型 格式字符串 描述 char %c 字符类型变量(ASCII值) int %d 机器最自然的整数大小。 float %f 单精度浮点值。 double %e 双精度浮点值。 void - N/A - 表示缺少类型。 C中的字符( char )变量 Character(

  • 我有以下向量: 现在我想按奇指数对向量进行字典排序(如果奇指数相等,则按偶数指数)。使得排序向量“vec”为: 我知道d::排序将完全排序“vec”。是否可以使用d::排序来选择性地对向量进行排序。d::lower_bound类似。是否可以仅使用奇数索引来查找lower_bound。 我想要与对向量相同的效果。出于效率原因,我不将vec存储为对向量。