다음 코드는 Visual Studio에서 릴리스를 실행하고 Visual Studio 외부에서 릴리스를 실행할 때 다른 출력을 제공합니다. Visual Studio 2008을 사용하고 .NET 3.5를 대상으로합니다. .NET 3.5 SP1도 시도했습니다.
Visual Studio 외부에서 실행할 때 JIT가 시작됩니다. (a) C #과 관련하여 미묘한 부분이 있거나 (b) JIT에 실제로 오류가 있습니다. 나는 JIT가 잘못 될 수 있다고 의심하지만 다른 가능성이 부족합니다 …
Visual Studio에서 실행할 때 출력 :
0 0,
0 1,
1 0,
1 1,
Visual Studio 외부에서 릴리스를 실행할 때 출력 :
0 2,
0 2,
1 2,
1 2,
이유가 무엇입니까?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Test
{
struct IntVec
{
public int x;
public int y;
}
interface IDoSomething
{
void Do(IntVec o);
}
class DoSomething : IDoSomething
{
public void Do(IntVec o)
{
Console.WriteLine(o.x.ToString() + " " + o.y.ToString()+",");
}
}
class Program
{
static void Test(IDoSomething oDoesSomething)
{
IntVec oVec = new IntVec();
for (oVec.x = 0; oVec.x < 2; oVec.x++)
{
for (oVec.y = 0; oVec.y < 2; oVec.y++)
{
oDoesSomething.Do(oVec);
}
}
}
static void Main(string[] args)
{
Test(new DoSomething());
Console.ReadLine();
}
}
}
답변
JIT 최적화 프로그램 버그입니다. 내부 루프를 풀지 만 oVec.y 값을 올바르게 업데이트하지 않습니다.
for (oVec.x = 0; oVec.x < 2; oVec.x++) {
0000000a xor esi,esi ; oVec.x = 0
for (oVec.y = 0; oVec.y < 2; oVec.y++) {
0000000c mov edi,2 ; oVec.y = 2, WRONG!
oDoesSomething.Do(oVec);
00000011 push edi
00000012 push esi
00000013 mov ecx,ebx
00000015 call dword ptr ds:[00170210h] ; first unrolled call
0000001b push edi ; WRONG! does not increment oVec.y
0000001c push esi
0000001d mov ecx,ebx
0000001f call dword ptr ds:[00170210h] ; second unrolled call
for (oVec.x = 0; oVec.x < 2; oVec.x++) {
00000025 inc esi
00000026 cmp esi,2
00000029 jl 0000000C
oVec.y를 4로 증가 시키면 버그가 사라져서 너무 많은 호출이 풀립니다.
한 가지 해결 방법은 다음과 같습니다.
for (int x = 0; x < 2; x++) {
for (int y = 0; y < 2; y++) {
oDoesSomething.Do(new IntVec(x, y));
}
}
업데이트 : 2012 년 8 월에 다시 확인 된이 버그는 버전 4.0.30319 지터에서 수정되었습니다. 그러나 v2.0.50727 지터에는 여전히 존재합니다. 오랜만에 구버전에서는이 문제를 해결하지 못할 것 같습니다.
답변
나는 이것이 진정한 JIT 컴파일 버그에 있다고 생각합니다. 나는 그것을 Microsoft에보고하고 그들이 말하는 것을 볼 것입니다. 흥미롭게도 x64 JIT에는 동일한 문제가 없다는 것을 알았습니다.
다음은 x86 JIT에 대한 내용입니다.
// save context
00000000 push ebp
00000001 mov ebp,esp
00000003 push edi
00000004 push esi
00000005 push ebx
// put oDoesSomething pointer in ebx
00000006 mov ebx,ecx
// zero out edi, this will store oVec.y
00000008 xor edi,edi
// zero out esi, this will store oVec.x
0000000a xor esi,esi
// NOTE: the inner loop is unrolled here.
// set oVec.y to 2
0000000c mov edi,2
// call oDoesSomething.Do(oVec) -- y is always 2!?!
00000011 push edi
00000012 push esi
00000013 mov ecx,ebx
00000015 call dword ptr ds:[002F0010h]
// call oDoesSomething.Do(oVec) -- y is always 2?!?!
0000001b push edi
0000001c push esi
0000001d mov ecx,ebx
0000001f call dword ptr ds:[002F0010h]
// increment oVec.x
00000025 inc esi
// loop back to 0000000C if oVec.x < 2
00000026 cmp esi,2
00000029 jl 0000000C
// restore context and return
0000002b pop ebx
0000002c pop esi
0000002d pop edi
0000002e pop ebp
0000002f ret
이것은 나에게 좋지 않은 최적화처럼 보입니다 …
답변
코드를 새 콘솔 앱에 복사했습니다.
- 디버그 빌드
- 디버거와 디버거가없는 올바른 출력
- 릴리스 빌드로 전환
- 다시, 두 번 올바른 출력
- 새로운 x86 구성 생성 (X64 Windows 2008을 실행 중이며 ‘Any CPU’를 사용 중임)
- 디버그 빌드
- F5와 CTRL + F5 모두 올바른 출력을 얻었습니다.
- 릴리스 빌드
- 디버거가 연결된 올바른 출력
- 디버거 없음- 잘못된 출력을 얻음
따라서 코드를 잘못 생성하는 x86 JIT입니다. 루프 재 배열 등에 관한 원본 텍스트를 삭제했습니다. 여기에 대한 다른 답변은 x86에서 JIT가 루프를 잘못 풀고 있음을 확인했습니다.
문제를 해결하기 위해 IntVec의 선언을 클래스로 변경할 수 있으며 모든 취향에서 작동합니다.
이것이 MS Connect에서 진행되어야한다고 생각하십시오 ….
Microsoft에 -1!