Oui, il y a d'autres astuces :-)
En fait, j'ai fait pas mal de recherches sur l'optimisation du code C#. Jusqu'à présent, voici les résultats les plus significatifs :
- Les fonctions et les actions qui sont transmises directement sont souvent intégrées par le JIT'ter. Notez que vous ne devez pas les stocker en tant que variables, car ils sont ensuite appelés en tant que délégués. Voir aussi ce poste pour plus de détails.
- Faites attention aux surcharges. Appeler Equals sans utiliser
IEquatable<T>
est généralement un mauvais plan - donc si vous utilisez, par exemple, un hachage, assurez-vous d'implémenter les bonnes surcharges et interfaces, car cela vous permettra de gagner beaucoup en performance.
- Les génériques appelés depuis d'autres classes sont jamais en ligne. La raison en est que la "magie" décrite aquí .
- Si vous utilisez une structure de données, essayez d'utiliser un tableau à la place :-) Vraiment, ces choses sont rapides comme l'enfer par rapport à ... eh bien, à peu près tout, je suppose. J'ai optimisé pas mal de choses en utilisant mes propres tables de hachage et en utilisant des tableaux au lieu de listes.
- Dans de nombreux cas, la consultation de tables est plus rapide que le calcul ou l'utilisation de constructions telles que la consultation de vtable, les commutateurs, les instructions if multiples et même les calculs. C'est aussi une bonne astuce si vous avez des branches ; une prédiction de branche ratée peut souvent devenir un gros problème. Voir aussi ce poste - C'est une astuce que j'utilise souvent en C# et qui fonctionne très bien dans de nombreux cas. Oh, et les tables de consultation sont des tableaux, bien sûr.
- Expérimentez la création de (petites) classes structes. En raison de la nature des types de valeurs, certaines optimisations sont différentes pour les struct que pour les class. Par exemple, les appels de méthode sont plus simples, parce que le compilateur sait exactement quelle méthode va être appelée. De même, les tableaux de structures sont généralement plus rapides que les tableaux de classes, car ils nécessitent une opération mémoire de moins par opération de tableau.
- N'utilisez pas de tableaux multidimensionnels. Bien que je préfère
Foo[]
même Foo[][]
est normalement plus rapide que Foo[,]
.
- Si vous copiez des données, préférez Buffer.BlockCopy à Array.Copy tous les jours de la semaine. Soyez également prudent avec les chaînes de caractères : les opérations sur les chaînes de caractères peuvent réduire les performances.
Il existait également un guide intitulé "Optimisation pour le processeur Intel Pentium" contenant un grand nombre d'astuces (comme le décalage ou la multiplication au lieu de la division). Bien que le compilateur fasse un bel effort de nos jours, cela aide aussi parfois un peu.
Bien entendu, il ne s'agit que d'optimisations ; les gains de performance les plus importants résultent généralement d'une modification de l'algorithme et/ou de la structure des données. Assurez-vous de vérifier les options qui vous sont offertes et ne vous limitez pas trop au cadre .NET... j'ai aussi une tendance naturelle à me méfier de l'implémentation .NET jusqu'à ce que j'aie vérifié le code décompilé par moi-même... il y a une tonne de choses qui auraient pu être implémentées beaucoup plus rapidement (la plupart du temps pour de bonnes raisons).
HTH
Alex m'a fait remarquer que Array.Copy
est en fait plus rapide selon certaines personnes. Et comme je ne sais pas vraiment ce qui a changé au fil des ans, j'ai décidé que la seule façon de procéder était de créer un nouveau benchmark et de le mettre à l'épreuve.
Si vous êtes juste intéressé par les résultats, allez-y. Dans la plupart des cas, l'appel à Buffer.BlockCopy
surpasse clairement les performances Array.Copy
. Testé sur un Intel Skylake avec 16 Go de mémoire (>10 Go libres) sur .NET 4.5.2.
Code :
static void TestNonOverlapped1(int K)
{
long total = 1000000000;
long iter = total / K;
byte[] tmp = new byte[K];
byte[] tmp2 = new byte[K];
for (long i = 0; i < iter; ++i)
{
Array.Copy(tmp, tmp2, K);
}
}
static void TestNonOverlapped2(int K)
{
long total = 1000000000;
long iter = total / K;
byte[] tmp = new byte[K];
byte[] tmp2 = new byte[K];
for (long i = 0; i < iter; ++i)
{
Buffer.BlockCopy(tmp, 0, tmp2, 0, K);
}
}
static void TestOverlapped1(int K)
{
long total = 1000000000;
long iter = total / K;
byte[] tmp = new byte[K + 16];
for (long i = 0; i < iter; ++i)
{
Array.Copy(tmp, 0, tmp, 16, K);
}
}
static void TestOverlapped2(int K)
{
long total = 1000000000;
long iter = total / K;
byte[] tmp = new byte[K + 16];
for (long i = 0; i < iter; ++i)
{
Buffer.BlockCopy(tmp, 0, tmp, 16, K);
}
}
static void Main(string[] args)
{
for (int i = 0; i < 10; ++i)
{
int N = 16 << i;
Console.WriteLine("Block size: {0} bytes", N);
Stopwatch sw = Stopwatch.StartNew();
{
sw.Restart();
TestNonOverlapped1(N);
Console.WriteLine("Non-overlapped Array.Copy: {0:0.00} ms", sw.Elapsed.TotalMilliseconds);
GC.Collect(GC.MaxGeneration);
GC.WaitForFullGCComplete();
}
{
sw.Restart();
TestNonOverlapped2(N);
Console.WriteLine("Non-overlapped Buffer.BlockCopy: {0:0.00} ms", sw.Elapsed.TotalMilliseconds);
GC.Collect(GC.MaxGeneration);
GC.WaitForFullGCComplete();
}
{
sw.Restart();
TestOverlapped1(N);
Console.WriteLine("Overlapped Array.Copy: {0:0.00} ms", sw.Elapsed.TotalMilliseconds);
GC.Collect(GC.MaxGeneration);
GC.WaitForFullGCComplete();
}
{
sw.Restart();
TestOverlapped2(N);
Console.WriteLine("Overlapped Buffer.BlockCopy: {0:0.00} ms", sw.Elapsed.TotalMilliseconds);
GC.Collect(GC.MaxGeneration);
GC.WaitForFullGCComplete();
}
Console.WriteLine("-------------------------");
}
Console.ReadLine();
}
Résultats sur x86 JIT :
Block size: 16 bytes
Non-overlapped Array.Copy: 4267.52 ms
Non-overlapped Buffer.BlockCopy: 2887.05 ms
Overlapped Array.Copy: 3305.01 ms
Overlapped Buffer.BlockCopy: 2670.18 ms
-------------------------
Block size: 32 bytes
Non-overlapped Array.Copy: 1327.55 ms
Non-overlapped Buffer.BlockCopy: 763.89 ms
Overlapped Array.Copy: 2334.91 ms
Overlapped Buffer.BlockCopy: 2158.49 ms
-------------------------
Block size: 64 bytes
Non-overlapped Array.Copy: 705.76 ms
Non-overlapped Buffer.BlockCopy: 390.63 ms
Overlapped Array.Copy: 1303.00 ms
Overlapped Buffer.BlockCopy: 1103.89 ms
-------------------------
Block size: 128 bytes
Non-overlapped Array.Copy: 361.18 ms
Non-overlapped Buffer.BlockCopy: 219.77 ms
Overlapped Array.Copy: 620.21 ms
Overlapped Buffer.BlockCopy: 577.20 ms
-------------------------
Block size: 256 bytes
Non-overlapped Array.Copy: 192.92 ms
Non-overlapped Buffer.BlockCopy: 108.71 ms
Overlapped Array.Copy: 347.63 ms
Overlapped Buffer.BlockCopy: 353.40 ms
-------------------------
Block size: 512 bytes
Non-overlapped Array.Copy: 104.69 ms
Non-overlapped Buffer.BlockCopy: 65.65 ms
Overlapped Array.Copy: 211.77 ms
Overlapped Buffer.BlockCopy: 202.94 ms
-------------------------
Block size: 1024 bytes
Non-overlapped Array.Copy: 52.93 ms
Non-overlapped Buffer.BlockCopy: 38.84 ms
Overlapped Array.Copy: 144.39 ms
Overlapped Buffer.BlockCopy: 154.09 ms
-------------------------
Block size: 2048 bytes
Non-overlapped Array.Copy: 45.64 ms
Non-overlapped Buffer.BlockCopy: 30.11 ms
Overlapped Array.Copy: 118.33 ms
Overlapped Buffer.BlockCopy: 109.16 ms
-------------------------
Block size: 4096 bytes
Non-overlapped Array.Copy: 30.93 ms
Non-overlapped Buffer.BlockCopy: 30.72 ms
Overlapped Array.Copy: 119.73 ms
Overlapped Buffer.BlockCopy: 104.66 ms
-------------------------
Block size: 8192 bytes
Non-overlapped Array.Copy: 30.37 ms
Non-overlapped Buffer.BlockCopy: 26.63 ms
Overlapped Array.Copy: 90.46 ms
Overlapped Buffer.BlockCopy: 87.40 ms
-------------------------
Résultats sur x64 JIT :
Block size: 16 bytes
Non-overlapped Array.Copy: 1252.71 ms
Non-overlapped Buffer.BlockCopy: 694.34 ms
Overlapped Array.Copy: 701.27 ms
Overlapped Buffer.BlockCopy: 573.34 ms
-------------------------
Block size: 32 bytes
Non-overlapped Array.Copy: 995.47 ms
Non-overlapped Buffer.BlockCopy: 654.70 ms
Overlapped Array.Copy: 398.48 ms
Overlapped Buffer.BlockCopy: 336.86 ms
-------------------------
Block size: 64 bytes
Non-overlapped Array.Copy: 498.86 ms
Non-overlapped Buffer.BlockCopy: 329.15 ms
Overlapped Array.Copy: 218.43 ms
Overlapped Buffer.BlockCopy: 179.95 ms
-------------------------
Block size: 128 bytes
Non-overlapped Array.Copy: 263.00 ms
Non-overlapped Buffer.BlockCopy: 196.71 ms
Overlapped Array.Copy: 137.21 ms
Overlapped Buffer.BlockCopy: 107.02 ms
-------------------------
Block size: 256 bytes
Non-overlapped Array.Copy: 144.31 ms
Non-overlapped Buffer.BlockCopy: 101.23 ms
Overlapped Array.Copy: 85.49 ms
Overlapped Buffer.BlockCopy: 69.30 ms
-------------------------
Block size: 512 bytes
Non-overlapped Array.Copy: 76.76 ms
Non-overlapped Buffer.BlockCopy: 55.31 ms
Overlapped Array.Copy: 61.99 ms
Overlapped Buffer.BlockCopy: 54.06 ms
-------------------------
Block size: 1024 bytes
Non-overlapped Array.Copy: 44.01 ms
Non-overlapped Buffer.BlockCopy: 33.30 ms
Overlapped Array.Copy: 53.13 ms
Overlapped Buffer.BlockCopy: 51.36 ms
-------------------------
Block size: 2048 bytes
Non-overlapped Array.Copy: 27.05 ms
Non-overlapped Buffer.BlockCopy: 25.57 ms
Overlapped Array.Copy: 46.86 ms
Overlapped Buffer.BlockCopy: 47.83 ms
-------------------------
Block size: 4096 bytes
Non-overlapped Array.Copy: 29.11 ms
Non-overlapped Buffer.BlockCopy: 25.12 ms
Overlapped Array.Copy: 45.05 ms
Overlapped Buffer.BlockCopy: 47.84 ms
-------------------------
Block size: 8192 bytes
Non-overlapped Array.Copy: 24.95 ms
Non-overlapped Buffer.BlockCopy: 21.52 ms
Overlapped Array.Copy: 43.81 ms
Overlapped Buffer.BlockCopy: 43.22 ms
-------------------------