Array.CopyとBuffer.BlockCopy


124

Array.CopyBuffer.BlockCopyはどちらも同じことBlockCopyを行いますCopyが、汎用の実装であるのに対して、バイトレベルのプリミティブ配列の高速コピーを目的としています。私の質問は-どのような状況で使用する必要がありますBlockCopyか?プリミティブ型の配列をコピーしているときはいつでも使用する必要がありますか、それともパフォーマンスのためにコーディングしている場合にのみ使用する必要がありますか?Buffer.BlockCopyoverの使用に関して本質的に危険なものはありArray.Copyますか?


3
Marshal.Copy:-)をお忘れなく。まあ、Array.Copy参照型、複雑な値型、および型が変わらない場合は、Buffer.BlockCopy値型、バイト配列、およびバイトマジック間の「変換」に使用します。F.ex. との組み合わせStructLayoutは、何をしているのかわかっている場合は非常に強力です。パフォーマンスに関しては、管理されていないmemcpy/ への呼び出しがcpblk最も速いようです-code4k.blogspot.nl/2010/10/…を参照してください。
atlaste

1
でいくつかのベンチマークテストを行いましたbyte[]。リリースバージョンに違いはありませんでした。時々Array.Copy、時々Buffer.BlockCopy(わずかに)速い。
Bitterblue 14

以下に投稿された新しい包括的な答え。バッファサイズが小さい場合、通常は明示的なループコピーが最適です。
2015年

私は、彼らが常に同じことを行うのですかとは思わない-あなたは、インスタンスのためにバイトの配列にintの配列をコピーするArray.Copyを使用することはできません
mcmillab

Array.Copyはかなり特殊なバージョンです。たとえば、同じランクの配列のみをコピーできます。
astrowalker 2018年

回答:


59

へのパラメーターBuffer.BlockCopyはインデックスベースではなくバイトベースであるためArray.Copy、を使用する場合よりもコードをめちゃくちゃにする可能性が高いのでBuffer.BlockCopy、コードのパフォーマンスが重要なセクションでのみ使用します。


9
完全に同意します。Buffer.BlockCopyにはエラーの余地が多すぎます。シンプルに保ち、ジュースの場所がわかるまで(プロファイリング)、プログラムからジュースを絞り出さないようにしてください。
スティーブン

5
byte []を処理している場合はどうでしょうか?BlockCopyの他の落とし穴はありますか?
thecoop 2009

4
@thecoop:byte []を処理している場合、「バイト」の定義が後でバイト以外の何かに変更されない限り、BlockCopyを使用しても問題ないでしょう。とにかくあなたのコード。:)その他の唯一の潜在的な問題は、BlockCopyが単純なバイトを実行するだけであるため、エンディアンを考慮に入れていないことですが、これはWindows以外のマシンでのみ、コードを台無しにした場合にのみ機能します。そもそも。また、monoを使用している場合は、奇妙な違いが生じる可能性があります。
MusiGenesis 2009

6
私自身のテストでは、Array.Copy()のパフォーマンスはBuffer.BlockCopy()と非常に似ています。Buffer.BlockCopyは、640要素のバイト配列を処理するときに、一貫して<10%高速です(これは、私が最も興味を持っている種類です)。ただし、データ、データ型、配列サイズなどによって異なると考えられるため、独自のデータを使用して独自のテストを行う必要があります。どちらの方法も、Array.Clone()を使用するよりも約3倍高速であり、forループでコピーするよりもおそらく20倍高速であることに注意してください。
ケン・スミス

3
@KevinMiller:ええと、UInt16要素あたり2バイトです。この配列を配列の要素数とともにBlockCopyに渡すと、当然配列の半分だけがコピーされます。これが正しく動作するためには、要素の数合格する必要があるだろうの長さのパラメータとして各要素(2)の大きさを。msdn.microsoft.com/en-us/library/…そしてINT_SIZE、例で検索します。
MusiGenesis

129

プレリュード

私はパーティーに遅く参加しますが、32kのビューがあるため、これを正しく行う価値があります。これまでに投稿された回答のマイクロベンチマークコードのほとんどは、メモリ割り当てをテストループから外さない(これにより、重大なGCアーティファクトが発生する)、変数と決定論的な実行フローのテストを行わない、JITウォームアップなど、1つ以上の重大な技術的欠陥に悩まされています。テスト内の変動を追跡しません。さらに、ほとんどの回答では、さまざまなバッファーサイズとさまざまなプリミティブタイプ(32ビットまたは64ビットシステムに関して)の影響をテストしていませんでした。この問題にさらに包括的に対処するために、私が開発したカスタムマイクロベンチマークフレームワークに接続して、一般的な「落とし穴」のほとんどを可能な限り減らしました。テストは、32ビットマシンと64ビットマシンの両方で.NET 4.0リリースモードで実行されました。結果は20回のテスト実行で平均され、各実行ではメソッドごとに100万回の試行が行われました。テストされたプリミティブ型はbyte(1バイト)、int(4バイト)、およびdouble(8バイト)。3つの方法が試験された:Array.Copy()Buffer.BlockCopy()ループ内で、かつシンプルごとのインデックスの割り当て。データが多すぎてここに投稿できないので、重要なポイントを要約します。

テイクアウェイ

  • バッファー長が75〜100以下の場合、明示的なループコピールーチンは、通常、32ビットマシンと64ビットマシンの両方でテストされた3つのプリミティブタイプのいずれArray.Copy()かよりも高速(約5%)ですBuffer.BlockCopy()。さらに、明示的なループコピールーチンは、2つの方法に比べてパフォーマンスのばらつきが著しく低くなっています。メソッド呼び出しのオーバーヘッドがなく、CPU L1 / L2 / L3メモリキャッシングによって参照の局所性が活用されているため、パフォーマンスはほぼ間違いなく良好です。
    • 32ビットマシンのdoubleバッファの場合のみ 100kまでテストされたすべてのバッファサイズについて、明示的なループコピールーチンは両方の方法より優れています。他の方法よりも3〜5%改善されています。これは、ネイティブの32ビット幅を渡すArray.Copy()と、パフォーマンスがBuffer.BlockCopy()低下し、完全に低下するためです。したがって、同じ効果がlongバッファにも適用されると思います。
  • バッファサイズが100を超える場合、明示的なループコピーは他の2つの方法よりも大幅に遅くなります(1つの特定の例外を除きます)。違いはで最も顕著でbyte[]、明示的なループコピーは、大きなバッファーサイズで7倍以上遅くなる可能性があります。
  • 一般に、テストされた3つのプリミティブタイプすべてとすべてのバッファーサイズでArray.Copy()Buffer.BlockCopy()ほぼ同じように実行されました。平均Array.Copy()して、所要時間は約2%以下の非常にわずかなエッジがあるようです(ただし、0.2%-0.5%が一般的です)Buffer.BlockCopy()。未知の理由により、Buffer.BlockCopy()はテストよりも著しくテスト内変動が大きいArray.Copy()。この影響は、私が複数の緩和策を試してみて、その理由について運用可能な理論を持っていないにもかかわらず、取り除くことはできませんでした。
  • のでArray.Copy()「賢く」、より一般的な、そしてより安全な方法である、非常にわずかに速くていると、平均的に少ない変動を有することに加えて、それがより好まれるべきでBuffer.BlockCopy()、ほぼすべての一般的なケースで。Buffer.BlockCopy()(Ken Smithの回答で指摘されているように)ソース配列とデスティネーション配列の値の型が異なる場合のみ、大幅に改善されます。このシナリオは一般的ではありませんArray.Copy()が、の直接キャストと比較して、継続的な「安全な」値タイプのキャストにより、ここではパフォーマンスが非常に低下する可能性がありBuffer.BlockCopy()ます。
  • 同じタイプの配列のコピーArray.Copy()よりも高速なStackOverflowの外部からの追加の証拠は、こちらにありますBuffer.BlockCopy()

余談として、それはまた、.NETの場合は、100の配列長の周りであることが判明したArray.Clear()最初の(に設定するアレイの明示的なループ割り当てクリアビートを開始するfalse0またはnull)です。これは、上記の同様の結果と一致しています。これらの個別のベンチマークは、オンラインでここで発見されました:manski.net/2012/12/net-array-clear-vs-arrayx-0-performance
Special Sauce

バッファサイズと言うと、バイト、または要素数の意味ですか?
dmarra 2016

上記の私の回答では、「バッファの長さ」と「バッファサイズ」はどちらも一般的に要素数を指します。
2016年

私は、約8バイトのデータを、5バイトずつオフセットされたソースから読み取るバッファーに頻繁にコピーする必要がある例を持っています。明示的なループコピーは、Buffer.BlockCopyまたは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 ただし、コピーサイズが20バイトを超える場合、明示的なループは大幅に遅くなります。
トッドカニンガム

@ TodCunningham、8バイトのデータ?長い等価物ですか?単一の要素をキャストしてコピーする(非常に高速)か、そのループを手動でアンロールします。
astrowalker

67

使用する意味がある別の例 Buffer.BlockCopy()、プリミティブの配列(たとえば、shorts)が提供されており、それをバイトの配列に変換する必要がある場合(たとえば、ネットワーク経由での送信用)です。Silverlight AudioSinkからのオーディオを処理するときに、この方法を頻繁に使用します。これはサンプルをshort[]配列として提供しbyte[]ますが、送信するパケットを作成するときに、それを配列に変換する必要がありますSocket.SendAsync()。を使用しBitConverterて、配列を1つずつ反復処理することもできますが、これを行うだけの方がはるかに高速です(私のテストでは約20倍)。

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

そして同じトリックが逆にも機能します:

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

これは、安全なC#に入るのとほぼ同じです。 (void *)、CおよびC ++で一般的な種類のメモリ管理に。


6
これはクールなアイデアです。エンディアンの問題に遭遇したことはありますか?
フィリップ

ええ、シナリオによっては、この問題に遭遇する可能性があると思います。私自身のシナリオは、通常、(a)同じマシン上でバイト配列とショート配列を切り替える必要がある、または(b)データを同じマシンに送信していることを偶然に知っているエンディアン、そしてリモート側を制御します。しかし、リモートマシンがホスト順ではなくネットワーク順でデータが送信されることを期待するプロトコルを使用している場合、そうです、このアプローチは問題を引き起こします。
ケン・スミス

ケンはまた、彼のブログにBlockCopyに関する記事を掲載しています。 blog.wouldbetheologian.com/2011/11/...
ドリューNoakes

4
.Net Core 2.1以降、コピーせずにこれを実行できることに注意してください。 MemoryMarshal.AsBytes<T>またはMemoryMarshal.Cast<TFrom, TTo>、あるプリミティブのシーケンスを別のプリミティブのシーケンスとして解釈させます。
ティモ

16

私のテストによると、パフォーマンスはArray.CopyよりもBuffer.BlockCopyを選ぶ理由にはなりません。私のテストでは、Array.Copyは実際には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));

出力例:

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
この回答がコメントであることに申し訳ありませんが、コメントには長すぎます。合意はBuffer.BlockCopyの方がパフォーマンスに優れていると思われたので、私はテストでその合意を確認できなかったことに誰もが気付くべきだと思いました。
ケビン

10
テストの方法論に問題があると思います。あなたが気づいているほとんどの時間差は、アプリケーションのスピンアップ、それ自体のキャッシュ、JITの実行などの結果です。小さいバッファーで数千回試してください。その後、ループ内でテスト全体を半ダース回繰り返し、最後の実行にのみ注意を払います。私自身のテストでは、640バイトの配列の場合、Array.Copy()よりも5%高速に動作するBuffer.BlockCopy()を使用しています。それほど速くはありませんが、少しです。
ケン・スミス

2
特定の問題について同じことを測定しましたが、Array.Copy()とBuffer.BlockCopy()の間にパフォーマンスの違いは見られませんでした。どちらかと言えば、BlockCopyが危険をもたらし、実際に1つのインスタンスでアプリを強制終了しました
gatopeich

1
Array.Copyを追加するのと同じように、ソース位置のlongをサポートしているため、大きなバイト配列に分割しても、範囲外の例外はスローされません。
Alxwest 2012

2
私が作成したテスト(bitbucket.org/breki74/tutis/commits/…)に基づいて、バイト配列を処理する場合、2つの方法の間に実際的なパフォーマンスの違いはないと言えます。
Igor Brejc 2014年

4

ArrayCopyはBlockCopyよりもスマートです。ソースと宛先が同じ配列である場合に要素をコピーする方法を理解します。

int配列に0、1、2、3、4を入力して適用すると、次のようになります。

Array.Copy(配列、0、配列、1、配列。長さ-1);

期待どおりに0,0,1,2,3になります。

これをBlockCopyで試してみてください:0,0,2,3,4。割り当てた場合array[0]=-1その後と、期待どおり-1,0,2,3,4になりますが、配列の長さが6のように偶数の場合は、-1,256,2,3,4,5になります。危険なもの。1つのバイト配列を別のバイト配列にコピーする場合以外は、BlockCopyを使用しないでください。

Array.Copyのみを使用できる別のケースがあります:配列のサイズが2 ^ 31より大きい場合。Array.Copyには、longサイズパラメータを持つオーバーロードがあります。BlockCopyにはありません。


2
BlockCopyを使用したテストの結果は予想外ではありません。これは、ブロックコピーが一度に1バイトではなく、一度にデータのチャンクをコピーしようとするためです。32ビットシステムでは一度に4バイトをコピーし、64ビットシステムでは一度に8バイトをコピーします。
Pharap

予期されない未定義の動作です。
Binki 2015

2

この議論を検討するために、彼らがこのベンチマークをどのように作成したかについて注意深くなければ、彼らは簡単に誤解される可能性があります。私はこれを説明するために非常に簡単なテストを書きました。以下のテストでは、Buffer.BlockCopyを最初に開始するかArray.Copyを開始するかをテストの順序で入れ替えると、ほとんどの場合、最初の方が遅くなります(近いものです)。これは、さまざまな理由により、単純にテストを複数回実行するのではなく、次々と正確な結果が得られないことを意味します。

1000000の連続するdoubleの配列に対して、1000000試行ごとにテストをそのまま維持することに頼りました。しかし、私は最初の900000サイクルを無視し、残りを平均します。その場合、バッファは優れています。

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
回答にタイミングの結果はありません。コンソール出力を含めてください。
ToolmakerSteve

0

BlockCopyがArray.Copyよりも「パフォーマンス」のメリットがないことを再度示す私のテストケースを追加したいだけです。私のマシンのリリースモードでは、パフォーマンスは同じであるようです(5000万の整数をコピーするには、どちらも約66ミリ秒かかります)。デバッグモードでは、BlockCopyはわずかに高速です。

    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
問題はありませんが、テスト結果はあまり役に立ちません;)まず、「20ms速く」とは、全体の時間を知らなければ何もわかりません。また、これら2つのテストを非常に異なる方法で実行しました。BlockCopyケースには、追加のメソッド呼び出しと、Array.Copyケースにはないターゲット配列の割り当てがあります。マルチスレッドの変動(可能なタスクスイッチ、コアスイッチ)により、テストを実行するたびに簡単に異なる結果を得ることができます。
Bunny83 14年

@ Bunny83コメントをありがとう。タイマーの位置を少し変更して、より公平な比較ができるようにしました。そして、blockcopyがarray.copyよりも速くないことに少し驚いています。
stt106 2014年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.