En C #, existe-t-il une différence de performance significative entre l’utilisation de UInt32 et celle de Int32?

StackOverflow https://stackoverflow.com/questions/306602

  •  08-07-2019
  •  | 
  •  

Question

Je porte une application existante en C # et souhaite améliorer les performances dans la mesure du possible. De nombreux compteurs de boucles et références de tableaux existants sont définis en tant que System.UInt32, au lieu de l’Int32 que j’aurais utilisé.

Existe-t-il une différence de performances significative entre l’utilisation de UInt32 et Int32?

Était-ce utile?

La solution

Je ne pense pas qu'il y ait des considérations de performances, autres que la différence possible entre l'arithmétique signée et non signée au niveau du processeur, mais à ce stade, je pense que les différences sont discutables.

La différence majeure réside dans la conformité CLS, car les types non signés ne sont pas conformes à CLS car toutes les langues ne les prennent pas en charge.

Autres conseils

La réponse courte est "Non. Tout impact sur les performances sera négligeable ".

La réponse correcte est "Cela dépend."

Une meilleure question est: "Dois-je utiliser uint lorsque je suis certain de ne pas avoir besoin d'un panneau?"

La raison pour laquelle vous ne pouvez pas donner un "oui" définitif ou " no " en ce qui concerne les performances est parce que la plate-forme cible déterminera en définitive les performances. En d’autres termes, les performances sont dictées par le processeur qui exécutera le code et les instructions disponibles. Votre code .NET est compilé en Langage intermédiaire . Ces instructions sont ensuite compilées sur la plate-forme cible par le Compilateur Just-In-Time (JIT) dans le cadre du Exécution en langage commun (CLR). Vous ne pouvez pas contrôler ou prédire quel code sera généré pour chaque utilisateur.

Alors, sachant que le matériel est l'arbitre final des performances, la question devient: "Dans quelle mesure le code généré par .NET est-il différent pour un entier signé par rapport à un entier non signé?" et "La différence a-t-elle un impact sur mon application et mes plates-formes cibles?"

La meilleure façon de répondre à ces questions est de lancer un test.

class Program
{
  static void Main(string[] args)
  {
    const int iterations = 100;
    Console.WriteLine(
Signed:      00:00:00.5066966

Unsigned:    00:00:00.5052279
quot;Signed: {Iterate(TestSigned, iterations)}"); Console.WriteLine(<*>quot;Unsigned: {Iterate(TestUnsigned, iterations)}"); Console.Read(); } private static void TestUnsigned() { uint accumulator = 0; var max = (uint)Int32.MaxValue; for (uint i = 0; i < max; i++) ++accumulator; } static void TestSigned() { int accumulator = 0; var max = Int32.MaxValue; for (int i = 0; i < max; i++) ++accumulator; } static TimeSpan Iterate(Action action, int count) { var elapsed = TimeSpan.Zero; for (int i = 0; i < count; i++) elapsed += Time(action); return new TimeSpan(elapsed.Ticks / count); } static TimeSpan Time(Action action) { var sw = new Stopwatch(); sw.Start(); action(); sw.Stop(); return sw.Elapsed; } }

Les deux méthodes de test, TestSigned et TestUnsigned , effectuent chacune environ 2 millions d'itérations d'un incrément simple sur un entier signé et non signé, respectivement. Le code de test exécute 100 itérations de chaque test et fait la moyenne des résultats. Cela devrait éliminer toutes les incohérences potentielles. Les résultats sur mon i7-5960X compilé pour x64 sont les suivants:

<*>

Ces résultats sont presque identiques, mais pour obtenir une réponse définitive, nous devons vraiment examiner le bytecode généré pour le programme. Nous pouvons utiliser ILDASM dans le cadre du SDK .NET pour inspecter le code de l’assembly généré par le compilateur.

 Bytecode

Ici, nous pouvons voir que le compilateur C # privilégie les entiers signés et effectue la plupart des opérations en mode natif en tant qu'entiers signés et ne traite jamais la valeur en mémoire comme non signée lors de la comparaison de la branche (saut a.k.a ou if). Bien que nous utilisions un entier non signé pour l'itérateur ET l'accumulateur dans TestUnsigned , le code est presque identique à la méthode TestSigned , à l'exception d'une seule instruction: IL_0016 . Un rapide coup d'œil sur la spécification ECMA décrit la différence:

  

blt.un.s:   Branche la cible si inférieur à (non signé ou non ordonné), forme abrégée.

     

blt.s:   Branche la cible si inférieur à, forme abrégée.

Étant une instruction aussi courante, il est raisonnable de supposer que la plupart des processeurs haute puissance modernes auront des instructions matérielles pour les deux opérations et qu’elles exécuteront très probablement le même nombre de cycles, mais cela n’est pas garanti . Un processeur de faible puissance peut avoir moins d'instructions et ne pas avoir de branche pour unsigned int. Dans ce cas, le compilateur JIT peut devoir émettre plusieurs instructions matérielles (une conversion en premier, puis une branche, par exemple) pour exécuter l'instruction IL blt.un.s . Même si tel était le cas, ces instructions supplémentaires seraient élémentaires et n'auraient probablement aucune incidence sur les performances.

Je n'ai pas fait de recherche à ce sujet dans .NET, mais dans l'ancien temps de Win32 / C ++, si vous vouliez créer un "int signé". Pour un "long sign", le cpu a dû lancer une opération pour prolonger le signe. Pour lancer un " unsigned int " à un "unsigned long", il avait juste zéro dans les octets supérieurs. L’économie était de l’ordre de quelques cycles d’horloge (c’est-à-dire que vous deviez le faire des milliards de fois pour obtenir une différence perceptible)

Il n'y a pas de différence en termes de performances. Les calculs de nombres entiers simples sont bien connus et les processeurs modernes sont hautement optimisés pour les exécuter rapidement.

Ces types d’optimisation en valent rarement la peine. Utilisez le type de données le plus approprié pour la tâche et laissez-le rester. Si cela touche une base de données, vous pouvez probablement trouver une douzaine de modifications dans la conception de la base de données, la syntaxe de la requête ou la stratégie d'indexation qui compenseraient l'optimisation du code en C # de quelques centaines de fois.

Il va allouer la même quantité de mémoire dans tous les cas (bien que celui-ci puisse stocker une valeur plus grande, car il n’a pas économisé d’espace pour le signe). Je doute donc que vous constatiez une différence de "performances", à moins que vous utilisiez des valeurs élevées / des valeurs négatives qui provoqueraient l'explosion d'une option ou de l'autre.

cela ne concerne pas vraiment les performances mais plutôt les exigences du compteur de boucles.

Il y avait beaucoup d'itérations à compléter

        Console.WriteLine(Int32.MaxValue);      // Max interation 2147483647
        Console.WriteLine(UInt32.MaxValue);     // Max interation 4294967295

Le unsigned int peut être là pour une raison.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top