99

ゼロ以外のbyte[]値を 1 つ入力する必要があります。配列内のそれぞれをループせずに C# でこれを行うにはどうすればよいですか?byte

更新:コメントはこれを2つの質問に分割したようです-

  1. に似ている可能性のあるbyte []を埋めるフレームワークメソッドはありますかmemset
  2. 非常に大きな配列を扱う場合、最も効率的な方法は何ですか?

エリックと他の人が指摘したように、単純なループを使用しても問題なく機能することに完全に同意します。質問のポイントは、C# について何か新しいことを学ぶことができるかどうかを確認することでした:) 並列操作に対するジュリエットの方法は、単純なループよりもさらに高速である必要があると思います。

ベンチマーク: Mikael Svenson に感謝: http://techmikael.blogspot.com/2009/12/filling-array-with-default-value.html

for安全でないコードを使用したくない場合を除き、単純なループが適していることがわかりました。

元の投稿で明確になっていないことをお詫びします。Eric と Mark のコメントはどちらも正しいです。確かに、より焦点を絞った質問が必要です。みんなの提案と応答に感謝します。

4

16 に答える 16

64

使用できますEnumerable.Repeat

byte[] a = Enumerable.Repeat((byte)10, 100).ToArray();

最初のパラメーターは繰り返したい要素で、2 番目のパラメーターはそれを繰り返す回数です。

これは小さな配列では問題ありませんが、非常に大きな配列を扱っていてパフォーマンスが懸念される場合は、ループ メソッドを使用する必要があります。

于 2009-12-13T20:02:52.153 に答える
23

Lucero's answerに基づいて、より高速なバージョンがここにあります。Buffer.BlockCopy反復ごとにコピーされるバイト数が 2 倍になります。興味深いことに、比較的小さな配列 (1000) を使用すると 10 倍のパフォーマンスを発揮しますが、より大きな配列 (1000000) ではその差はそれほど大きくありませんが、常に高速です。それの良いところは、小さな配列でもうまく機能することです。length = 100 付近で単純なアプローチよりも高速になります。100 万要素のバイト配列の場合、43 倍高速でした。(Intel i7、.Net 2.0 でテスト済み)

public static void MemSet(byte[] array, byte value) {
    if (array == null) {
        throw new ArgumentNullException("array");
    }

    int block = 32, index = 0;
    int length = Math.Min(block, array.Length);

    //Fill the initial array
    while (index < length) {
        array[index++] = value;
    }

    length = array.Length;
    while (index < length) {
        Buffer.BlockCopy(array, 0, array, index, Math.Min(block, length-index));
        index += block;
        block *= 2;
    }
}
于 2012-12-10T17:29:05.173 に答える
22

少し遅れていますが、次のアプローチは、安全でないコードに戻ることなく、適切な妥協案になる可能性があります。基本的に、従来のループを使用して配列の先頭を初期化し、次に に戻りますBuffer.BlockCopy()。これは、マネージ コールを使用して得られるのと同じくらい高速です。

public static void MemSet(byte[] array, byte value) {
  if (array == null) {
    throw new ArgumentNullException("array");
  }
  const int blockSize = 4096; // bigger may be better to a certain extent
  int index = 0;
  int length = Math.Min(blockSize, array.Length);
  while (index < length) {
    array[index++] = value;
  }
  length = array.Length;
  while (index < length) {
    Buffer.BlockCopy(array, 0, array, index, Math.Min(blockSize, length-index));
    index += blockSize;
  }
}
于 2010-03-25T19:40:22.203 に答える
16

Konradの回答が言及している指示System.Runtime.CompilerServices.Unsafe.InitBlockと同じことを今やるように見えます(彼はまた、ソースリンクについて言及しました)。OpCodes.Initblk

配列に入力するコードは次のとおりです。

byte[] a = new byte[N];
byte valueToFill = 255;

System.Runtime.CompilerServices.Unsafe.InitBlock(ref a[0], valueToFill, (uint) a.Length);
于 2018-01-08T14:54:26.330 に答える
12

パフォーマンスが重要な場合は、安全でないコードを使用し、配列へのポインターを直接操作することを検討できます。

別のオプションとして、msvcrt.dll から memset をインポートして使用することもできます。ただし、呼び出しによるオーバーヘッドは、速度の向上よりも簡単に大きくなる可能性があります。

于 2009-12-13T20:06:51.707 に答える
7

パフォーマンスが絶対的に重要な場合はEnumerable.Repeat(n, m).ToArray()、ニーズに対して遅すぎます。PLINQ またはTask Parallel Libraryを使用して、より高速なパフォーマンスを引き出すことができる場合があります。

using System.Threading.Tasks;

// ...

byte initialValue = 20;
byte[] data = new byte[size]
Parallel.For(0, size, index => data[index] = initialValue);
于 2009-12-14T16:25:56.693 に答える
6

すべての回答は 1 バイトのみを書き込んでいます。バイト配列に単語を入力したい場合はどうすればよいでしょうか。それとも浮きますか?私は時々それを使います。そのため、「memset」と同様のコードを一般的ではない方法で数回記述し、このページにたどり着いて単一バイトの適切なコードを見つけた後、以下のメソッドを記述しました。

PInvoke と C++/CLI にはそれぞれ欠点があると思います。そして、ランタイム 'PInvoke' を mscorxxx に入れてみませんか? Array.Copy と Buffer.BlockCopy は確かにネイティブ コードです。BlockCopy は「安全」でさえありません。long を別の途中までコピーしたり、配列内にある限り DateTime を超えてコピーしたりできます。

少なくとも、このようなことのために新しい C++ プロジェクトを提出するつもりはありません。それはほぼ確実に時間の無駄です。

基本的に、これは Lucero と TowerOfBricks によって提示されたソリューションの拡張バージョンであり、long、int など、およびシングルバイトを memset するために使用できます。

public static class MemsetExtensions
{
    static void MemsetPrivate(this byte[] buffer, byte[] value, int offset, int length) {
        var shift = 0;
        for (; shift < 32; shift++)
            if (value.Length == 1 << shift)
                break;
        if (shift == 32 || value.Length != 1 << shift)
            throw new ArgumentException(
                "The source array must have a length that is a power of two and be shorter than 4GB.", "value");

        int remainder;
        int count = Math.DivRem(length, value.Length, out remainder);

        var si = 0;
        var di = offset;
        int cx;
        if (count < 1) 
            cx = remainder;
        else 
            cx = value.Length;
        Buffer.BlockCopy(value, si, buffer, di, cx);
        if (cx == remainder)
            return;

        var cachetrash = Math.Max(12, shift); // 1 << 12 == 4096
        si = di;
        di += cx;
        var dx = offset + length;
        // doubling up to 1 << cachetrash bytes i.e. 2^12 or value.Length whichever is larger
        for (var al = shift; al <= cachetrash && di + (cx = 1 << al) < dx; al++) {
            Buffer.BlockCopy(buffer, si, buffer, di, cx);
            di += cx;
        }
        // cx bytes as long as it fits
        for (; di + cx <= dx; di += cx)
            Buffer.BlockCopy(buffer, si, buffer, di, cx);
        // tail part if less than cx bytes
        if (di < dx)
            Buffer.BlockCopy(buffer, si, buffer, di, dx - di);
    }
}

これがあれば、短いメソッドを追加して memset に必要な値の型を取得し、プライベート メソッドを呼び出すことができます。たとえば、このメソッドで ulong の置き換えを見つけるだけです。

    public static void Memset(this byte[] buffer, ulong value, int offset, int count) {
        var sourceArray = BitConverter.GetBytes(value);
        MemsetPrivate(buffer, sourceArray, offset, sizeof(ulong) * count);
    }

または、ばかげて、任意のタイプの構造体でそれを行います (ただし、上記の MemsetPrivate は、2 の累乗であるサイズにマーシャリングする構造体に対してのみ機能します)。

    public static void Memset<T>(this byte[] buffer, T value, int offset, int count) where T : struct {
        var size = Marshal.SizeOf<T>();
        var ptr = Marshal.AllocHGlobal(size);
        var sourceArray = new byte[size];
        try {
            Marshal.StructureToPtr<T>(value, ptr, false);
            Marshal.Copy(ptr, sourceArray, 0, size);
        } finally {
            Marshal.FreeHGlobal(ptr);
        }
        MemsetPrivate(buffer, sourceArray, offset, count * size);
    }

前に述べた initblk を変更して、コードとパフォーマンスを比較するために ulong を取得しましたが、それは黙って失敗しました。コードは実行されますが、結果のバッファーには ulong の最下位バイトのみが含まれます。

それにもかかわらず、for、initblk、および memset メソッドを使用して、大きなバッファーとして書き込むパフォーマンスを比較しました。バッファ長に収まる回数に関係なく、8 バイトの ulong を書き込む 100 回の繰り返しの合計時間はミリ秒です。for バージョンは、単一の ulong の 8 バイトに対して手動でループ展開されます。

Buffer Len  #repeat  For millisec  Initblk millisec   Memset millisec
0x00000008  100      For   0,0032  Initblk   0,0107   Memset   0,0052
0x00000010  100      For   0,0037  Initblk   0,0102   Memset   0,0039
0x00000020  100      For   0,0032  Initblk   0,0106   Memset   0,0050
0x00000040  100      For   0,0053  Initblk   0,0121   Memset   0,0106
0x00000080  100      For   0,0097  Initblk   0,0121   Memset   0,0091
0x00000100  100      For   0,0179  Initblk   0,0122   Memset   0,0102
0x00000200  100      For   0,0384  Initblk   0,0123   Memset   0,0126
0x00000400  100      For   0,0789  Initblk   0,0130   Memset   0,0189
0x00000800  100      For   0,1357  Initblk   0,0153   Memset   0,0170
0x00001000  100      For   0,2811  Initblk   0,0167   Memset   0,0221
0x00002000  100      For   0,5519  Initblk   0,0278   Memset   0,0274
0x00004000  100      For   1,1100  Initblk   0,0329   Memset   0,0383
0x00008000  100      For   2,2332  Initblk   0,0827   Memset   0,0864
0x00010000  100      For   4,4407  Initblk   0,1551   Memset   0,1602
0x00020000  100      For   9,1331  Initblk   0,2768   Memset   0,3044
0x00040000  100      For  18,2497  Initblk   0,5500   Memset   0,5901
0x00080000  100      For  35,8650  Initblk   1,1236   Memset   1,5762
0x00100000  100      For  71,6806  Initblk   2,2836   Memset   3,2323
0x00200000  100      For  77,8086  Initblk   2,1991   Memset   3,0144
0x00400000  100      For 131,2923  Initblk   4,7837   Memset   6,8505
0x00800000  100      For 263,2917  Initblk  16,1354   Memset  33,3719

initblk と memset の両方がヒットするため、毎回最初の呼び出しを除外しました。最初の呼び出しでは約 .22ms だったと思います。少し驚いたことに、私のコードは initblk よりも短いバッファーを埋める方が速く、ページの半分がセットアップ コードでいっぱいになっています。

誰かがこれを最適化したいと思うなら、本当に先に進んでください。それが可能だ。

于 2015-03-16T15:57:58.070 に答える
4

さまざまな回答で説明されているいくつかの方法をテストしました。c#テスト クラスでテストのソースを参照してください

ベンチマーク レポート

于 2016-03-14T19:16:38.770 に答える
3

配列を初期化するときにそれを行うことができますが、それはあなたが求めていることではないと思います:

byte[] myBytes = new byte[5] { 1, 1, 1, 1, 1};
于 2009-12-13T20:02:54.040 に答える