Niska wydajność szablonu funkcji

0

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?

2
  1. To nie jest szablon tylko metoda generyczna. To że coś się nazywa tak w c++ to nie znaczy że w c# też.
  2. Genreryki muszą zostać wyrenderowane przez clr'a. Powtórz sobie test 1000 i różnica nie będzie tak widoczna. W C# wiele rzeczy w przypadku cold run wyjdzie że jest wolne.
  3. W C# generyki są renderowane w locie, w c++ (z tego co sie orentuje) w czasie kompilacji.
  4. Jaki ma sens ma pokazywanie asma w debug a czasu z 'release'? Większość optymalizacji jest robiona w runtime wlasnie. IL z release potrafi być nawet 2/3 razy krótszy z 'release', a do tego dojdą optymalizacje jita właśnie.
  5. .net 4.5.2? Co to za archeologia?

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.

Dokładnie tak to jit robi żebyś nie musiał ręcznie.

1 użytkowników online, w tym zalogowanych: 0, gości: 1