C++ 为什么使用';如果';你能给我更好的表现吗?

C++ 为什么使用';如果';你能给我更好的表现吗?,c++,performance,visual-c++,x86,atomic,C++,Performance,Visual C++,X86,Atomic,当在VC++2015的发行版模式下编译以下程序时,优化设置为Ox(完全优化),我不知何故获得了更好的性能,即使有额外的条件检查 演示:(它没有演示性能差异,因为g++为两个版本生成几乎相同的代码) 运行此程序可以减少2的执行时间。。这违背了我的常识,因为2.每次都要检查if语句 在我的机器上,1.和2.的平均时间分别为105ms和86ms。演示还显示了一个差异,但只有5毫秒的差异仍然有利于2。;这是什么原因造成的 下面是完整的代码,它实际上给了我执行时间上的巨大差异。请注意,它实际上并不使用两

当在VC++2015的发行版模式下编译以下程序时,优化设置为Ox(完全优化),我不知何故获得了更好的性能,即使有额外的条件检查

演示:(它没有演示性能差异,因为g++为两个版本生成几乎相同的代码)

运行此程序可以减少2的执行时间。。这违背了我的常识,因为2.每次都要检查
if
语句

在我的机器上,1.2.的平均时间分别为105ms和86ms。演示还显示了一个差异,但只有5毫秒的差异仍然有利于2。;这是什么原因造成的


下面是完整的代码,它实际上给了我执行时间上的巨大差异。请注意,它实际上并不使用两个函数。我只是注释掉
operator++()
中的相关部分

2生成的程序集。

000000013FA110F0  lea         rcx,[rsp+38h]  
000000013FA110F5  call        std::chrono::steady_clock::now (013FA11000h)+100000000h  
000000013FA110FA  mov         eax,2160EC0h  
000000013FA110FF  nop  
000000013FA11100 loopv1: mov    ecx,1  
000000013FA11105  lock xadd   qword ptr [ac],rcx  
000000013FA1110C  sub         rax,1  
000000013FA11110  jne    loopv1 ;   main+70h (013FA11100h)  
000000013FA11112  lea         rcx,[rsp+40h]  
000000013FA11117  call        std::chrono::steady_clock::now (013FA11000h)+100000000h
long long measure_execution_time( F&& f, FArgs&&... fargs )
{
000000013F871230  mov         qword ptr [rsp+8],rbx  
000000013F871235  push        rdi  
000000013F871236  sub         rsp,50h  
000000013F87123A  mov         rdi,rcx  
000000013F87123D  mov         rbx,rdx  
    auto time_begin = Clock::now();
000000013F871240  lea         rcx,[rsp+70h]  
000000013F871245  call        std::chrono::steady_clock::now (013F871110h)  
    f( std::forward<FArgs>( fargs )... );
000000013F87124A  xor         r9d,r9d  
000000013F87124D  cmp         qword ptr [rbx],r9  
000000013F871250  jbe         measure_execution_time<std::chrono::steady_clock,std::chrono::duration<__int64,std::ratio<1,1000> >,<lambda_fb2a7610a6d36531125f2c739fce673b> & __ptr64,unsigned __int64 const & __ptr64>+41h (013F871271h)  
000000013F871252 loopv2: mov    rax,qword ptr [rdi]   ; top of the inner loop
000000013F871255  mov         r8d,1  
000000013F87125B  lock xadd   qword ptr [rax],r8  
000000013F871260  lea         rax,[r8+1]  
000000013F871264  test        rax,rax  
000000013F871267  je          measure_execution_time<std::chrono::steady_clock,std::chrono::duration<__int64,std::ratio<1,1000> >,<lambda_fb2a7610a6d36531125f2c739fce673b> & __ptr64,unsigned __int64 const & __ptr64>+7Bh (013F8712ABh)  
000000013F871269  inc         r9  
000000013F87126C  cmp         r9,qword ptr [rbx]   ; loop upper-bound in memory
000000013F87126F  jb  loopv2  ;         measure_execution_time<std::chrono::steady_clock,std::chrono::duration<__int64,std::ratio<1,1000> >,<lambda_fb2a7610a6d36531125f2c739fce673b> & __ptr64,unsigned __int64 const & __ptr64>+22h (013F871252h)  
    auto time_end = Clock::now();
000000013F871271  lea         rcx,[time_end]  
000000013F871276  call        std::chrono::steady_clock::now (013F871110h)
long-long-measure\u-execution\u-time(F&&F,FArgs&&F…FArgs)
{
0000000 13F871230 mov qword ptr[rsp+8],rbx
0000000 13F871235推送rdi
0000000 13F871236子rsp,50小时
0000000 13F87123A移动rdi,rcx
0000000 13F87123D mov rbx,rdx
自动计时开始=时钟::现在();
0000000 13F871240 lea rcx,[rsp+70小时]
0000000 13F871245调用标准::时钟::稳定时钟::现在(013F871110h)
f(标准:前进(法格斯)…);
0000000 13F87124A异或r9d,r9d
0000000 13F87124D cmp qword ptr[rbx],r9
0000000 13F871250 jbe测量执行时间+41小时(013F871271小时)
0000000 13F871252循环v2:mov-rax,qword-ptr[rdi];内部循环的顶部
0000000 13F871255 mov r8d,1
0000000 13F87125B锁xadd qword ptr[rax],r8
0000000 13F871260 lea rax,[r8+1]
0000000 13F871264测试rax,rax
0000000 13F871267 je测量执行时间+7小时(013F8712ABh)
0000000 13F871269股份有限公司r9
0000000 13F87126C cmp r9,qword ptr[rbx];内存中的循环上限
0000000 13F87126F jb loopv2;测量执行时间+22小时(013F871252小时)
自动时间=时钟::现在();
0000000 13F871271 lea rcx[时间结束]
0000000 13F871276调用标准::时钟::稳定时钟::现在(013F871110h)
玩得开心

g++-std=c++17-O3-Wall-pedantic-pthread main.cpp&./a.out 平均1:159毫秒 平均2:165毫秒 这里首先运行“平均2”测试


很明显,这有点像是在为下面的“平均值1”测试启动泵(准备东西).

编译器在检测到您的测试程序中永远不会满足if条件时应该不会有问题。因此,您正在计时的是优化中的随机性和其他奇怪效果的组合;例如,可能第一次循环迭代触发了页面错误,因为您尚未访问内存。

看起来像e两个成员函数内联,但生成的代码明显不同:

.L5:
        movq    $-1, %rdx
        lock xaddq      %rdx, (%rsp)
        testq   %rdx, %rdx
        je      .L14
        subq    $1, %rax
        jne     .L5
vs


我在fedora上使用g++5.1.1,您需要生成程序集(使用-S编译器标志)看看你的编译器在做什么。很难想象第一个版本运行得更快。

gcc的代码与MSVC不同。v1和v2的代码非常相似。请看包含
锁添加的循环。
。它们以相同的速度运行(在我的Sandybridge CPU上),仅在
lock add
吞吐量上受到限制(每19个周期一个,剩余大量执行资源用于处理其他指令。请参阅insn延迟/吞吐量表和一些不错的指南。)根据
ocperf.py
,gcc的v2以每周期0.17条指令、每周期0.61 uops(融合域)的速度运行

MSVC对v2做了一些奇怪的事情,但我仍然无法解释除了
lock xadd
吞吐量之外为什么会出现瓶颈,因为锁定的指令非常慢。我认为唯一重要的区别是v2使用了
lock xadd
的寄存器地址,而不是绝对地址

lock xadd   qword ptr [ac],rcx    ; v1: ac is a 32bit absolute address

lock xadd   qword ptr [rax],r8    ; v2: rax is a pointer (reloaded from memory every time through the loop)
Agner Fog的Microach文档没有详细说明
lock
ed指令如何影响周围指令的执行,例如,根据它们解码到的uop数量,它们占用执行端口的时间是否比预期的要长。他的指令表在Sandybridge之前甚至没有uop计数(可能是因为SnB引入了uop缓存,这使得uop的数量更加重要。)

MSVC的v2将指针留给原子变量和循环上限(
n
)在内存中,并在每次迭代中加载它们。不过,这不应该是个问题。它们在一级缓存中会保持热状态,并且每次迭代额外的两个加载UOP不应该是一个因素。我没有Nehalem可供测试,所以可能不值得在该循环中包装一些寄存器初始化代码来在我的SandyBridge上尝试它(我没有MSVC,甚至没有Windows电脑可供测试)。我见过一些情况,额外的内存操作实际上会将一些代码的速度提高一小部分,但这种情况似乎很奇怪

并不是说uop吞吐量应该接近一个因子,但根据IACA,循环的底部仍然是微观和宏观的,与Nehalem上的内存操作数进行比较和分支

cmp   r9,qword [rbx]   ; loop upper-bound in memory
jb    loopv2
IACA说,在Nehalem上,v2循环总共是12个uops。不过,我认为它不能正确解释锁定指令的有限吞吐量,因为它认为循环将以每3.2个时钟一次迭代的速度运行。它说v1是8个uops,应该以每3.0个时钟一次迭代的速度运行。因此IACA没有对CPU行为进行足够详细的建模这两个循环都应该从Nehalem上的28uop循环缓冲区运行,而不是前端在Nehalem的任何地方
.L3:
        lock addq       $1, (%rsp)
        subq    $1, %rax
        jne     .L3
lock xadd   qword ptr [ac],rcx    ; v1: ac is a 32bit absolute address

lock xadd   qword ptr [rax],r8    ; v2: rax is a pointer (reloaded from memory every time through the loop)
cmp   r9,qword [rbx]   ; loop upper-bound in memory
jb    loopv2