Array.Copy ve Buffer.BlockCopy


124

Array.Copy ve Buffer.BlockCopy aynı şeyi yapar, ancak BlockCopyhızlı bayt düzeyinde ilkel dizi kopyalamayı Copyhedeflerken, genel amaçlı uygulama. Sorum şu - hangi durumlarda kullanmalısınız BlockCopy? İlkel tür dizileri kopyalarken herhangi bir zamanda mı kullanmalısınız yoksa yalnızca performans için kodlama yapıyorsanız mı kullanmalısınız? Buffer.BlockCopyOver kullanmanın doğası gereği tehlikeli bir şey var mı Array.Copy?


3
Marshal.Copy:-) unutma . Peki, Array.Copybaşvuru türleri, karmaşık değer türleri ve tür değişmezse, Buffer.BlockCopydeğer türleri, bayt dizileri ve bayt büyüsü arasında "dönüştürme" için kullanın. F.ex. StructLayoutne yaptığınızı biliyorsanız ile kombinasyon oldukça güçlüdür. Performansa gelince, yönetilmeyen bir çağrı gibi görünüyor memcpy/ cpblkbunun için en hızlısı - bkz. Code4k.blogspot.nl/2010/10/… .
atlaste

1
İle bazı kıyaslama testleri yaptım byte[]. Sürüm versiyonunda bir fark yoktu. Bazen Array.Copy, bazen Buffer.BlockCopy(hafifçe) daha hızlı.
Bitterblue

Yeni kapsamlı cevap aşağıda yayınlandı. Küçük arabellek boyutlarının olduğu durumlarda, açık döngü kopyalamanın genellikle en iyisi olduğunu unutmayın.
Özel Sos

Her zaman aynı şeyi yaptıklarını sanmıyorum - örneğin
İnts

Array.Copydaha ziyade özelleştirilmiş bir versiyondur - örneğin sadece aynı sıra dizilerini kopyalayabilir.
astrowalker

Yanıtlar:


59

Parametreler Buffer.BlockCopyindeks tabanlı değil bayt tabanlı olduğundan, kodunuzu kullanmaktan çok bozma olasılığınız daha yüksektir Array.Copy, bu nedenle kodumun yalnızca Buffer.BlockCopyperformans açısından kritik bir bölümünde kullanırım.


9
Kesinlikle katılmak. Buffer.BlockCopy'de hata için çok fazla yer var. Basit tutun ve meyve suyunun nerede olduğunu öğrenene kadar (profil oluşturma) programınızdan herhangi bir meyve suyu sıkmaya çalışmayın.
Stephen

5
Ya bir bayt [] ile uğraşıyorsanız? BlockCopy'de başka sorun var mı?
thecoop

4
@thecoop: Eğer bir bayt [] ile uğraşıyorsanız, "bayt" tanımı daha sonra bir bayttan başka bir şeye değiştirilmedikçe, muhtemelen diğer kısımları üzerinde oldukça olumsuz bir etkiye sahip olacaksa, BlockCopy'yi kullanmak muhtemelen iyidir. yine de kodunuz. :) Diğer tek potansiyel sorun, BlockCopy'nin sadece düz baytlar yapmasıdır, bu yüzden sonu hesaba katılmaz, ancak bu yalnızca Windows olmayan bir makinede devreye girer ve yalnızca kodu ilk sırada. Ayrıca, mono kullanıyorsanız bazı garip farklar olabilir.
MusiGenesis

6
Kendi testimde, Array.Copy () performans açısından Buffer.BlockCopy () 'ye çok benzer. Buffer.BlockCopy, 640 eleman bayt dizisi ile uğraşırken (en çok ilgilendiğim tür olan) benim için sürekli olarak% 10'dan daha hızlı. Ancak kendi verilerinizle kendi testinizi yapmalısınız, çünkü muhtemelen verilere, veri türlerine, dizi boyutlarına vb. Bağlı olarak değişecektir. Her iki yöntemin de Array.Clone () kullanmaktan yaklaşık 3 kat daha hızlı olduğunu ve belki de bir for döngüsüne kopyalamaktan 20 kat daha hızlı olduğunu not etmeliyim.
Ken Smith

3
@KevinMiller: uh, UInt16eleman başına iki bayttır. Bu diziyi BlockCopy'ye dizideki eleman sayısıyla birlikte geçirirseniz, elbette dizinin sadece yarısı kopyalanacaktır. Bunun düzgün çalışması için , uzunluk parametresi olarak eleman sayısı çarpı her bir elemanın (2) boyutu geçmeniz gerekir . msdn.microsoft.com/en-us/library/… ve INT_SIZEörneklerde arayın .
MusiGenesis

129

başlangıç

Partiye geç katılıyorum, ancak 32 bin görüşle bunu doğru yapmaya değer. Şimdiye kadar gönderilen yanıtlardaki mikro kıyaslama kodlarının çoğu, bellek ayırmalarını test döngülerinden çıkarmamak (ciddi GC yapaylıkları ortaya çıkarır), değişkene karşı deterministik yürütme akışlarını test etmemek, JIT ısınması dahil olmak üzere bir veya daha fazla ciddi teknik kusurdan muzdariptir. ve test içi değişkenliği izlemiyor. Ek olarak, çoğu yanıt, değişen tampon boyutlarının ve değişen ilkel türlerin (32 bit veya 64 bit sistemlere göre) etkilerini test etmedi. Bu soruyu daha kapsamlı bir şekilde ele almak için, bunu, yaygın "sorunların" çoğunu mümkün olduğu kadar azaltan, geliştirdiğim özel bir mikro kıyaslama çerçevesine bağladım. Testler, hem 32 bit makinede hem de 64 bit makinede .NET 4.0 Sürüm modunda çalıştırıldı. Sonuçların ortalaması, her çalıştırmada yöntem başına 1 milyon deneme içeren 20 test çalışmasının ortalaması alınmıştır. Test edilen ilkel türlerbyte(1 bayt), int(4 bayt) ve double(8 bayt). Üç yöntem test edildi: Array.Copy(), Buffer.BlockCopy()bir döngü içinde, ve basit başına endeksi atama. Veriler buraya gönderilemeyecek kadar büyük, bu yüzden önemli noktaları özetleyeceğim.

Çıkarımlar

  • Arabellek uzunluğunuz yaklaşık 75-100 veya daha azsa, açık bir döngü kopyalama yordamı genellikle hem 32 bit hem de 64 bit makinelerde test edilen 3 ilkel türden biri Array.Copy()veya Buffer.BlockCopy()hepsinden daha hızlıdır (yaklaşık% 5 oranında) . Ek olarak, açık döngü kopya rutini, iki alternatife kıyasla performansta önemli ölçüde daha düşük değişkenliğe sahiptir. İyi performans, neredeyse kesin olarak , CPU L1 / L2 / L3 bellek önbelleği tarafından hiçbir yöntem çağrısı ek yükü olmadan kullanılan referans yerelliğinden kaynaklanmaktadır .
    • İçin doubletampon 32 bit makinelerde yalnızca : açık döngü kopya rutin tüm tampon için her iki alternatifleri 100k kadar test boyutları daha iyidir. İyileşme diğer yöntemlere göre% 3-5 daha iyidir. Bunun nedeni, yerel 32 bit genişliğini geçtikten sonra performansının tamamen düşmesi Array.Copy()ve Buffer.BlockCopy()azalmasıdır. Bu nedenle, aynı etkinin longtamponlar için de geçerli olacağını varsayıyorum .
  • ~ 100'ü aşan arabellek boyutları için, açık döngü kopyalama diğer 2 yöntemden çok daha yavaş hale gelir (az önce belirtilen özel bir istisna dışında). Fark en çok byte[], açık döngü kopyalamanın büyük arabellek boyutlarında 7x veya daha yavaş olabileceği durumlarda belirgindir.
  • Genel olarak, test edilen 3 ilkel türün tümü için ve tüm tampon boyutlarında Array.Copy()ve Buffer.BlockCopy()neredeyse aynı şekilde gerçekleştirildi. Ortalama olarak, Array.Copy()yaklaşık% 2 veya daha az zaman gibi çok hafif bir kenar var gibi görünmektedir (ancak% 0,2 -% 0,5 daha iyi tipiktir), ancak Buffer.BlockCopy()ara sıra onu geçmiştir. Bilinmeyen nedenlerden dolayı, Buffer.BlockCopy()test içi değişkenliğe göre belirgin şekilde daha yüksek Array.Copy(). Birden fazla hafifletme denememe ve neden olduğuna dair uygulanabilir bir teoriye sahip olmama rağmen bu etki ortadan kaldırılamadı.
  • Çünkü Array.Copy()çok biraz daha hızlı olması ve ortalama olarak daha az değişkenliği sahip olmanın yanı sıra bir "akıllı" daha genel ve daha güvenli bir yöntem olduğunu, bu tercih edilmelidir Buffer.BlockCopy()hemen hemen tüm yaygın vakalarda. Önemli Buffer.BlockCopy()ölçüde daha iyi olacağı tek kullanım durumu , kaynak ve hedef dizi değer türlerinin farklı olduğu zamandır (Ken Smith'in cevabında belirtildiği gibi). Bu senaryo yaygın olmamakla Array.Copy()birlikte, doğrudan yayınlamaya kıyasla sürekli "güvenli" değer türü atama nedeniyle burada çok kötü performans gösterebilir Buffer.BlockCopy().
  • Aynı tür dizi kopyalamadan Array.Copy()daha hızlı olan StackOverflow dışından ek kanıtlar buradaBuffer.BlockCopy() bulunabilir .

Olarak bir kenara, o da .NET olduğunda 100 dizisi uzunluğunun etrafında olduğu ortaya çıktı Array.Clear()(ayarı bir dizinin açık bir döngü atama açıklığa atmaya başlar ilk false, 0ya da null). Bu, yukarıdaki benzer bulgularımla tutarlıdır. Bu ayrı kriterler çevrimiçi olarak burada keşfedildi: manski.net/2012/12/net-array-clear-vs-arrayx-0-performance
Özel Sos

Tampon boyutu dediğinizde; Bayt mı yoksa eleman sayısı mı demek istiyorsunuz?
dmarra

Yukarıdaki cevabımda, hem "tampon uzunluğu" hem de "tampon boyutu" genellikle öğe sayısını ifade eder.
Special Sauce

Sık sık 8 baytlık veriyi 5 baytlık bir kaynak uzaklığından okuyarak bir arabelleğe kopyalamam gereken bir örneğim var. Açık döngü kopyasının, Buffer.BlockCopy veya Array.Copy kullandıktan sonra önemli ölçüde daha hızlı olduğunu buldum. Loop Results for 1000000 iterations 17.9515ms. Buffer.BlockCopy Results for 1000000 iterations 39.8937ms. Array.Copy Results for 1000000 iterations 45.9059ms Ancak, kopya boyutu> ~ 20 bayt ise, açık döngü önemli ölçüde daha yavaştır.
Tod Cunningham

@TodCunningham, 8 bayt veri? Uzun eşdeğer mi demek istiyorsun? Ya tek bir elemanı çevirin ve kopyalayın (çok hızlı) ya da sadece bu döngüyü manuel olarak açın.
astrowalker

67

Kullanmanın mantıklı olduğu başka bir örnek, Buffer.BlockCopy()bir dizi ilkel (örneğin, şortlar) sağlandığında ve bunu bir bayt dizisine dönüştürmeniz gerektiğidir (örneğin, bir ağ üzerinden iletim için). Silverlight AudioSink'ten gelen sesle uğraşırken bu yöntemi sıklıkla kullanıyorum. Örneği bir short[]dizi olarak sağlar, ancak byte[]gönderdiğiniz paketi oluştururken onu bir diziye dönüştürmeniz gerekir Socket.SendAsync(). Kullanabilir BitConverterve diziyi tek tek yineleyebilirsiniz, ancak bunu yapmak için çok daha hızlı (testlerimde yaklaşık 20x):

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

Ve aynı numara ters yönde de çalışır:

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

Bu, güvenli C # ' (void *)da C ve C ++' da çok yaygın olan bellek yönetimine ne kadar yakın olursanız olun .


6
Bu harika bir fikir - hiç endianness ile ilgili sorunlarla karşılaştınız mı?
Phillip

Evet, senaryonuza bağlı olarak bu problemle karşılaşabileceğinizi düşünüyorum. Kendi senaryolarım tipik olarak ya (a) aynı makinedeki bayt dizileri ve kısa diziler arasında ileri geri geçiş yapmam gerekir ya da (b) verilerimi aynı makineye gönderdiğimi biliyorum. endianness ve uzak tarafı kontrol ettiğim. Ancak uzak makinenin verinin ana bilgisayar siparişi yerine ağ sırasına göre gönderilmesini beklediği bir protokol kullanıyorsanız, evet, bu yaklaşım size problemler verecektir.
Ken Smith

Ken'in blogunda BlockCopy hakkında bir makalesi de var: blog.wouldbetheologian.com/2011/11/…
Drew Noakes

4
Net Core 2.1'den beri bunu kopyalamadan yapabileceğinizi unutmayın. MemoryMarshal.AsBytes<T>ya da MemoryMarshal.Cast<TFrom, TTo>bir ilkel dizinizi başka bir ilkelin dizisi olarak yorumlamanıza izin verin.
Timo

16

Testlerime göre, performans Array.Copy yerine Buffer.BlockCopy'yi tercih etmek için bir neden değil . Array.Copy testime göre, aslında Buffer.BlockCopy'den daha hızlı .

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

Örnek Çıktı:

bufferLength: 396011520
BlockCopy: 00:00:02.0441855
ArrayCopy: 00:00:01.8876299
BlockCopy (average): 00:00:00.1020000
ArrayCopy (average): 00:00:00.0940000

1
Bu cevabın daha çok bir yorum olduğu için üzgünüm, ancak bir yorum için çok uzundu. Konsensüs, Buffer.BlockCopy'nin performans için daha iyi olduğu gibi göründüğünden, herkesin bu fikir birliğini test ederek onaylayamadığımın farkında olması gerektiğini düşündüm.
Kevin

10
Sanırım test metodolojinizle ilgili bir sorun var. Fark ettiğiniz zaman farkının çoğu, uygulamanın dönmesi, kendini önbelleğe alması, JIT'yi çalıştırması gibi şeylerin sonucudur. Daha küçük bir tamponla deneyin, ancak birkaç bin kez; ve sonra tüm testi bir döngü içinde yarım düzine kez tekrarlayın ve yalnızca son çalıştırmaya dikkat edin. Kendi testimde Buffer.BlockCopy (), 640 baytlık diziler için Array.Copy () 'den belki% 5 daha hızlı çalışıyor. Çok daha hızlı değil, biraz.
Ken Smith

2
Aynısını belirli bir problem için ölçtüm, Array.Copy () ve Buffer.BlockCopy () arasında performans farkı göremedim . Bir şey olursa, BlockCopy uygulamamı bir seferde gerçekten öldüren güvenli olmayanları tanıttı .
gatopeich

1
Tıpkı Array.Copy'yi eklemek gibi, kaynak konumu için uzun destekler, bu nedenle büyük bayt dizilerini kırmak , aralık dışı bir istisna oluşturmaz.
Alxwest

2
Az önce yaptığım testlere dayanarak ( bitbucket.org/breki74/tutis/commits/… ) Bayt dizileriyle uğraşırken iki yöntem arasında pratik bir performans farkı olmadığını söyleyebilirim.
Igor Brejc

4

ArrayCopy, BlockCopy'den daha akıllıdır. Kaynak ve hedef aynı dizi ise öğelerin nasıl kopyalanacağını anlar.

Bir int dizisini 0,1,2,3,4 ile doldurup uygularsak:

Array.Copy (dizi, 0, dizi, 1, dizi.Uzunluğu - 1);

Beklendiği gibi 0,0,1,2,3 ile sonuçlanırız.

Bunu BlockCopy ile deneyin ve şunu elde ederiz: 0,0,2,3,4. Bundan array[0]=-1sonra atarsam, beklendiği gibi -1,0,2,3,4 olur, ancak dizi uzunluğu çift ise, 6 gibi, -1,256,2,3,4,5 elde ederiz. Tehlikeli şeyler. Bir bayt dizisini diğerine kopyalamak dışında BlockCopy kullanmayın.

Yalnızca Array.Copy'yi kullanabileceğiniz başka bir durum daha vardır: dizi boyutu 2 ^ 31'den uzunsa. Array.Copy, longboyut parametresine sahip bir aşırı yüklemeye sahip . BlockCopy buna sahip değil.


2
BlockCopy ile testlerinizin sonuçları beklenmedik değildir. Bunun nedeni, Blok Kopyalama'nın bir seferde bir bayt yerine bir seferde veri yığınlarını kopyalamaya çalışmasıdır. 32 bitlik bir sistemde bir seferde 4 baytı, 64 bitlik bir sistemde bir seferde 8 baytı kopyalar.
Pharap

Yani beklenen tanımlanmamış davranış.
binki

2

Bu argümana ağırlık verecek olursak, bu kıyaslamayı nasıl yazdıklarına dikkat edilmezse, kolaylıkla yanıltılabilirler. Bunu göstermek için çok basit bir test yazdım. Aşağıdaki testimde, testlerimin sırasını önce Buffer.BlockCopy veya Array.Copy arasında değiştirirsem, ilk giden neredeyse her zaman en yavaş olanıdır (yakın olmasına rağmen). Bu, basitçe testleri birden çok kez çalıştırmaya girmeyeceğim bir dizi nedenden dolayı, özellikle birbiri ardına doğru sonuçlar vermeyeceği anlamına gelir.

Testi, 1000000 ardışık çift dizisi için 1000000 denemede olduğu gibi sürdürmeye başvurdum. Bununla birlikte, daha sonra ilk 900000 döngüyü göz ardı edip geri kalanın ortalamasını alıyorum. Bu durumda Tampon üstündür.

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


5
Cevabınızda herhangi bir zamanlama sonucu göremiyorum. Lütfen konsol çıktısını ekleyin.
ToolmakerSteve

0

BlockCopy'nin Array.Copy'ye göre 'PERFORMANS' avantajı olmadığını tekrar gösteren test durumumu eklemek istiyorum. Makinemde yayın modunda aynı performansa sahip görünüyorlar (her ikisi de 50 milyon tamsayıyı kopyalamak için yaklaşık 66 ms sürüyor). Hata ayıklama modunda, BlockCopy marjinal olarak daha hızlıdır.

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

3
Alınma ama test sonucunuz gerçekten yardımcı olmuyor;) Öncelikle "20 ms daha hızlı", toplam süreyi bilmeden size hiçbir şey söylemez. Ayrıca bu iki testi çok farklı bir şekilde yaptınız. BlockCopy durumunda ek bir yöntem çağrısı ve Array.Copy durumunuzda olmayan hedef dizinizin tahsisi vardır. Çok iş parçacıklı dalgalanmalar nedeniyle (olası görev anahtarı, temel anahtar) testi her çalıştırdığınızda kolayca farklı sonuçlar elde edebilirsiniz.
Bunny83

@ Bunny83 yorum için teşekkürler. Şimdi daha adil bir karşılaştırma yapması gereken zamanlayıcı konumunu biraz değiştirdim. Ve blok kopyanın array.copy'den daha hızlı olmamasına biraz şaşırdım.
stt106
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.