133 votes

Array.Copy vs Buffer.BlockCopy

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 ?

3 votes

N'oubliez pas Marshal.Copy :-) . Utilisez Array.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 avec StructLayout 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 parfois Buffer.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.

2voto

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

https://github.com/chivandikwa/Random-Benchmarks

1voto

stt106 Points 33

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

-1voto

Chrism Points 27

Tout ce qui est <15ms ne vous donne pas un timing exact avec Stopwatch de toute façon à cause du Scheduler de Windows (Windows n'étant pas un OS temps réel). Exécutez-le en boucle plus de 1000.0000 fois et comparez les résultats.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X