Array.Copy vs Buffer.BlockCopy

124

Array.Copy dan Buffer.BlockCopy keduanya melakukan hal yang sama, tetapi BlockCopyditujukan untuk penyalinan array primitif tingkat-byte yang cepat, sedangkan Copyimplementasi untuk tujuan umum. Pertanyaan saya adalah - dalam keadaan apa Anda harus menggunakan BlockCopy? Haruskah Anda menggunakannya kapan saja saat Anda menyalin array tipe primitif, atau haruskah Anda menggunakannya hanya jika Anda mengkode untuk kinerja? Apakah ada sesuatu yang secara inheren berbahaya tentang penggunaan Buffer.BlockCopyberlebihan Array.Copy?

kandang
sumber
3
Jangan lupa Marshal.Copy:-). Nah, gunakan Array.Copyuntuk tipe referensi, tipe nilai kompleks dan jika tipenya tidak berubah, Buffer.BlockCopyuntuk "konversi" antara tipe nilai, array byte, dan sihir byte. F.ex. kombinasi dengan StructLayoutcukup kuat jika Anda tahu apa yang Anda lakukan. Mengenai kinerja, tampaknya panggilan tidak terkelola ke memcpy/ cpblkadalah yang tercepat untuk itu - lihat code4k.blogspot.nl/2010/10/… .
atlaste
1
Saya melakukan beberapa tes benchmark dengan byte[]. Tidak ada perbedaan dalam versi Rilis. Terkadang Array.Copy, terkadang Buffer.BlockCopy(sedikit) lebih cepat.
Bitterblue
Jawaban komprehensif baru baru saja diposting di bawah ini. Perhatikan bahwa dalam kasus dengan ukuran buffer kecil, penyalinan loop eksplisit biasanya paling baik.
Saus Khusus
Saya tidak berpikir mereka selalu melakukan hal yang sama - Anda tidak dapat menggunakan Array.Copy untuk menyalin array Ints ke array Bytes misalnya
mcmillab
Array.Copylebih merupakan versi khusus - misalnya ia hanya dapat menyalin array peringkat yang sama.
astrowalker

Jawaban:

59

Karena parameter yang Buffer.BlockCopyakan berbasis byte daripada berbasis indeks, Anda lebih cenderung mengacaukan kode Anda daripada jika Anda menggunakan Array.Copy, jadi saya hanya akan menggunakan Buffer.BlockCopydi bagian kinerja-kritis dari kode saya.

MusiGenesis
sumber
9
Sangat setuju. Ada terlalu banyak ruang untuk kesalahan dengan Buffer.BlockCopy. Buat sederhana, dan jangan mencoba memeras jus apa pun dari program Anda sampai Anda tahu di mana jus itu (pembuatan profil).
Stephen
5
Bagaimana jika Anda berurusan dengan byte []? Apakah ada gotcha lain dengan BlockCopy?
kandang
4
@thecoop: jika Anda berurusan dengan byte [] maka mungkin tidak masalah untuk menggunakan BlockCopy, kecuali definisi "byte" kemudian diubah menjadi sesuatu selain byte, yang mungkin akan memiliki efek yang cukup negatif pada bagian lain dari kode Anda. :) Satu-satunya masalah potensial lainnya adalah bahwa BlockCopy hanya melakukan byte langsung, jadi tidak memperhitungkan ketekunan, tetapi ini hanya akan berlaku pada mesin non-Windows, dan hanya jika Anda mengacaukan kodenya di posisi pertama. Juga, mungkin ada beberapa perbedaan aneh jika Anda menggunakan mono.
MusiGenesis
6
Dalam pengujian saya sendiri, Array.Copy () sangat mirip kinerjanya dengan Buffer.BlockCopy (). Buffer.BlockCopy secara konsisten <10% lebih cepat bagi saya ketika berhadapan dengan array byte 640 elemen (yang paling saya minati). Tetapi Anda harus melakukan pengujian Anda sendiri dengan data Anda sendiri, karena itu mungkin akan bervariasi tergantung pada data, tipe data, ukuran array, dan sebagainya. Saya harus mencatat bahwa kedua metode kira-kira 3x lebih cepat daripada menggunakan Array.Clone (), dan mungkin 20x lebih cepat daripada menyalinnya di for loop.
Ken Smith
3
@KevinMiller: uh, UInt16adalah dua byte per elemen. Jika Anda meneruskan array ini ke BlockCopy bersama dengan jumlah elemen dalam array, tentu saja hanya setengah dari array yang akan disalin. Agar ini berfungsi dengan baik, Anda harus meneruskan jumlah elemen dikalikan ukuran setiap elemen (2) sebagai parameter panjang. msdn.microsoft.com/en-us/library/… dan cari INT_SIZEdi contoh.
MusiGenesis
129

Pendahuluan

Saya terlambat bergabung ke pesta, tetapi dengan 32 ribu penayangan, hal ini layak dilakukan. Sebagian besar kode microbenchmarking dalam jawaban yang diposting sejauh ini mengalami satu atau beberapa kelemahan teknis yang parah, termasuk tidak memindahkan alokasi memori keluar dari loop pengujian (yang memperkenalkan artefak GC yang parah), tidak menguji variabel vs. aliran eksekusi deterministik, pemanasan JIT, dan tidak melacak variabilitas intra-pengujian. Selain itu, sebagian besar jawaban tidak menguji efek dari berbagai ukuran buffer dan berbagai jenis primitif (berkenaan dengan sistem 32-bit atau 64-bit). Untuk menjawab pertanyaan ini secara lebih komprehensif, saya menghubungkannya ke kerangka kerja mikrobenchmarking khusus yang saya kembangkan yang mengurangi sebagian besar "gotcha" umum sejauh mungkin. Pengujian dijalankan dalam mode Rilis .NET 4.0 pada mesin 32-bit dan mesin 64-bit. Hasil rata-rata dilakukan selama 20 pengujian, di mana setiap pengujian memiliki 1 juta pengujian per metode. Jenis primitif yang diuji adalahbyte(1 byte), int(4 byte), dan double(8 byte). Tiga metode yang diuji: Array.Copy(), Buffer.BlockCopy(), dan sederhana tugas per-indeks dalam satu lingkaran. Datanya terlalu banyak untuk diposting di sini, jadi saya akan merangkum poin-poin penting.

Takeaways

  • Jika panjang buffer Anda sekitar 75-100 atau kurang, rutinitas penyalinan loop eksplisit biasanya lebih cepat (sekitar 5%) daripada salah satu Array.Copy()atau Buffer.BlockCopy()untuk ketiga jenis primitif yang diuji pada mesin 32-bit dan 64-bit. Selain itu, rutinitas salinan loop eksplisit memiliki variabilitas kinerja yang lebih rendah dibandingkan dengan dua alternatif. Kinerja yang baik hampir pasti karena lokalitas referensi yang dieksploitasi oleh cache memori CPU L1 / L2 / L3 dalam hubungannya dengan tidak ada overhead panggilan metode.
    • Untuk doublebuffer di mesin 32-bit saja : Rutinitas penyalinan loop eksplisit lebih baik daripada kedua alternatif untuk semua ukuran buffer yang diuji hingga 100k. Peningkatannya 3-5% lebih baik daripada metode lainnya. Ini karena kinerja Array.Copy()dan Buffer.BlockCopy()menjadi sangat menurun setelah melewati lebar asli 32-bit. Jadi saya berasumsi efek yang sama akan berlaku untuk longbuffer juga.
  • Untuk ukuran buffer yang melebihi ~ 100, penyalinan loop eksplisit dengan cepat menjadi jauh lebih lambat daripada 2 metode lainnya (dengan satu pengecualian khusus yang baru saja disebutkan). Perbedaannya paling terlihat dengan byte[], di mana penyalinan loop eksplisit bisa menjadi 7x atau lebih lambat pada ukuran buffer yang besar.
  • Secara umum, untuk semua 3 tipe primitif yang diuji dan di semua ukuran buffer, Array.Copy()danBuffer.BlockCopy() dilakukan hampir identik. Rata-rata, Array.Copy()tampaknya memiliki keunggulan yang sangat tipis sekitar 2% atau kurang waktu yang dibutuhkan (tetapi 0,2% - 0,5% lebih baik adalah tipikal), meskipun Buffer.BlockCopy()kadang-kadang mengalahkannya. Untuk alasan yang tidak diketahui, Buffer.BlockCopy()memiliki variabilitas intra-tes yang jauh lebih tinggi daripada Array.Copy(). Efek ini tidak dapat dihilangkan meskipun saya mencoba beberapa mitigasi dan tidak memiliki teori yang dapat dioperasikan tentang mengapa.
  • Karena Array.Copy()merupakan metode yang "lebih cerdas", lebih umum, dan jauh lebih aman, selain sangat sedikit lebih cepat dan rata-rata memiliki variabilitas yang lebih kecil, metode ini harus dipilih untukBuffer.BlockCopy() di hampir semua kasus umum. Satu-satunya kasus penggunaan yang Buffer.BlockCopy()akan jauh lebih baik adalah ketika tipe nilai array sumber dan tujuan berbeda (seperti yang ditunjukkan dalam jawaban Ken Smith). Meskipun skenario ini tidak umum, Array.Copy()dapat berkinerja sangat buruk di sini karena transmisi jenis nilai "aman" terus-menerus, dibandingkan dengan transmisi langsung dari Buffer.BlockCopy().
  • Bukti tambahan dari luar StackOverflow yang Array.Copy()lebih cepat daripada Buffer.BlockCopy()penyalinan array tipe yang sama dapat ditemukan di sini .
Saus Khusus
sumber
Sebagai samping, juga ternyata bahwa sekitar panjang array 100 adalah ketika NET yang Array.Clear()pertama mulai mengalahkan kliring lingkaran tugas eksplisit dari sebuah array (pengaturan untuk false, 0atau null). Ini sesuai dengan temuan saya yang serupa di atas. Tolok ukur terpisah ini ditemukan secara online di sini: manski.net/2012/12/net-array-clear-vs-arrayx-0-performance
Saus Khusus
Ketika Anda mengatakan ukuran buffer; maksud Anda dalam byte, atau jumlah elemen?
dmarra
Dalam jawaban saya di atas, baik "panjang buffer" dan "ukuran buffer" umumnya mengacu pada jumlah elemen.
Saus Khusus
Saya punya contoh di mana saya perlu sering menyalin sekitar 8 byte data ke dalam buffer membaca dari sumber offset 5 byte. Saya menemukan salinan loop eksplisit secara signifikan lebih cepat daripada menggunakan Buffer.BlockCopy atau Array.Copy. Loop Results for 1000000 iterations 17.9515ms. Buffer.BlockCopy Results for 1000000 iterations 39.8937ms. Array.Copy Results for 1000000 iterations 45.9059ms Namun, jika ukuran salinan> ~ 20 byte loop eksplisit secara signifikan lebih lambat.
Tod Cunningham
@TodCunningham, 8 byte data? Maksudmu padanan panjang? Baik cast dan copy elemen tunggal (mati cepat) atau cukup buka gulungan itu secara manual.
astrowalker
67

Contoh lain ketika masuk akal untuk digunakan Buffer.BlockCopy()adalah ketika Anda diberikan array primitif (katakanlah, pendek), dan perlu mengubahnya menjadi array byte (misalnya, untuk transmisi melalui jaringan). Saya sering menggunakan metode ini saat menangani audio dari Silverlight AudioSink. Ini memberikan sampel sebagai short[]larik, tetapi Anda perlu mengubahnya menjadi byte[]larik saat Anda membuat paket yang Anda kirimkan Socket.SendAsync(). Anda dapat menggunakan BitConverter, dan mengulang melalui array satu per satu, tetapi itu jauh lebih cepat (sekitar 20x dalam pengujian saya) hanya untuk melakukan ini:

Buffer.BlockCopy(shortSamples, 0, packetBytes, 0, shortSamples.Length * sizeof(short)).  

Dan trik yang sama juga bekerja secara terbalik:

Buffer.BlockCopy(packetBytes, readPosition, shortSamples, 0, payloadLength);

Ini hampir sedekat yang Anda dapatkan di C # yang aman dengan (void *)jenis manajemen memori yang sangat umum di C dan C ++.

Ken Smith
sumber
6
Itu ide yang bagus - apakah Anda pernah mengalami masalah dengan ketekunan?
Phillip
Ya, saya pikir Anda bisa mengalami masalah itu, tergantung pada skenario Anda. Skenario saya sendiri biasanya adalah (a) Saya perlu beralih bolak-balik antara array byte dan array pendek pada mesin yang sama, atau (b) Saya kebetulan tahu bahwa saya mengirim data saya ke mesin yang sama endianness, dan yang saya kendalikan dari sisi jarak jauh. Tetapi jika Anda menggunakan protokol di mana mesin jarak jauh mengharapkan data dikirim dalam urutan jaringan daripada urutan host, ya, pendekatan ini akan memberi Anda masalah.
Ken Smith
Ken juga memiliki artikel tentang BlockCopy di blognya: blog.wouldbetheologian.com/2011/11/…
Drew Noakes
4
Perhatikan bahwa sejak .Net Core 2.1 Anda dapat melakukan ini tanpa menyalin. MemoryMarshal.AsBytes<T>atau MemoryMarshal.Cast<TFrom, TTo>membiarkan Anda menafsirkan urutan satu primitif sebagai urutan primitif lainnya.
Timo
16

Berdasarkan pengujian saya, kinerja bukanlah alasan untuk memilih Buffer.BlockCopy daripada Array.Copy. Dari pengujian saya Array.Copy sebenarnya lebih cepat dari Buffer.BlockCopy.

var buffer = File.ReadAllBytes(...);

var length = buffer.Length;
var copy = new byte[length];

var stopwatch = new Stopwatch();

TimeSpan blockCopyTotal = TimeSpan.Zero, arrayCopyTotal = TimeSpan.Zero;

const int times = 20;

for (int i = 0; i < times; ++i)
{
    stopwatch.Start();
    Buffer.BlockCopy(buffer, 0, copy, 0, length);
    stopwatch.Stop();

    blockCopyTotal += stopwatch.Elapsed;

    stopwatch.Reset();

    stopwatch.Start();
    Array.Copy(buffer, 0, copy, 0, length);
    stopwatch.Stop();

    arrayCopyTotal += stopwatch.Elapsed;

    stopwatch.Reset();
}

Console.WriteLine("bufferLength: {0}", length);
Console.WriteLine("BlockCopy: {0}", blockCopyTotal);
Console.WriteLine("ArrayCopy: {0}", arrayCopyTotal);
Console.WriteLine("BlockCopy (average): {0}", TimeSpan.FromMilliseconds(blockCopyTotal.TotalMilliseconds / times));
Console.WriteLine("ArrayCopy (average): {0}", TimeSpan.FromMilliseconds(arrayCopyTotal.TotalMilliseconds / times));

Contoh Keluaran:

bufferLength: 396011520
BlockCopy: 00:00:02.0441855
ArrayCopy: 00:00:01.8876299
BlockCopy (average): 00:00:00.1020000
ArrayCopy (average): 00:00:00.0940000
Kevin
sumber
1
Maaf tentang jawaban ini lebih dari komentar, tapi terlalu panjang untuk komentar. Karena konsensus tampaknya Buffer.BlockCopy lebih baik untuk kinerja, saya pikir semua orang harus sadar bahwa saya tidak dapat mengkonfirmasi konsensus itu dengan pengujian.
Kevin
10
Saya pikir ada masalah dengan metodologi pengujian Anda. Sebagian besar perbedaan waktu yang Anda catat adalah hasil dari aplikasi yang berputar, menyimpan cache sendiri, menjalankan JIT, hal semacam itu. Cobalah dengan buffer yang lebih kecil, tetapi beberapa ribu kali; dan kemudian ulangi seluruh pengujian dalam satu loop setengah lusin kali, dan hanya perhatikan proses terakhir. Pengujian saya sendiri memiliki Buffer.BlockCopy () yang berjalan mungkin 5% lebih cepat daripada Array.Copy () untuk array 640 byte. Tidak lebih cepat, tapi sedikit.
Ken Smith
2
Saya mengukur hal yang sama untuk masalah tertentu, saya tidak melihat perbedaan performa antara Array.Copy () dan Buffer.BlockCopy () . Jika ada, BlockCopy memperkenalkan unsafey yang sebenarnya mematikan aplikasi saya dalam satu contoh.
gatopeich
1
Sama seperti menambahkan Array.Copy mendukung long untuk posisi sumber sehingga membobol array byte besar tidak akan membuang pengecualian di luar jangkauan.
Alxwest
2
Berdasarkan tes yang baru saja saya buat ( bitbucket.org/breki74/tutis/commits/… ) Saya akan mengatakan tidak ada perbedaan kinerja praktis antara kedua metode saat Anda berurusan dengan array byte.
Igor Brejc
4

ArrayCopy lebih pintar dari BlockCopy. Ini mencari cara untuk menyalin elemen jika sumber dan tujuan adalah array yang sama.

Jika kita mengisi array int dengan 0,1,2,3,4 dan menerapkan:

Array.Copy (array, 0, array, 1, array.Length - 1);

kami berakhir dengan 0,0,1,2,3 seperti yang diharapkan.

Coba ini dengan BlockCopy dan kami mendapatkan: 0,0,2,3,4. Jika saya menetapkanarray[0]=-1 setelah itu, itu menjadi -1,0,2,3,4 seperti yang diharapkan, tetapi jika panjang array genap, seperti 6, kita mendapatkan -1,256,2,3,4,5. Barang berbahaya. Jangan gunakan BlockCopy selain untuk menyalin satu array byte ke yang lain.

Ada kasus lain di mana Anda hanya dapat menggunakan Array.Copy: jika ukuran array lebih panjang dari 2 ^ 31. Array.Copy memiliki kelebihan beban dengan longparameter ukuran. BlockCopy tidak memiliki itu.

pengguna3523091
sumber
2
Hasil tes Anda dengan BlockCopy tidak terduga. Itu karena Salinan blok mencoba menyalin potongan data pada satu waktu daripada satu byte pada satu waktu. Pada sistem 32 bit ia menyalin 4 byte sekaligus, pada sistem 64 bit menyalin 8 byte sekaligus.
Pharap
Jadi diharapkan perilaku tidak terdefinisi.
binki
2

Untuk mempertimbangkan argumen ini, jika seseorang tidak berhati-hati dalam membuat tolok ukur ini, mereka dapat dengan mudah disesatkan. Saya menulis tes yang sangat sederhana untuk menggambarkan hal ini. Dalam pengujian saya di bawah ini jika saya menukar urutan pengujian saya antara memulai Buffer.BlockCopy terlebih dahulu atau Array. Salin yang berjalan lebih dulu hampir selalu paling lambat (meskipun mendekati). Ini berarti untuk banyak alasan yang saya tidak akan membahas hanya menjalankan tes beberapa kali esp satu demi satu tidak akan memberikan hasil yang akurat.

Saya terpaksa mempertahankan tes seperti dengan 1000000 percobaan masing-masing untuk array 1000000 ganda berurutan. Namun, saya kemudian mengabaikan 900000 siklus pertama dan rata-rata sisanya. Dalam hal ini Buffer lebih unggul.

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

Thulani Chivandikwa
sumber
5
Saya tidak melihat hasil waktu apa pun dalam jawaban Anda. Harap sertakan keluaran konsol.
ToolmakerSteve
0

Hanya ingin menambahkan kasus pengujian saya yang menunjukkan lagi BlockCopy tidak memiliki manfaat 'PERFORMANCE' atas Array.Copy. Mereka tampaknya memiliki kinerja yang sama dalam mode rilis pada mesin saya (keduanya membutuhkan waktu sekitar 66ms untuk menyalin 50 juta bilangan bulat). Di bawah mode debug, BlockCopy hanya sedikit lebih cepat.

    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);
    }
stt106
sumber
3
Jangan tersinggung tetapi hasil tes Anda tidak terlalu membantu;) Pertama-tama "20ms lebih cepat" tidak memberi tahu Anda apa pun tanpa mengetahui waktu keseluruhan. Anda juga melakukan kedua tes itu dengan cara yang sangat berbeda. Kasus BlockCopy memiliki panggilan metode tambahan dan alokasi array target Anda yang tidak Anda miliki dalam kasus Array.Copy Anda. Karena fluktuasi multithreading (kemungkinan sakelar tugas, sakelar inti), Anda dapat dengan mudah mendapatkan hasil yang berbeda setiap kali menjalankan pengujian.
Bunny83
@ Bunny83 terima kasih atas komentarnya. Saya telah sedikit mengubah lokasi pengatur waktu yang seharusnya memberikan perbandingan yang lebih adil sekarang. Dan saya sedikit terkejut bahwa blockcopy sama sekali tidak lebih cepat dari array.copy.
stt106