2017-06-03 4 views
9

J'ai remarqué qu'une structure enveloppant un seul flottant est significativement plus lente que d'utiliser un flottant directement, avec environ la moitié de la performance.Pourquoi l'ajout d'un champ supplémentaire à struct améliore grandement ses performances?

using System; 
using System.Diagnostics; 

struct Vector1 { 

    public float X; 

    public Vector1(float x) { 
     X = x; 
    } 

    public static Vector1 operator +(Vector1 a, Vector1 b) { 
     a.X = a.X + b.X; 
     return a; 
    } 
} 

Cependant, lors de l'ajout d'un champ « extra » supplémentaire, la magie semble se produire et la performance devient de nouveau plus raisonnable:

struct Vector1Magic { 

    public float X; 
    private bool magic; 

    public Vector1Magic(float x) { 
     X = x; 
     magic = true; 
    } 

    public static Vector1Magic operator +(Vector1Magic a, Vector1Magic b) { 
     a.X = a.X + b.X; 
     return a; 
    } 
} 

Le code que je l'habitude de référence ci se présente comme suit:

class Program { 
    static void Main(string[] args) { 
     int iterationCount = 1000000000; 
     var sw = new Stopwatch(); 
     sw.Start(); 
     var total = 0.0f; 
     for (int i = 0; i < iterationCount; i++) { 
      var v = (float) i; 
      total = total + v; 
     } 
     sw.Stop(); 
     Console.WriteLine("Float time was {0} for {1} iterations.", sw.Elapsed, iterationCount); 
     Console.WriteLine("total = {0}", total); 
     sw.Reset(); 
     sw.Start(); 
     var totalV = new Vector1(0.0f); 
     for (int i = 0; i < iterationCount; i++) { 
      var v = new Vector1(i); 
      totalV += v; 
     } 
     sw.Stop(); 
     Console.WriteLine("Vector1 time was {0} for {1} iterations.", sw.Elapsed, iterationCount); 
     Console.WriteLine("totalV = {0}", totalV); 
     sw.Reset(); 
     sw.Start(); 
     var totalVm = new Vector1Magic(0.0f); 
     for (int i = 0; i < iterationCount; i++) { 
      var vm = new Vector1Magic(i); 
      totalVm += vm; 
     } 
     sw.Stop(); 
     Console.WriteLine("Vector1Magic time was {0} for {1} iterations.", sw.Elapsed, iterationCount); 
     Console.WriteLine("totalVm = {0}", totalVm); 
     Console.Read(); 
    } 
} 

Avec les résultats de référence:

Float time was 00:00:02.2444910 for 1000000000 iterations. 
Vector1 time was 00:00:04.4490656 for 1000000000 iterations. 
Vector1Magic time was 00:00:02.2262701 for 1000000000 iterations. 

paramètres du compilateur/environnement: OS: Windows 10 64 bits Toolchain: VS2017 Cadre: .Net 4.6.2 cible: Tout processeur Prefer 32 bits

Si 64 bits est défini comme la cible, nos résultats sont plus prévisibles, mais bien pire que ce que nous voyons avec Vector1Magic sur le 32 bit cible:

Float time was 00:00:00.6800014 for 1000000000 iterations. 
Vector1 time was 00:00:04.4572642 for 1000000000 iterations. 
Vector1Magic time was 00:00:05.7806399 for 1000000000 iterations. 

Pour les sorciers réels, j'ai inclus une décharge de l'iL ici: https://pastebin.com/sz2QLGEx

Une recherche plus poussée indique que cela semble être spécifique à l'exécution de Windows, car le compilateur mono produit la même IL. Sur le mono runtime, les deux variantes de struct ont des performances environ 2x plus lentes par rapport au flottant brut. C'est assez différent de la performance que nous voyons sur .Net.

Que se passe-t-il ici?

* Notez que cette question comportait à l'origine un processus de référence imparfait (merci Max Payne pour l'avoir signalé), et a été mise à jour pour refléter plus fidèlement les horaires.

+1

Im devinant cela est dû à l'emballage struct maintenant avoir un meilleur alignement de la mémoire. –

+2

Vous devez ajouter une itération de préchauffage pour exclure les interférences possibles de JIT ou d'autres traitements ponctuels. – PetSerAl

+2

Si je passe en 64 bits, j'obtiens des performances moins bonnes pour votre vecteur "magique". – Adrian

Répondre

0

Cela ne devrait pas arriver. C'est évidemment une sorte de désalignement qui force le JAT à ne pas fonctionner comme il le devrait.

struct Vector1 //Works fast in 32 Bit 
{ 
    public double X; 
} 

struct Vector1 //Works fast in 64 Bit and 32 Bit 
{ 
    public double X; 
    public double X2; 
} 

Vous devez aussi appeler: Console.WriteLine (total); qui augmente le temps exactement au temps Vector1Magic qui a du sens. La question est toujours là, pourquoi Vector1 est si lent.

Peut-être les structures ne sont pas optimisées pour sizeof (foo) < 64 bits en mode 64 bits.

Il semble que cela a été ansered il y a 7 ans: Why is 16 byte the recommended size for struct in C#?

+0

"Cela ne devrait pas se produire, il s'agit évidemment d'un désalignement qui force le JIT à ne pas fonctionner comme il le devrait." - Cela ne répond pas vraiment à la question. Pourquoi cela arrive-t-il? Quel est le raisonnement derrière cela? – Varon

+0

Ensuite, laissez-upvote jusqu'à ce que quelqu'un vient autour de qui sait comment .net fonctionne en interne. Le code IL produit est bon donc la lecture ne nous indiquera pas la solution. Le problème est plus profond, à l'intérieur de l'optimiseur JIT. C'est une trouvaille très intéressante, peut-être pourriez-vous poster ceci sur le forum MSDN .net team-developer? –

+0

Veuillez insérer une ligne indiquant Console.WriteLine (total); après votre première boucle. Le JIT ne va pas exécuter les nœuds où le résultat n'est pas utilisé par la suite. –