나는 그 뒤에 꽤 괜찮은 프로그래밍 조언이이 일을 쓸 그렇게 빨리 코드를 작성에 대한 관심있는 C#을 프로그래머에게 문제한다고합니다. 일반적으로 마이크로 벤치 마크 사용에 대한주의는 15 % 이하의 차이는 현대 CPU 코어에서 코드 실행 속도가 예측할 수 없기 때문에 일반적으로 통계적으로 유의하지 않습니다. 거기에없는 것을 측정 할 확률을 줄이려면 캐싱 효과를 제거하고 코드 정렬 효과를 제거 할 수 있도록 테스트를 바꾸기 위해 적어도 10 번 테스트를 반복해야합니다.
하지만 실제로 본 것은 정적 메서드를 호출하는 대리자는 실제로 느립니다. 이 효과는 x86 코드에서 매우 작지만 x64 코드에서 현저하게 나빠집니다. 프로젝트> 속성> 빌드 탭> 32 비트 및 플랫폼 대상 선호 설정을 사용하여 둘 다 시도해보십시오.
왜 속도가 느린 지 알면 지터가 생성하는 기계 코드를 살펴 봐야합니다. 델리게이트의 경우 해당 코드는 이며 매우입니다. 디버그> Windows> 디스 어셈블리를 사용하여 코드를 보면이 코드가 표시되지 않습니다. 그리고 코드를 단계별로 처리 할 수도 없으며 관리되는 디버거는 코드를 숨기도록 작성되었으며 코드를 표시하는 것을 완전히 거부합니다. "Visual"을 Visual Studio에 다시 넣을 수있는 기술을 설명해야합니다.
"스텁"에 대해 조금 이야기해야합니다. 스텁은 지터가 생성하는 코드 외에도 CLR이 동적으로 생성하는 기계 코드를 조금 더 복잡하게합니다. 스텁은 인터페이스를 구현하는 데 사용되며 클래스의 메소드 테이블에있는 메소드의 순서가 인터페이스 메소드의 순서와 일치하지 않아도되는 유연성을 제공합니다. 그리고 그들은이 질문의 주제 인 대의원들에게 중요합니다. 스텁은 JIT (just-in-time) 컴파일에 중요합니다. 스텁의 초기 코드는 Jitter가 시작될 때 컴파일 된 메서드를 얻기 위해 진입 점을 가리 킵니다. 그 후 스텁이 바뀌고 jitted 타겟 메서드가 호출됩니다. 정적 메서드 호출을 느리게 만드는 스텁은 정적 메서드 대상의 스텁이 인스턴스 메서드의 스텁보다 더 정교합니다.
는 자신의 코드를 보여주기 위해 강제로 디버거를 다툰해야 스텁을 참조하십시오. 몇 가지 설정이 필요합니다. 먼저 도구> 옵션> 디버깅> 일반을 사용하십시오. "내 코드 만 허용"확인란을 선택 취소하고 "JIT 최적화 무시"확인란의 선택을 취소하십시오. VS2015를 사용하고 "관리되는 호환 모드 사용"을 선택하면 VS2015 디버거는 매우 버그가 있으며 이러한 종류의 디버깅을 위해 심각하게 사용됩니다.이 옵션은 VS2010 관리 디버거 엔진을 강제로 사용하여 해결 방법을 제공합니다. 릴리스 구성으로 전환하십시오. 그런 다음 Project> Properties> Debug에서 "Enable native code debugging"체크 박스를 체크한다. 그리고 Project> Properties> Build를 선택하고 "32 비트 선호"확인란을 선택 해제하고 "Platform target"은 AnyCPU 여야합니다.
Run() 메서드에 중단 점을 설정하십시오. 중단 점이 최적화 된 코드에서 정확하지 않을 수 있으므로주의하십시오. 메서드 헤더에 설정하는 것이 가장 좋습니다. 적중하면, Debug> Windows> Disassembly를 사용하여 지터가 생성 된 기계 코드를보십시오.
funcResult += _func.Invoke(1d, 2d);
0000001a mov rax,qword ptr [rsi+8] ; rax = _func
0000001e mov rcx,qword ptr [rax+8] ; rcx = _func._methodBase (?)
00000022 vmovsd xmm2,qword ptr [0000000000000070h] ; arg3 = 2d
0000002b vmovsd xmm1,qword ptr [0000000000000078h] ; arg2 = 1d
00000034 call qword ptr [rax+18h] ; call stub
64 비트 메서드 호출이 처음 4 개 인수를 전달합니다 대리자 호출 호출이 하 스웰 코어에 다음과 같습니다, 아직 AVX를 지원하지 않는 구형 프로세서가 있다면 당신이 볼 일치하지 않을 수 있습니다 레지스터에 추가 인수가 스택 (여기서는 아님)을 통해 전달됩니다. XMM 레지스터는 인수가 부동 소수점이기 때문에 여기에서 사용됩니다. 이 시점에서 지터는 메서드가 정적인지 인스턴스인지 여부를 아직 알 수 없으며이 코드가 실제로 실행될 때까지는 알 수 없습니다. 차이를 숨기는 것은 스텁의 일입니다. 그것은 인스턴스 메소드라고 가정합니다. 그래서 arg2와 arg3에 주석을 달았습니다.
CALL 명령에 두 번째로 중단 점을 설정하면 (스텁이 더 이상 지터를 가리 키지 않은 이후에) 더 자세히 볼 수 있습니다. 이 작업은 수작업으로 수행해야합니다. 디버그> Windows> 레지스터를 사용하고 RAX 레지스터의 값을 복사하십시오. 디버그> Windows> 메모리> Memory1 그리고 붙여 넣기 값을 앞에 "0x"를 넣고 0x18을 추가하십시오. 해당 창을 마우스 오른쪽 버튼으로 클릭하고 "8 바이트 정수"를 선택하고 첫 번째 표시된 값을 복사하십시오. 그것이 스텁 코드의 주소입니다.
이제 트릭을 관리하는 디버깅 엔진이 여전히 사용되고 있으며 스텁 코드를 볼 수 없습니다. 관리되지 않는 디버깅 엔진이 제어되도록 모드 전환을 강제해야합니다. 디버그> Windows> 호출 스택을 사용하고 RtlUserThreadStart와 같이 하단의 메서드 호출을 두 번 클릭합니다. 디버거가 엔진을 전환하도록합니다. 이제 가서 주소 상자에 주소를 붙여 넣을 수 있습니다. 주소 앞에 "0x"를 넣으십시오. Out 스텁 코드가 튀어 나옵니다.
00007FFCE66D0100 jmp 00007FFCE66D0E40
매우 간단한 것으로, 위임 대상 메소드로 바로 점프합니다. 이것은 빠른 코드가 될 것입니다. 지터는 인스턴스 메서드에서 올바르게 추측되었으며 대리자 개체는 RCX 레지스터에 this
인수를 제공하므로 특별한 작업을 수행 할 필요가 없습니다.
두 번째 테스트를 계속 진행하고 똑같은 작업을 수행하여 인스턴스 호출에 대한 스텁을 확인합니다. 이제 스텁은 매우 다르다 :
000001FE559F0850 mov rax,rsp ; ?
000001FE559F0853 mov r11,rcx ; r11 = _func (?)
000001FE559F0856 movaps xmm0,xmm1 ; shuffle arg3 into right register
000001FE559F0859 movaps xmm1,xmm2 ; shuffle arg2 into right register
000001FE559F085C mov r10,qword ptr [r11+20h] ; r10 = _func.Method
000001FE559F0860 add r11,20h ; ?
000001FE559F0864 jmp r10 ; jump to _func.Method
코드는 마이크로 소프트가 아마 여기에 더 나은 일을 할 수있는 최적의 조금 남았습니다 및 아니며, 내가 제대로 주석 100 % 확실하지 않다. 불필요한 mov rax, rsp 명령어는 4 개 이상의 인수가있는 메소드의 스텁에만 관련이 있습니다. 왜 add 명령어가 필요한지 모르겠다. 중요한 것은 XMM 레지스터 이동과 관련하여 정적 메서드에 this
인수가 없으므로 다시 이동해야한다는 것입니다. 코드를 느리게 만드는 것은 이러한 개편 요구 사항입니다.
당신은 86 지터와 같은 운동을 할 수있는, 정적 메소드 스텁은 지금과 같이 보인다 :
04F905B4 mov eax,ecx
04F905B6 add eax,10h
04F905B9 jmp dword ptr [eax] ; jump to _func.Method
32 비트 코드에서 고통을하지 않는 이유는 64 비트 스텁,보다 훨씬 간단 경기 침체는 거의 마찬가지입니다. 매우 다른 이유 중 하나는 32 비트 코드가 FPU 스택에 부동 소수점을 전달하므로 재구성 할 필요가 없다는 것입니다. 정수 또는 객체 인수를 사용할 때 반드시 더 빠를 필요는 없습니다.
매우 신비하고, 나는 아직 모든 사람을 잠들게하지 않았 으면 좋겠다. 일부 주석을 잘못 읽었을 수도 있습니다. 스텁 및 CLR이 객체 멤버를 위임하여 가능한 빨리 코드를 작성하는 방법을 완전히 이해하지 못합니다. 그러나 확실히 괜찮은 프로그래밍 조언이 여기 있습니다. 인스턴스 메서드를 위임 대상으로 사용하면 static
이 이 아닌 최적화가됩니다.
중복? http://stackoverflow.com/questions/2082735/performance-of-calling-delegates-vs-methods –
@Dan이 질문은 직접 메서드 호출을 비교하는 방법에 대한 다른 하나의 중복 있다고 생각하지 않습니다. 대 대표. 제 경우에는 대의원들의 공연에만 관심이 있습니다. –
'나는 정말로 마이크로 최적화를 찾고 있지 않다. '그런 다음 왜 마이크로 최적화에 관한 질문을 하는가? – Servy