인텔 컴파일러를 사용하는 Windows와 Linux의 성능 차이 : 어셈블리 살펴보기
Windows와 Linux (x86-64)에서 프로그램을 실행하고 있습니다. 동일한 옵션으로 동일한 컴파일러 (Intel Parallel Studio XE 2017)로 컴파일되었으며 Windows 버전은 Linux 버전보다 3 배 빠릅니다. 범인은 std::erf
두 경우 모두에 대해 Intel 수학 라이브러리에서 해결되는 호출입니다 (기본적으로 Windows에서는 동적으로 연결되고 Linux에서는 정적으로 연결되지만 Linux에서 동적 연결을 사용하면 동일한 성능이 제공됨).
다음은 문제를 재현하는 간단한 프로그램입니다.
#include <cmath>
#include <cstdio>
int main() {
int n = 100000000;
float sum = 1.0f;
for (int k = 0; k < n; k++) {
sum += std::erf(sum);
}
std::printf("%7.2f\n", sum);
}
vTune을 사용하여이 프로그램을 프로파일 링 할 때 Windows와 Linux 버전간에 어셈블리가 약간 다릅니다. 다음은 Windows의 호출 사이트 (루프)입니다.
Block 3:
"vmovaps xmm0, xmm6"
call 0x1400023e0 <erff>
Block 4:
inc ebx
"vaddss xmm6, xmm6, xmm0"
"cmp ebx, 0x5f5e100"
jl 0x14000103f <Block 3>
그리고 Windows에서 호출되는 erf 함수의 시작
Block 1:
push rbp
"sub rsp, 0x40"
"lea rbp, ptr [rsp+0x20]"
"lea rcx, ptr [rip-0xa6c81]"
"movd edx, xmm0"
"movups xmmword ptr [rbp+0x10], xmm6"
"movss dword ptr [rbp+0x30], xmm0"
"mov eax, edx"
"and edx, 0x7fffffff"
"and eax, 0x80000000"
"add eax, 0x3f800000"
"mov dword ptr [rbp], eax"
"movss xmm6, dword ptr [rbp]"
"cmp edx, 0x7f800000"
...
Linux에서는 코드가 약간 다릅니다. 호출 사이트는 다음과 같습니다.
Block 3
"vmovaps %xmm1, %xmm0"
"vmovssl %xmm1, (%rsp)"
callq 0x400bc0 <erff>
Block 4
inc %r12d
"vmovssl (%rsp), %xmm1"
"vaddss %xmm0, %xmm1, %xmm1" <-------- hotspot here
"cmp $0x5f5e100, %r12d"
jl 0x400b6b <Block 3>
호출 된 함수 (erf)의 시작은 다음과 같습니다.
"movd %xmm0, %edx"
"movssl %xmm0, -0x10(%rsp)" <-------- hotspot here
"mov %edx, %eax"
"and $0x7fffffff, %edx"
"and $0x80000000, %eax"
"add $0x3f800000, %eax"
"movl %eax, -0x18(%rsp)"
"movssl -0x18(%rsp), %xmm0"
"cmp $0x7f800000, %edx"
jnl 0x400dac <Block 8>
...
나는 Linux에서 시간이 손실되는 두 가지 지점을 보여주었습니다.
누구든지 2 코드의 차이점과 Linux 버전이 3 배 느린 이유를 설명 할만큼 어셈블리를 충분히 이해하고 있습니까?
두 경우 모두 인수와 결과는 Windows 및 GNU / Linux의 각 호출 규칙에 따라 레지스터 에서만 전달 됩니다 .
GNU / Linux 변형에서는 xmm1
합계를 누적하는 데 사용됩니다. 호출 차단 레지스터 (또는 호출자 저장)이기 때문에 각 호출에서 호출자의 스택 프레임에 저장 (및 복원)됩니다.
Windows 변형에서는 xmm6
합계를 누적하는 데 사용됩니다. 이 레지스터는 Windows 호출 규칙 ( GNU / Linux에서는 아님)에 피 호출자에 저장됩니다 .
요약하면, GNU / Linux 버전은 xmm0
(피 호출자 [1])와 xmm1
(호출자에서 ) 모두 저장 / 복원하는 반면 Windows 버전은 xmm6
(피 호출자 에서만) 저장 / 복원합니다 .
[1] std::errf
이유를 알아 내야합니다.
Using Visual Studio 2015, Win 7 64 bit mode, I find the following code for some of the paths used in erf() (not all paths shown). Each path involves up to 8 (maybe more for other paths) constants read from memory, so a single store / load to save a register seems unlikely to result in a 3x speed differential between Linux and Windows. As far for save / restores, this example saves and restores xmm6 and xmm7. As for the time, the program in the original post takes about 0.86 seconds on an Intel 3770K (3.5ghz cpu) (VS2015 / Win 7 64 bit). Update - I later determined the overhead for a save and restore of a xmm register is about 0.03 seconds in the case of the programs 10^8 loops (about 3 nanoseconds per loop).
000007FEEE25CF90 mov rax,rsp
000007FEEE25CF93 movss dword ptr [rax+8],xmm0
000007FEEE25CF98 sub rsp,48h
000007FEEE25CF9C movaps xmmword ptr [rax-18h],xmm6
000007FEEE25CFA0 lea rcx,[rax+8]
000007FEEE25CFA4 movaps xmmword ptr [rax-28h],xmm7
000007FEEE25CFA8 movaps xmm6,xmm0
000007FEEE25CFAB call 000007FEEE266370
000007FEEE25CFB0 movsx ecx,ax
000007FEEE25CFB3 test ecx,ecx
000007FEEE25CFB5 je 000007FEEE25D0AF
000007FEEE25CFBB sub ecx,1
000007FEEE25CFBE je 000007FEEE25D08F
000007FEEE25CFC4 cmp ecx,1
000007FEEE25CFC7 je 000007FEEE25D0AF
000007FEEE25CFCD xorps xmm7,xmm7
000007FEEE25CFD0 movaps xmm2,xmm6
000007FEEE25CFD3 comiss xmm7,xmm6
000007FEEE25CFD6 jbe 000007FEEE25CFDF
000007FEEE25CFD8 xorps xmm2,xmmword ptr [7FEEE2991E0h]
000007FEEE25CFDF movss xmm0,dword ptr [7FEEE298E50h]
000007FEEE25CFE7 comiss xmm0,xmm2
000007FEEE25CFEA jbe 000007FEEE25D053
000007FEEE25CFEC movaps xmm2,xmm6
000007FEEE25CFEF mulss xmm2,xmm6
000007FEEE25CFF3 movaps xmm0,xmm2
000007FEEE25CFF6 movaps xmm1,xmm2
000007FEEE25CFF9 mulss xmm0,dword ptr [7FEEE298B34h]
000007FEEE25D001 mulss xmm1,dword ptr [7FEEE298B5Ch]
000007FEEE25D009 addss xmm0,dword ptr [7FEEE298B8Ch]
000007FEEE25D011 addss xmm1,dword ptr [7FEEE298B9Ch]
000007FEEE25D019 mulss xmm0,xmm2
000007FEEE25D01D mulss xmm1,xmm2
000007FEEE25D021 addss xmm0,dword ptr [7FEEE298BB8h]
000007FEEE25D029 addss xmm1,dword ptr [7FEEE298C88h]
000007FEEE25D031 mulss xmm0,xmm2
000007FEEE25D035 mulss xmm1,xmm2
000007FEEE25D039 addss xmm0,dword ptr [7FEEE298DC8h]
000007FEEE25D041 addss xmm1,dword ptr [7FEEE298D8Ch]
000007FEEE25D049 divss xmm0,xmm1
000007FEEE25D04D mulss xmm0,xmm6
000007FEEE25D051 jmp 000007FEEE25D0B2
000007FEEE25D053 movss xmm1,dword ptr [7FEEE299028h]
000007FEEE25D05B comiss xmm1,xmm2
000007FEEE25D05E jbe 000007FEEE25D076
000007FEEE25D060 movaps xmm0,xmm2
000007FEEE25D063 call 000007FEEE25CF04
000007FEEE25D068 movss xmm1,dword ptr [7FEEE298D8Ch]
000007FEEE25D070 subss xmm1,xmm0
000007FEEE25D074 jmp 000007FEEE25D07E
000007FEEE25D076 movss xmm1,dword ptr [7FEEE298D8Ch]
000007FEEE25D07E comiss xmm7,xmm6
000007FEEE25D081 jbe 000007FEEE25D08A
000007FEEE25D083 xorps xmm1,xmmword ptr [7FEEE2991E0h]
000007FEEE25D08A movaps xmm0,xmm1
000007FEEE25D08D jmp 000007FEEE25D0B2
000007FEEE25D08F mov eax,8000h
000007FEEE25D094 test word ptr [rsp+52h],ax
000007FEEE25D099 je 000007FEEE25D0A5
000007FEEE25D09B movss xmm0,dword ptr [7FEEE2990DCh]
000007FEEE25D0A3 jmp 000007FEEE25D0B2
000007FEEE25D0A5 movss xmm0,dword ptr [7FEEE298D8Ch]
000007FEEE25D0AD jmp 000007FEEE25D0B2
000007FEEE25D0AF movaps xmm0,xmm6
000007FEEE25D0B2 movaps xmm6,xmmword ptr [rsp+30h]
000007FEEE25D0B7 movaps xmm7,xmmword ptr [rsp+20h]
000007FEEE25D0BC add rsp,48h
000007FEEE25D0C0 ret
'IT Share you' 카테고리의 다른 글
Xcode 4.2는 시뮬레이터를 중지 한 후 매번 main.m으로 점프합니다. (0) | 2020.11.18 |
---|---|
기본 스타일을 기반으로 스타일을 만드는 방법은 무엇입니까? (0) | 2020.11.18 |
git : 각 원격에 대해 다른 .gitignore 파일이 있습니다. (0) | 2020.11.18 |
오류가있을 때 LaTeX 컴파일을 수동으로 중단하지 마십시오. (0) | 2020.11.18 |
ASP.NET MVC를위한 최상의 리포지토리 패턴 (0) | 2020.11.18 |