/ / Чому поле поля встановлюється багато разів повільніше, ніж отримання поля? - c #, .net, продуктивність, поле

Чому поле для налаштування багато разів повільніше, ніж отримання поля? - c #, .net, продуктивність, поле

Я вже знав, що встановлення поля набагато повільніше, ніж налаштування локальної змінної, але також з'являється налаштування поля з локальна змінна значно повільніше, ніж встановлення локальної змінної з полем. Чому це? У будь-якому випадку використовується адреса поля.

public class Test
{
public int A = 0;
public int B = 4;

public void Method1() // Set local with field
{
int a = A;

for (int i = 0; i < 100; i++)
{
a += B;
}

A = a;
}

public void Method2() // Set field with local
{
int b = B;

for (int i = 0; i < 100; i++)
{
A += b;
}
}
}

Результати тесту з ітераціями 10e + 6:

Метод 1: 28.1321 мс Спосіб 2: 162.4528 мс

Відповіді:

15 для відповіді № 1

Запускаючи це на моїй машині, я отримую аналогічні відмінності часу, однак, дивлячись на JITted код для 10-метрової ітерації, ясно, зрозуміло, чому це так:

Метод A:

mov     r8,rcx
; "A" is loaded into eax
mov     eax,dword ptr [r8+8]
xor     edx,edx
; "B" is loaded into ecx
mov     ecx,dword ptr [r8+0Ch]
nop     dword ptr [rax]
loop_start:
; Partially unrolled loop, all additions done in registers
add     eax,ecx
add     eax,ecx
add     eax,ecx
add     eax,ecx
add     edx,4
cmp     edx,989680h
jl      loop_start
; Store the sum in eax back to "A"
mov     dword ptr [r8+8],eax
ret

І метод Б:

; "B" is loaded into edx
mov     edx,dword ptr [rcx+0Ch]
xor     r8d,r8d
nop word ptr [rax+rax]
loop_start:
; Partially unrolled loop, but each iteration requires reading "A" from memory
; adding "B" to it, and then writing the new "A" back to memory.
mov     eax,dword ptr [rcx+8]
add     eax,edx
mov     dword ptr [rcx+8],eax
mov     eax,dword ptr [rcx+8]
add     eax,edx
mov     dword ptr [rcx+8],eax
mov     eax,dword ptr [rcx+8]
add     eax,edx
mov     dword ptr [rcx+8],eax
mov     eax,dword ptr [rcx+8]
add     eax,edx
mov     dword ptr [rcx+8],eax
add     r8d,4
cmp     r8d,989680h
jl      loop_start
rep ret

Як ви можете бачити з монтажу, метод A єбуде значно швидшим, оскільки значення A і B обидва ставляться до регістрів, і всі додатки відбуваються там без проміжних записів до пам'яті. Спосіб B, з іншого боку, набуває навантаження і зберігає до "A" в пам'яті для кожна окрема ітерація.


2 для відповіді № 2

У випадку 1 a явно зберігається в реєстрі. Все інше було б жахливим результатом компіляції.

Можливо, .NET JIT не бажає / не може конвертувати магазини в A зареєструвати магазини у випадку 2.

Я сумніваюсь, що це примушує модель пам'яті .NET, тому що інші теми ніколи не зможуть визначити різницю між вашими двома способами, якщо вони лише спостерігатимуть A бути 0 або сумою. Вони не можуть спростувати теорію про те, що оптимізації ніколи не було. Це дозволяє це зробити під семантикою .NET абстрактної машини.

Не дивно, що .NET JIT виконує невелику оптимізацію. Це добре відомо послідовникам performance тег на переповненні стека.

З досвіду я знаю, що JIT набагато частіше завантажує кеш-пам'ять в регістрах. Ось чому справа 1 (очевидно) не доступна B з кожною ітерацією.

Регіональні обчислення дешевше, ніж доступ до пам'яті. Це навіть вірно, якщо згадана пам'ять знаходиться в кеші CPU L1 (як у випадку з цим).

Я думав, що тільки місцеві жителі мають право на кешування процесорів?

Це не може бути так, тому що процесор навіть не знає, що таке місцеві. Всі адреси виглядають однаково.


-2 для відповіді № 3

method2: поле читається ~ 100x і встановлюється ~ 100x занадто = 200x larg_0 (це) + 100x ldfld (поле навантаження) + 100x stfld (поле встановленого) + 100x ldloc (локальний)

method1: поле читається 100x, але не встановлено це еквівалентно методу 1 мінус 100x ldarg_0 (це)