/ / Pourquoi mettre un champ beaucoup plus lent que l'obtention d'un champ? - c #, .net, performance, champ

Pourquoi définir un champ beaucoup plus lent que l'obtention d'un champ? - c #, .net, performance, champ

Je savais déjà que définir un champ est beaucoup plus lent que définir une variable locale, mais il apparaît également que la définition d'un champ avec une variable locale est beaucoup plus lente que la définition d'une variable locale avec un champ. Pourquoi est-ce? Dans les deux cas, l'adresse du champ est utilisée.

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;
}
}
}

Les résultats de référence avec 10e + 6 itérations sont les suivants:

Méthode 1: 28.1321 ms Méthode 2: 162,4528 ms

Réponses:

15 pour la réponse № 1

En exécutant ceci sur ma machine, j'obtiens des différences de temps similaires, mais en regardant le code JITted pour des itérations de 10M, il est évident de voir pourquoi:

Méthode 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

Et méthode B:

; "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

Comme vous pouvez le voir sur l’assemblage, la méthode A estva être significativement plus rapide puisque les valeurs de A et B sont toutes les deux placées dans des registres, et toutes les additions se produisent là-bas sans écritures intermédiaires en mémoire. La méthode B en revanche encourt un chargement et stocke dans "A" en mémoire pour chaque itération.


2 pour la réponse № 2

Dans le cas 1 a est clairement enregistré dans un registre. Tout le reste serait une horrible compilation.

Probablement, le JIT .NET ne veut pas / ne peut pas convertir les magasins en A enregistrer des magasins dans le cas 2.

Je doute que cela soit forcé par le modèle de mémoire .NET, car les autres threads ne peuvent jamais faire la différence entre vos deux méthodes si elles observent uniquement A être 0 ou la somme. Ils ne peuvent pas réfuter la théorie selon laquelle l'optimisation n'a jamais eu lieu. Cela le permet sous la sémantique de la machine abstraite .NET.

Il n'est pas surprenant de voir le JIT .NET effectuer de petites optimisations. Ceci est bien connu des adeptes de performance tag sur le débordement de pile.

Je sais d'expérience que le JIT est beaucoup plus susceptible de mettre en mémoire cache les charges de mémoire dans les registres. C'est pourquoi le cas 1 (apparemment) n'accède pas B à chaque itération.

Les calculs de registre sont moins chers que les accès à la mémoire. Ceci est même vrai si la mémoire en question se trouve dans le cache de la CPU L1 (comme c'est le cas ici).

Je pensais que seules les sections locales étaient éligibles pour la mise en cache du processeur?

Cela ne peut pas être le cas parce que le CPU ne sait même pas ce qu'est un local. Toutes les adresses se ressemblent.


-2 pour la réponse № 3

method2: le champ est lu ~ 100x et défini ~ 100x aussi = 200x larg_0 (this) + 100x ldfld (champ de chargement) + 100x stfld (champ défini) + 100x ldloc (local)

method1: le champ est lu 100x mais pas défini c'est équivalent à method1 moins 100x ldarg_0 (this)