Array.Copy y Buffer.BlockCopy font tous deux la même chose, mais BlockCopy
est destiné à la copie rapide de tableaux primitifs au niveau de l'octet, alors que Copy
est l'implémentation à usage général. Ma question est la suivante : dans quelles circonstances devriez-vous utiliser BlockCopy
? Devriez-vous l'utiliser à tout moment lorsque vous copiez des tableaux de type primitif, ou ne devriez-vous l'utiliser que si vous codez pour la performance ? Y a-t-il un danger inhérent à l'utilisation de Buffer.BlockCopy
sur Array.Copy
?
Réponses
Trop de publicités?Si l'on ne fait pas attention à la manière dont on rédige ce critère, on peut facilement être induit en erreur. J'ai écrit un test très simple pour illustrer cela. Dans mon test ci-dessous, si j'échange l'ordre de mes tests entre lancer Buffer.BlockCopy en premier ou Array.Copy, celui qui est lancé en premier est presque toujours le plus lent (bien qu'il soit très proche). Cela signifie que pour un tas de raisons que je ne vais pas détailler, le simple fait d'exécuter les tests plusieurs fois l'un après l'autre ne donnera pas de résultats précis.
J'ai eu recours au maintien du test tel quel avec 1000000 essais chacun pour un tableau de 1000000 doubles séquentiels. Cependant, je ne tiens pas compte des 900 000 premiers cycles et je fais la moyenne du reste. Dans ce cas, la mémoire tampon est supérieure.
private static void BenchmarkArrayCopies()
{
long[] bufferRes = new long[1000000];
long[] arrayCopyRes = new long[1000000];
long[] manualCopyRes = new long[1000000];
double[] src = Enumerable.Range(0, 1000000).Select(x => (double)x).ToArray();
for (int i = 0; i < 1000000; i++)
{
bufferRes[i] = ArrayCopyTests.ArrayBufferBlockCopy(src).Ticks;
}
for (int i = 0; i < 1000000; i++)
{
arrayCopyRes[i] = ArrayCopyTests.ArrayCopy(src).Ticks;
}
for (int i = 0; i < 1000000; i++)
{
manualCopyRes[i] = ArrayCopyTests.ArrayManualCopy(src).Ticks;
}
Console.WriteLine("Loop Copy: {0}", manualCopyRes.Average());
Console.WriteLine("Array.Copy Copy: {0}", arrayCopyRes.Average());
Console.WriteLine("Buffer.BlockCopy Copy: {0}", bufferRes.Average());
//more accurate results - average last 1000
Console.WriteLine();
Console.WriteLine("----More accurate comparisons----");
Console.WriteLine("Loop Copy: {0}", manualCopyRes.Where((l, i) => i > 900000).ToList().Average());
Console.WriteLine("Array.Copy Copy: {0}", arrayCopyRes.Where((l, i) => i > 900000).ToList().Average());
Console.WriteLine("Buffer.BlockCopy Copy: {0}", bufferRes.Where((l, i) => i > 900000).ToList().Average());
Console.ReadLine();
}
public class ArrayCopyTests
{
private const int byteSize = sizeof(double);
public static TimeSpan ArrayBufferBlockCopy(double[] original)
{
Stopwatch watch = new Stopwatch();
double[] copy = new double[original.Length];
watch.Start();
Buffer.BlockCopy(original, 0 * byteSize, copy, 0 * byteSize, original.Length * byteSize);
watch.Stop();
return watch.Elapsed;
}
public static TimeSpan ArrayCopy(double[] original)
{
Stopwatch watch = new Stopwatch();
double[] copy = new double[original.Length];
watch.Start();
Array.Copy(original, 0, copy, 0, original.Length);
watch.Stop();
return watch.Elapsed;
}
public static TimeSpan ArrayManualCopy(double[] original)
{
Stopwatch watch = new Stopwatch();
double[] copy = new double[original.Length];
watch.Start();
for (int i = 0; i < original.Length; i++)
{
copy[i] = original[i];
}
watch.Stop();
return watch.Elapsed;
}
}
Je veux juste ajouter mon cas de test qui montre à nouveau que BlockCopy n'a aucun avantage en termes de "PERFORMANCE" par rapport à Array.Copy. Ils semblent avoir les mêmes performances en mode release sur ma machine (les deux mettent environ 66 ms pour copier 50 millions d'entiers). En mode debug, BlockCopy est à peine plus rapide.
private static T[] CopyArray<T>(T[] a) where T:struct
{
T[] res = new T[a.Length];
int size = Marshal.SizeOf(typeof(T));
DateTime time1 = DateTime.Now;
Buffer.BlockCopy(a,0,res,0, size*a.Length);
Console.WriteLine("Using Buffer blockcopy: {0}", (DateTime.Now - time1).Milliseconds);
return res;
}
static void Main(string[] args)
{
int simulation_number = 50000000;
int[] testarray1 = new int[simulation_number];
int begin = 0;
Random r = new Random();
while (begin != simulation_number)
{
testarray1[begin++] = r.Next(0, 10000);
}
var copiedarray = CopyArray(testarray1);
var testarray2 = new int[testarray1.Length];
DateTime time2 = DateTime.Now;
Array.Copy(testarray1, testarray2, testarray1.Length);
Console.WriteLine("Using Array.Copy(): {0}", (DateTime.Now - time2).Milliseconds);
}
- Réponses précédentes
- Plus de réponses
3 votes
N'oubliez pas
Marshal.Copy
:-) . UtilisezArray.Copy
pour les types de référence, les types de valeurs complexes et si le type ne change pas,Buffer.BlockCopy
pour la "conversion" entre les types de valeurs, les tableaux d'octets et la magie des octets. Par exemple, la combinaison avecStructLayout
est assez puissant si vous savez ce que vous faites. En ce qui concerne les performances, il semble qu'un appel non géré àmemcpy
/cpblk
est le plus rapide pour cela - voir code4k.blogspot.nl/2010/10/ .1 votes
J'ai fait quelques tests de référence avec
byte[]
. Il n'y avait aucune différence dans la version Release. Parfois,Array.Copy
parfoisBuffer.BlockCopy
(légèrement) plus rapide.0 votes
Une nouvelle réponse complète vient d'être postée ci-dessous. Notez que dans les cas où la taille du tampon est faible, la copie explicite de la boucle est généralement préférable.
0 votes
Je ne pense pas qu'ils fassent toujours la même chose - vous ne pouvez pas utiliser Array.Copy pour copier un tableau d'Ints vers un tableau d'Octets par exemple.
0 votes
Array.Copy
est plutôt une version spécialisée -- par exemple, elle ne peut copier que les tableaux de même rang.0 votes
Jon Skeet utilise Buffer.BlockCopy. Cela le rend plus rapide. Pas besoin d'analyse comparative :-)
0 votes
Est-ce que Span<T> a changé quelque chose dans la performance ?