web-dev-qa-db-ja.com

C#のmemsetと同等のものは何ですか?

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

更新:コメントはこれを2つの質問に分けているようです-

  1. memsetに似ている可能性のあるbyte []を埋めるためのFrameworkメソッドはありますか
  2. 非常に大きな配列を扱っているときに最も効率的な方法は何ですか?

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

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

安全でないコードを使用したくない限り、単純なforループが道であることがわかります。

私の元の投稿で明確になっていないことをおologiesびします。エリックとマークはどちらもコメントで正しいです。より確実な質問をする必要があります。皆の提案と応答をありがとう。

78
Jedidja

Enumerable.Repeat

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

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

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

57
Mark Byers

実際、 Initblk英語版 )と呼ばれる既知のIL操作はほとんどありません。それで、「安全でない」ことを必要としない方法としてそれを使用しましょう。ヘルパークラスは次のとおりです。

public static class Util
{
    static Util()
    {
        var dynamicMethod = new DynamicMethod("Memset", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard,
            null, new [] { typeof(IntPtr), typeof(byte), typeof(int) }, typeof(Util), true);

        var generator = dynamicMethod.GetILGenerator();
        generator.Emit(OpCodes.Ldarg_0);
        generator.Emit(OpCodes.Ldarg_1);
        generator.Emit(OpCodes.Ldarg_2);
        generator.Emit(OpCodes.Initblk);
        generator.Emit(OpCodes.Ret);

        MemsetDelegate = (Action<IntPtr, byte, int>)dynamicMethod.CreateDelegate(typeof(Action<IntPtr, byte, int>));
    }

    public static void Memset(byte[] array, byte what, int length)
    {
        var gcHandle = GCHandle.Alloc(array, GCHandleType.Pinned);
        MemsetDelegate(gcHandle.AddrOfPinnedObject(), what, length);
        gcHandle.Free();
    }

    public static void ForMemset(byte[] array, byte what, int length)
    {
        for(var i = 0; i < length; i++)
        {
            array[i] = what;
        }
    }

    private static Action<IntPtr, byte, int> MemsetDelegate;

}

そして、パフォーマンスは何ですか?これは、Windows/.NETとLinux/Mono(異なるPC)に対する私の結果です。

Mono/for:     00:00:01.1356610
Mono/initblk: 00:00:00.2385835 

.NET/for:     00:00:01.7463579
.NET/initblk: 00:00:00.5953503

したがって、検討する価値があります。結果のILは検証できないことに注意してください。

41

少し遅れましたが、次のアプローチは安全でないコードに戻すことなく適切に妥協することができます。基本的には、従来のループを使用して配列の先頭を初期化し、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;
  }
}
21
Lucero

Luceroの答え に基づいて作成した、こちらがより高速なバージョンです。繰り返しごとに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;
    }
}
20
TowerOfBricks

この単純な実装は、連続的な倍増を使用し、非常によく機能します(私のベンチマークによると、ナイーブバージョンよりも約3〜4倍高速です)。

public static void Memset<T>(T[] array, T elem) 
{
    int length = array.Length;
    if (length == 0) return;
    array[0] = elem;
    int count;
    for (count = 1; count <= length/2; count*=2)
        Array.Copy(array, 0, array, count, count);
    Array.Copy(array, 0, array, count, length - count);
}

編集:他の答えを読んで、この考えを持つ唯一の人ではないようです。それでも、私はこれをここに残しています、それは少しきれいで、他と同等に動作するからです。

12
staafl

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

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

12
Jan

パフォーマンスが絶対に重要な場合、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);
10
Juliet

すべての答えはシングルバイトのみを書いています-バイト配列を単語で埋めたい場合はどうしますか?または浮く?私は時々そのために使用を見つけます。したがって、「memset」に同様のコードを一般的でない方法で数回記述し、このページに到達して単一バイトの適切なコードを見つけた後、以下のメソッドを記述しました。

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

少なくとも、私はこのようなことのために新しいC++プロジェクトをファイルするつもりはありません-それはほとんど間違いなく時間の無駄です。

したがって、基本的には、LuceroおよびTowerOfBricksが提供するソリューションの拡張バージョンで、memset long、intなど、およびシングルバイトに使用できます。

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メソッドを使用して、大きなバッファとしての書き込みパフォーマンスを比較しました。時間はミリ秒単位で合計100回の繰り返しで、バッファ長に適合する回数に関係なく8バイトのulongを書き込みます。 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よりも短いバッファーを埋める方が高速であり、セットアップコードで半分のページがいっぱいになっていることがわかります。

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

6
Eric

またはP/Invoke方法を使用

[DllImport("msvcrt.dll", 
EntryPoint = "memset", 
CallingConvention = CallingConvention.Cdecl, 
SetLastError = false)]
public static extern IntPtr MemSet(IntPtr dest, int c, int count);

static void Main(string[] args)
{
    byte[] arr = new byte[3];
    GCHandle gch = GCHandle.Alloc(arr, GCHandleType.Pinned);
    MemSet(gch.AddrOfPinnedObject(), 0x7, arr.Length); 
}
6

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

byte[] myBytes = new byte[5] { 1, 1, 1, 1, 1};
4
Cory Charlton

System.Runtime.CompilerServices.Unsafe.InitBlockは、Konradの答えが言及するOpCodes.Initblk命令と同じことをするようになりました(彼は ソースリンク も言及しました)。

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

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

System.Runtime.CompilerServices.Unsafe.InitBlock(ref a[0], valueToFill, (uint) a.Length);
4
Gman

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

benchmark report

4
constructor

.NET Coreには組み込みのArray.Fill()関数がありますが、残念ながら.NET Frameworkにはそれがありません。 .NET Coreには2つのバリエーションがあります。配列全体を埋める、およびインデックスから始まる配列の一部を埋めます。

上記のアイデアに基づいて、いくつかのデータ型の配列全体を埋めるより一般的なFill関数を次に示します。これは、この投稿で説明した他の方法と比較してベンチマークする場合の最速の機能です。

この関数は、配列の一部を埋めるバージョンとともに、オープンソースの無料のNuGetパッケージで利用できます( nuget.orgのHPCsharp )。また、メモリ書き込みのみを実行するSIMD/SSE命令を使用したわずかに高速なFillが含まれていますが、BlockCopyベースのメソッドはメモリの読み取りと書き込みを実行します。

    public static void FillUsingBlockCopy<T>(this T[] array, T value) where T : struct
    {
        int numBytesInItem = 0;
        if (typeof(T) == typeof(byte) || typeof(T) == typeof(sbyte))
            numBytesInItem = 1;
        else if (typeof(T) == typeof(ushort) || typeof(T) != typeof(short))
            numBytesInItem = 2;
        else if (typeof(T) == typeof(uint) || typeof(T) != typeof(int))
            numBytesInItem = 4;
        else if (typeof(T) == typeof(ulong) || typeof(T) != typeof(long))
            numBytesInItem = 8;
        else
            throw new ArgumentException(string.Format("Type '{0}' is unsupported.", typeof(T).ToString()));

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

        while (index < endIndex)          // Fill the initial block
            array[index++] = value;

        endIndex = array.Length;
        for (; index < endIndex; index += block, block *= 2)
        {
            int actualBlockSize = Math.Min(block, endIndex - index);
            Buffer.BlockCopy(array, 0, array, index * numBytesInItem, actualBlockSize * numBytesInItem);
        }
    }
0
DragonSpit