다음 코드를 고려하십시오.
private static void Main(string[] args)
{
var ar = new double[]
{
100
};
FillTo(ref ar, 5);
Console.WriteLine(string.Join(",", ar.Select(a => a.ToString()).ToArray()));
}
public static void FillTo(ref double[] dd, int N)
{
if (dd.Length >= N)
return;
double[] Old = dd;
double d = double.NaN;
if (Old.Length > 0)
d = Old[0];
dd = new double[N];
for (int i = 0; i < Old.Length; i++)
{
dd[N - Old.Length + i] = Old[i];
}
for (int i = 0; i < N - Old.Length; i++)
dd[i] = d;
}
디버그 모드의 결과는 100,100,100,100,100입니다. 그러나 릴리스 모드에서는 100,100,100,100,0입니다.
무슨 일이야?
.NET Framework 4.7.1 및 .NET Core 2.0.0을 사용하여 테스트되었습니다.
답변
이것은 JIT 버그 인 것 같습니다. 나는 다음으로 테스트했습니다.
// ... existing code unchanged
for (int i = 0; i < N - Old.Length; i++)
{
// Console.WriteLine(i); // <== comment/uncomment this line
dd[i] = d;
}
Console.WriteLine(i)
수정 사항을 추가합니다 . 유일한 IL 변경은 다음과 같습니다.
// ...
L_0040: ldc.i4.0
L_0041: stloc.3
L_0042: br.s L_004d
L_0044: ldarg.0
L_0045: ldind.ref
L_0046: ldloc.3
L_0047: ldloc.1
L_0048: stelem.r8
L_0049: ldloc.3
L_004a: ldc.i4.1
L_004b: add
L_004c: stloc.3
L_004d: ldloc.3
L_004e: ldarg.1
L_004f: ldloc.0
L_0050: ldlen
L_0051: conv.i4
L_0052: sub
L_0053: blt.s L_0044
L_0055: ret
vs
// ...
L_0040: ldc.i4.0
L_0041: stloc.3
L_0042: br.s L_0053
L_0044: ldloc.3
L_0045: call void [System.Console]System.Console::WriteLine(int32)
L_004a: ldarg.0
L_004b: ldind.ref
L_004c: ldloc.3
L_004d: ldloc.1
L_004e: stelem.r8
L_004f: ldloc.3
L_0050: ldc.i4.1
L_0051: add
L_0052: stloc.3
L_0053: ldloc.3
L_0054: ldarg.1
L_0055: ldloc.0
L_0056: ldlen
L_0057: conv.i4
L_0058: sub
L_0059: blt.s L_0044
L_005b: ret
정확히 옳게 보입니다 (유일한 차이점은 여분의 ldloc.3
및 call void [System.Console]System.Console::WriteLine(int32)
, 다르지만 동등한 대상입니다 br.s
).
JIT 수정이 필요할 것 같습니다.
환경:
Environment.Version
: 4.0.30319.42000<TargetFramework>netcoreapp2.0</TargetFramework>
- VS : 15.5.0 미리보기 5.0
dotnet --version
: 2.1.1
답변
실제로 어셈블리 오류입니다. x64, .net 4.7.1, 릴리스 빌드.
분해 :
for(int i = 0; i < N - Old.Length; i++)
00007FF942690ADD xor eax,eax
for(int i = 0; i < N - Old.Length; i++)
00007FF942690ADF mov ebx,esi
00007FF942690AE1 sub ebx,ebp
00007FF942690AE3 test ebx,ebx
00007FF942690AE5 jle 00007FF942690AFF
dd[i] = d;
00007FF942690AE7 mov rdx,qword ptr [rdi]
00007FF942690AEA cmp eax,dword ptr [rdx+8]
00007FF942690AED jae 00007FF942690B11
00007FF942690AEF movsxd rcx,eax
00007FF942690AF2 vmovsd qword ptr [rdx+rcx*8+10h],xmm6
for(int i = 0; i < N - Old.Length; i++)
00007FF942690AF9 inc eax
00007FF942690AFB cmp ebx,eax
00007FF942690AFD jg 00007FF942690AE7
00007FF942690AFF vmovaps xmm6,xmmword ptr [rsp+20h]
00007FF942690B06 add rsp,30h
00007FF942690B0A pop rbx
00007FF942690B0B pop rbp
00007FF942690B0C pop rsi
00007FF942690B0D pop rdi
00007FF942690B0E pop r14
00007FF942690B10 ret
문제는 주소 00007FF942690AFD, jg 00007FF942690AE7에 있습니다. ebx (루프 끝 값인 4 포함)가 값 i 인 eax보다 크면 (jg) 뒤로 이동합니다. 물론 4이면 실패하므로 배열의 마지막 요소를 쓰지 않습니다.
inc가 i의 레지스터 값 (eax, 0x00007FF942690AF9)이므로 실패하고 4로 확인하지만 여전히 해당 값을 써야합니다. 디버그 빌드에 해당 코드가 포함되어 있기 때문에 (N-Old.Length) 최적화의 결과 인 것처럼 보이기 때문에 문제가 정확히 어디에 있는지 정확히 찾아내는 것은 약간 어렵지만 릴리스 빌드는이를 미리 계산합니다. 그래서 그것은 jit 사람들이 고칠 것입니다;)