Witam.
Czy znacznie dłuższy czas wykonywania funkcji z parametrem template
jest prawidłowym zachowaniem w c#?
A:
private void TestA(byte[] data, byte a, byte b)
{
for (int index = 0; index < data.Length; ++index)
{
if (data[index] == a)
{
data[index] = b;
}
}
}
B:
private void TestB(byte[] data, byte a, byte b)
{
for (int index = 0; index < data.Length; ++index)
{
if (data[index].Equals(a))
{
data[index] = b;
}
}
}
C
private void TestC<T>(T[] data, T a, T b)
{
for (int index = 0; index < data.Length; ++index)
{
if (data[index].Equals(a))
{
data[index] = b;
}
}
}
Wywołanie:
TestA(data, 0, 1);
timer.Restart();
TestA(data, 0, 1);
timer.LogTime("TestA");
TestB(data, 0, 1);
timer.LogTime("TestB");
TestC(data, (byte) 0, (byte) 1);
timer.LogTime("TestC");
Timer:
class Timer
{
private Stopwatch stopwatch;
public Timer()
{
stopwatch = new Stopwatch();
stopwatch.Start();
}
public void LogTime(string messagePrefix, bool restart = false)
{
Console.WriteLine(messagePrefix + ": {0}", stopwatch.ElapsedMilliseconds);
if (restart)
{
stopwatch.Restart();
}
}
public void Restart()
{
stopwatch.Restart();
}
}
Spodziewałem się różnicy czasów wykonywania funkcji w zakresie błędu pomiarowego, jednak wyniki są następujące:
TestA: 7
TestB: 15
TestC: 76
Różnica pomiędzy kodami funkcji jest dobrze widoczna w wersji disassembled:
A:
{
001E22A8 push ebp
001E22A9 mov ebp,esp
001E22AB push edi
001E22AC push esi
001E22AD push ebx
001E22AE sub esp,40h
001E22B1 mov esi,ecx
001E22B3 lea edi,[ebp-38h]
001E22B6 mov ecx,0Bh
001E22BB xor eax,eax
001E22BD rep stos dword ptr es:[edi]
001E22BF mov ecx,esi
001E22C1 mov dword ptr [ebp-3Ch],ecx
001E22C4 mov dword ptr [ebp-40h],edx
001E22C7 cmp dword ptr ds:[144268h],0
001E22CE je 001E22D5
001E22D0 call 7194D3B5
001E22D5 xor edx,edx
001E22D7 mov dword ptr [ebp-44h],edx
001E22DA mov dword ptr [ebp-4Ch],0
001E22E1 mov dword ptr [ebp-48h],0
001E22E8 nop
for (int index = 0; index < data.Length; ++index)
001E22E9 xor edx,edx
for (int index = 0; index < data.Length; ++index)
001E22EB mov dword ptr [ebp-44h],edx
001E22EE nop
001E22EF jmp 001E2339
{
001E22F1 nop
if (data[index] == a)
001E22F2 mov eax,dword ptr [ebp-44h]
001E22F5 mov edx,dword ptr [ebp-40h]
001E22F8 cmp eax,dword ptr [edx+4]
001E22FB jb 001E2302
001E22FD call 7194C64A
001E2302 movzx eax,byte ptr [edx+eax+8]
001E2307 movzx edx,byte ptr [ebp+0Ch]
001E230B cmp eax,edx
001E230D sete al
001E2310 movzx eax,al
001E2313 mov dword ptr [ebp-48h],eax
001E2316 cmp dword ptr [ebp-48h],0
001E231A je 001E2335
{
001E231C nop
data[index] = b;
001E231D mov eax,dword ptr [ebp-44h]
001E2320 mov edx,dword ptr [ebp-40h]
001E2323 cmp eax,dword ptr [edx+4]
001E2326 jb 001E232D
001E2328 call 7194C64A
001E232D mov ecx,dword ptr [ebp+8]
001E2330 mov byte ptr [edx+eax+8],cl
}
001E2334 nop
}
001E2335 nop
for (int index = 0; index < data.Length; ++index)
001E2336 inc dword ptr [ebp-44h]
001E2339 mov eax,dword ptr [ebp-44h]
001E233C mov edx,dword ptr [ebp-40h]
001E233F cmp eax,dword ptr [edx+4]
001E2342 setl al
001E2345 movzx eax,al
001E2348 mov dword ptr [ebp-4Ch],eax
001E234B cmp dword ptr [ebp-4Ch],0
001E234F jne 001E22F1
}
001E2351 nop
001E2352 lea esp,[ebp-0Ch]
001E2355 pop ebx
001E2356 pop esi
001E2357 pop edi
001E2358 pop ebp
001E2359 ret 8
B:
{
001E2C60 push ebp
001E2C61 mov ebp,esp
001E2C63 push edi
001E2C64 push esi
001E2C65 push ebx
001E2C66 sub esp,44h
001E2C69 mov esi,ecx
001E2C6B lea edi,[ebp-50h]
001E2C6E mov ecx,11h
001E2C73 xor eax,eax
001E2C75 rep stos dword ptr es:[edi]
001E2C77 mov ecx,esi
001E2C79 mov dword ptr [ebp-3Ch],ecx
001E2C7C mov dword ptr [ebp-40h],edx
001E2C7F cmp dword ptr ds:[144268h],0
001E2C86 je 001E2C8D
001E2C88 call 7194D3B5
001E2C8D xor edx,edx
001E2C8F mov dword ptr [ebp-44h],edx
001E2C92 mov dword ptr [ebp-4Ch],0
001E2C99 mov dword ptr [ebp-48h],0
001E2CA0 nop
for (int index = 0; index < data.Length; ++index)
001E2CA1 xor edx,edx
for (int index = 0; index < data.Length; ++index)
001E2CA3 mov dword ptr [ebp-44h],edx
001E2CA6 nop
001E2CA7 jmp 001E2CF4
{
001E2CA9 nop
if (data[index].Equals(a))
001E2CAA mov eax,dword ptr [ebp-44h]
001E2CAD mov edx,dword ptr [ebp-40h]
001E2CB0 cmp eax,dword ptr [edx+4]
001E2CB3 jb 001E2CBA
001E2CB5 call 7194C64A
001E2CBA lea ecx,[edx+eax+8]
001E2CBE movzx edx,byte ptr [ebp+0Ch]
001E2CC2 call 70A9E740
001E2CC7 mov dword ptr [ebp-50h],eax
001E2CCA movzx eax,byte ptr [ebp-50h]
001E2CCE mov dword ptr [ebp-48h],eax
001E2CD1 cmp dword ptr [ebp-48h],0
001E2CD5 je 001E2CF0
{
001E2CD7 nop
data[index] = b;
001E2CD8 mov eax,dword ptr [ebp-44h]
001E2CDB mov edx,dword ptr [ebp-40h]
001E2CDE cmp eax,dword ptr [edx+4]
001E2CE1 jb 001E2CE8
001E2CE3 call 7194C64A
001E2CE8 mov ecx,dword ptr [ebp+8]
001E2CEB mov byte ptr [edx+eax+8],cl
}
001E2CEF nop
}
001E2CF0 nop
for (int index = 0; index < data.Length; ++index)
001E2CF1 inc dword ptr [ebp-44h]
001E2CF4 mov eax,dword ptr [ebp-44h]
001E2CF7 mov edx,dword ptr [ebp-40h]
001E2CFA cmp eax,dword ptr [edx+4]
001E2CFD setl al
001E2D00 movzx eax,al
001E2D03 mov dword ptr [ebp-4Ch],eax
001E2D06 cmp dword ptr [ebp-4Ch],0
001E2D0A jne 001E2CA9
}
001E2D0C nop
001E2D0D lea esp,[ebp-0Ch]
001E2D10 pop ebx
001E2D11 pop esi
001E2D12 pop edi
001E2D13 pop ebp
001E2D14 ret 8
C:
{
001E2D28 push ebp
001E2D29 mov ebp,esp
001E2D2B push edi
001E2D2C push esi
001E2D2D push ebx
001E2D2E sub esp,50h
001E2D31 mov esi,ecx
001E2D33 lea edi,[ebp-5Ch]
001E2D36 mov ecx,14h
001E2D3B xor eax,eax
001E2D3D rep stos dword ptr es:[edi]
001E2D3F mov ecx,esi
001E2D41 mov dword ptr [ebp-3Ch],ecx
001E2D44 mov dword ptr [ebp-40h],edx
001E2D47 cmp dword ptr ds:[144268h],0
001E2D4E je 001E2D55
001E2D50 call 7194D3B5
001E2D55 xor edx,edx
001E2D57 mov dword ptr [ebp-44h],edx
001E2D5A mov dword ptr [ebp-4Ch],0
001E2D61 mov dword ptr [ebp-48h],0
001E2D68 nop
for (int index = 0; index < data.Length; ++index)
001E2D69 xor edx,edx
for (int index = 0; index < data.Length; ++index)
001E2D6B mov dword ptr [ebp-44h],edx
001E2D6E nop
001E2D6F jmp 001E2DDD
{
001E2D71 nop
if (data[index].Equals(a))
001E2D72 mov ecx,7049FDD8h
001E2D77 call 001330F4
001E2D7C mov dword ptr [ebp-50h],eax
001E2D7F mov eax,dword ptr [ebp-44h]
001E2D82 mov edx,dword ptr [ebp-40h]
001E2D85 cmp eax,dword ptr [edx+4]
001E2D88 jb 001E2D8F
001E2D8A call 7194C64A
001E2D8F lea eax,[edx+eax+8]
001E2D93 mov dword ptr [ebp-58h],eax
001E2D96 mov eax,dword ptr [ebp-50h]
001E2D99 mov edx,dword ptr [ebp+0Ch]
001E2D9C mov byte ptr [eax+4],dl
001E2D9F mov eax,dword ptr [ebp-50h]
001E2DA2 mov dword ptr [ebp-5Ch],eax
001E2DA5 mov ecx,dword ptr [ebp-58h]
001E2DA8 mov edx,dword ptr [ebp-5Ch]
001E2DAB call 70A9E6F4
001E2DB0 mov dword ptr [ebp-54h],eax
001E2DB3 movzx eax,byte ptr [ebp-54h]
001E2DB7 mov dword ptr [ebp-48h],eax
001E2DBA cmp dword ptr [ebp-48h],0
001E2DBE je 001E2DD9
{
001E2DC0 nop
data[index] = b;
001E2DC1 mov eax,dword ptr [ebp-44h]
001E2DC4 mov edx,dword ptr [ebp-40h]
001E2DC7 cmp eax,dword ptr [edx+4]
001E2DCA jb 001E2DD1
001E2DCC call 7194C64A
001E2DD1 mov ecx,dword ptr [ebp+8]
001E2DD4 mov byte ptr [edx+eax+8],cl
}
001E2DD8 nop
}
001E2DD9 nop
for (int index = 0; index < data.Length; ++index)
001E2DDA inc dword ptr [ebp-44h]
001E2DDD mov eax,dword ptr [ebp-44h]
001E2DE0 mov edx,dword ptr [ebp-40h]
001E2DE3 cmp eax,dword ptr [edx+4]
001E2DE6 setl al
001E2DE9 movzx eax,al
001E2DEC mov dword ptr [ebp-4Ch],eax
001E2DEF cmp dword ptr [ebp-4Ch],0
001E2DF3 jne 001E2D71
}
001E2DF9 nop
001E2DFA lea esp,[ebp-0Ch]
001E2DFD pop ebx
001E2DFE pop esi
001E2DFF pop edi
001E2E00 pop ebp
001E2E01 ret 8
Różnice:
A vs B:
https://www.diffchecker.com/MLvvg635
B vs C:
https://www.diffchecker.com/Yag3Tfx8
Środowisko: Microsoft Visual Studio 2017 Community
Target Framework: .NET Framework 4.5.2
Wyniki czasowe pochodzą z wersji Release, kod disassembled z wersji Debug.
Funkcje są maksymalnie uproszczone w celu pokazania różnic, właściwy kod jest znacznie dłuższy, jednak różnice w czasie wykonywania sprawdzają się do zaprezentowanego problemu.
W obecnej sytuacji najprostszym rozwiązaniem jest napisanie funkcji bez używania szablonu, jednak w przypadku użycia jej z innymi typami wymagane byłoby jej kopiowanie, co nie jest dobrym rozwiązaniem.
Czy istnieje inne rozwiązanie?