C++ 如何启用Visual Studio C++;分支优化?
我有两个功能,下面的功能在执行方面应该是相同的。唯一的区别是,在第二个函数funcB()中,我将(decobj==NULL)上的分支移到了始终为NULL的循环之外。我认为编译器可以很容易地优化funcA(),使其具有与FuncB()相同的执行时间,但事实并非如此,在执行时间上存在很大差异:C++ 如何启用Visual Studio C++;分支优化?,c++,visual-c++,compiler-optimization,micro-optimization,branch-prediction,C++,Visual C++,Compiler Optimization,Micro Optimization,Branch Prediction,我有两个功能,下面的功能在执行方面应该是相同的。唯一的区别是,在第二个函数funcB()中,我将(decobj==NULL)上的分支移到了始终为NULL的循环之外。我认为编译器可以很容易地优化funcA(),使其具有与FuncB()相同的执行时间,但事实并非如此,在执行时间上存在很大差异: FuncA took: 3644530 uS FuncB took: 1664598 uS FuncB took: 1626528 uS FuncA took: 3108780 uS Finished! 我
FuncA took: 3644530 uS
FuncB took: 1664598 uS
FuncB took: 1626528 uS
FuncA took: 3108780 uS
Finished!
我使用的是Visual Studio 2019(预览版),编译后的x64已启用完全优化:
有人知道如何让Visual Studio优化funcA(),使其与funcB()一样快吗
#包括
#包括
使用名称空间std;
结构解码{
整数解码(整数i,整数j){
返回i+j;
}
};
静态解码robj*decobj=nullptr;
静态常数int迭代次数=3000;
void funcA(){
自动启动=时钟::高分辨率时钟::现在();
对于(int rep=0;rep解码(i,j):i-j;
如果(a>99999){
你到底有什么CPU?(例如i7-6700k Skylake)。代码对齐效果可能会对“decobj为空”造成一些不好的影响循环,例如,为一个循环跨越32B的边界,否则它将是1次迭代/时钟。或者-JCC勘误表在微码中引入了一个性能缺陷,编译器可能还不能解决。你是否查看了汇编输出,以了解编译器在做什么?@PeterCordes我正在核心i5-8350U CPU上运行这个。我的想法无论是使用“decobj!=nullptr”语句还是不使用它,编译器都应该意识到“if(decobj!=nullptr)”永远不会被执行,因此永远不会为它生成代码。我也将这一行添加到funcA()中,但令人惊讶的是它减慢了funcB()而不是加快了funcA()。编译器应该能够在该行存在的情况下轻松优化这两个函数。应该能够在没有该行的情况下进行优化,但肯定是使用该行。是的,您希望这对MSVC来说也是显而易见的,尤其是对于最近的版本,但您必须检查asm以确保。例如,将其安装到或在本地询问编译器输出asm而不是机器代码。(或编译后反汇编)。首先要弄清楚它为什么慢,不管是一个微体系结构的凹坑,还是一堆工作,然后再弄清楚这些工作是否应该被优化掉。Clang对这两种方法都有相同的性能,因为它生成相同的程序集…你到底有什么CPU?(例如i7-6700k Skylake).Code alignment effects可能对“decobj为NULL”做了一些不幸的事情循环,例如,为一个循环跨越32B的边界,否则它将是1次迭代/时钟。或者-JCC勘误表在微码中引入了一个性能缺陷,编译器可能还不能解决。你是否查看了汇编输出,以了解编译器在做什么?@PeterCordes我正在核心i5-8350U CPU上运行这个。我的想法无论是使用“decobj!=nullptr”语句还是不使用它,编译器都应该意识到“if(decobj!=nullptr)”永远不会被执行,因此永远不会为它生成代码。我也将这一行添加到funcA()中,但令人惊讶的是它减慢了funcB()而不是加快了funcA()。编译器应该能够在该行存在的情况下轻松优化这两个函数。应该能够在没有该行的情况下进行优化,但肯定是使用该行。是的,您希望这对MSVC来说也是显而易见的,尤其是对于最近的版本,但您必须检查asm以确保。例如,将其安装到或在本地询问编译器输出asm而不是机器代码。(或编译后反汇编)。首要任务是弄清楚它为什么慢,是一个微体系结构的凹坑,还是一堆工作,然后看看这些工作是否应该被优化掉。Clang对这两种方法都有相同的性能,因为它生成相同的程序集。。。
#include <iostream>
#include <chrono>
using namespace std;
struct DecoderObj {
int decode(int i, int j) {
return i + j;
}
};
static DecoderObj* decobj = nullptr;
static const int iterations = 3000;
void funcA() {
auto start = chrono::high_resolution_clock::now();
for (int rep = 0; rep < iterations; rep++) {
for (int i = 0; i < 999; i++) {
for (int j = 0; j < 999; j++) {
int a = decobj ? decobj->decode(i, j) : i - j;
if (a > 99999) {
cout << "This should never print";
return;
}
}
}
}
auto end = chrono::high_resolution_clock::now();
cout << "FuncA took: " << chrono::duration_cast<chrono::microseconds>(end - start).count() << " uS" << endl;
}
void funcB() {
auto start = chrono::high_resolution_clock::now();
for (int rep = 0; rep < iterations; rep++) {
if (decobj != nullptr) {
for (int i = 0; i < 999; i++) {
for (int j = 0; j < 999; j++) {
int a = decobj->decode(i, j);
if (a > 99999) {
cout << "This should never print";
return;
}
}
}
}
else { //decobj is NULL
for (int i = 0; i < 999; i++) {
for (int j = 0; j < 999; j++) {
int a = i - j;
if (a > 99999) {
cout << "This should never print";
return;
}
}
}
}
}
auto end = chrono::high_resolution_clock::now();
cout << "FuncB took: " << chrono::duration_cast<chrono::microseconds>(end - start).count() << " uS" << endl;
}
int main() {
funcA();
funcB();
funcB();
funcA();
std::cout << "Finished!\n";
}
; 17 : for (int rep = 0; rep < iterations; rep++) {
mov r9, QWORD PTR ?decobj@@3PEAUDecoderObj@@EA
xor r10d, r10d
npad 6
$LL4@funcA:
; 18 : for (int i = 0; i < 999; i++) {
xor r8d, r8d
npad 13
$LL7@funcA:
; 19 : for (int j = 0; j < 999; j++) {
xor eax, eax
mov edx, r8d
$LL10@funcA:
; 20 : int a = decobj ? decobj->decode(i, j) : i - j;
lea ecx, DWORD PTR [rax+r8]
test r9, r9
jne SHORT $LN14@funcA
mov ecx, edx
$LN14@funcA:
; 21 : if (a > 99999) {
cmp ecx, 99999 ; 0001869fH
jg SHORT $LN20@funcA
; 19 : for (int j = 0; j < 999; j++) {
inc eax
dec edx
cmp eax, 999 ; 000003e7H
jl SHORT $LL10@funcA
; 18 : for (int i = 0; i < 999; i++) {
inc r8d
cmp r8d, 999 ; 000003e7H
jl SHORT $LL7@funcA
; 16 : //auto start = chrono::high_resolution_clock::now();
; 17 : for (int rep = 0; rep < iterations; rep++) {
inc r10d
cmp r10d, 3000 ; 00000bb8H
jl SHORT $LL4@funcA
; 23 : return;
; 24 : }
; 25 : }
; 26 : }
; 27 : }
; 28 : //auto end = chrono::high_resolution_clock::now();
; 29 : //cout << "FuncA took: " << chrono::duration_cast<chrono::microseconds>(end - start).count() << " uS" << endl;
; 30 : }
ret 0
$LN20@funcA:
; 22 : cout << "This should never print";
mov rcx, QWORD PTR __imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A
lea rdx, OFFSET FLAT:??_C@_0BI@DPJNIIKO@This?5should?5never?5print@
jmp ??$?6U?$char_traits@D@std@@@std@@YAAEAV?$basic_ostream@DU?$char_traits@D@std@@@0@AEAV10@PEBD@Z ; std::operator<<<std::char_traits<char> >
?funcA@@YAXXZ ENDP ; funcA
_TEXT SEGMENT
?funcB@@YAXXZ PROC ; funcB, COMDAT
; 33 : //auto start = chrono::high_resolution_clock::now();
; 34 : for (int rep = 0; rep < iterations; rep++) {
mov r9, QWORD PTR ?decobj@@3PEAUDecoderObj@@EA
xor r8d, r8d
npad 6
$LL4@funcB:
; 35 : if (decobj != nullptr) {
xor edx, edx
test r9, r9
je SHORT $LL13@funcB
npad 9
$LL7@funcB:
; 37 : for (int j = 0; j < 999; j++) {
xor eax, eax
$LL10@funcB:
; 38 : int a = decobj->decode(i, j);
; 39 : if (a > 99999) {
lea ecx, DWORD PTR [rax+rdx]
cmp ecx, 99999 ; 0001869fH
jg SHORT $LN30@funcB
; 37 : for (int j = 0; j < 999; j++) {
inc eax
cmp eax, 999 ; 000003e7H
jl SHORT $LL10@funcB
; 36 : for (int i = 0; i < 999; i++) {
inc edx
cmp edx, 999 ; 000003e7H
jl SHORT $LL7@funcB
; 40 : cout << "This should never print";
; 41 : return;
; 42 : }
; 43 : }
; 44 : }
; 45 : }
jmp SHORT $LN2@funcB
$LL13@funcB:
; 48 : for (int j = 0; j < 999; j++) {
xor ecx, ecx
mov eax, edx
$LL16@funcB:
; 49 : int a = i - j;
; 50 : if (a > 99999) {
cmp eax, 99999 ; 0001869fH
jg SHORT $LN30@funcB
; 48 : for (int j = 0; j < 999; j++) {
inc ecx
dec eax
cmp ecx, 999 ; 000003e7H
jl SHORT $LL16@funcB
; 46 : else { //decobj is NULL
; 47 : for (int i = 0; i < 999; i++) {
inc edx
cmp edx, 999 ; 000003e7H
jl SHORT $LL13@funcB
$LN2@funcB:
; 33 : //auto start = chrono::high_resolution_clock::now();
; 34 : for (int rep = 0; rep < iterations; rep++) {
inc r8d
cmp r8d, 3000 ; 00000bb8H
jge SHORT $LN3@funcB
jmp SHORT $LL4@funcB
$LN30@funcB:
; 51 : cout << "This should never print";
; 52 : return;
; 53 : }
; 54 : }
; 55 : }
; 56 : }
; 57 : }
; 58 : // auto end = chrono::high_resolution_clock::now();
; 59 : //cout << "FuncB took: " << chrono::duration_cast<chrono::microseconds>(end - start).count() << " uS" << endl;
; 60 : }
mov rcx, QWORD PTR __imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A
lea rdx, OFFSET FLAT:??_C@_0BI@DPJNIIKO@This?5should?5never?5print@
jmp ??$?6U?$char_traits@D@std@@@std@@YAAEAV?$basic_ostream@DU?$char_traits@D@std@@@0@AEAV10@PEBD@Z ; std::operator<<<std::char_traits<char> >
$LN3@funcB:
ret 0
?funcB@@YAXXZ ENDP ; funcB
_TEXT ENDS