web-dev-qa-db-de.com

Was ist das Äquivalent von Memset in C #?

Ich muss einen byte[] mit einem einzelnen Nicht-Null-Wert füllen. Wie kann ich dies in C # tun, ohne jede byte im Array zu durchlaufen?

Update: Die Kommentare scheinen dies in zwei Fragen aufgeteilt zu haben:

  1. Gibt es eine Framework-Methode zum Füllen eines Bytes [], das memset ähneln könnte
  2. Was ist der effizienteste Weg, wenn es sich um ein sehr großes Array handelt?

Ich stimme völlig zu, dass die Verwendung einer einfachen Schleife gut funktioniert, wie Eric und andere darauf hingewiesen haben. Die Frage war, ob ich etwas Neues über C # lernen könnte :) Ich denke, Juliets Methode für eine Paralleloperation sollte noch schneller als eine einfache Schleife sein.

Benchmarks: Danke an Mikael Svenson: http://techmikael.blogspot.com/2009/12/filling-array-with-default-value.html

Es stellt sich heraus, dass die einfache for-Schleife der Weg ist, es sei denn, Sie möchten unsicheren Code verwenden.

Entschuldigung, dass ich in meinem ursprünglichen Beitrag nicht klarer bin. Eric und Mark sind beide richtig in ihren Kommentaren. Ich muss sicher fokussiertere Fragen haben. Vielen Dank für alle Anregungen und Antworten.

72
Jedidja

Sie könnten Enumerable.Repeat verwenden:

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

Der erste Parameter ist das Element, das Sie wiederholen möchten, und der zweite Parameter gibt an, wie oft es wiederholt werden soll. 

Dies ist in Ordnung für kleine Arrays, aber Sie sollten die Schleifenmethode verwenden, wenn Sie sehr große Arrays verwenden und die Leistung ein Problem ist.

54
Mark Byers

Tatsächlich gibt es wenig bekannte IL-Operationen mit dem Namen Initblk ( English version ), die genau das tun. Verwenden wir sie also als Methode, die nicht "unsicher" ist. Hier ist die Helferklasse:

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;

}

Und was ist die Leistung? Hier ist mein Ergebnis für Windows/.NET und Linux/Mono (verschiedene PCs).

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

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

Es ist also eine Überlegung wert. Beachten Sie, dass die resultierende IL nicht überprüfbar ist.

40

Ein bisschen spät, aber der folgende Ansatz könnte ein guter Kompromiss sein, ohne auf unsicheren Code zurückzugreifen. Im Grunde initialisiert es den Anfang des Arrays mit einer konventionellen Schleife und kehrt dann zu Buffer.BlockCopy() zurück. Dies sollte so schnell sein, wie Sie einen verwalteten Anruf erhalten können.

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

Aufbauend auf Luceros Antwort gibt es eine schnellere Version. Die Anzahl der Bytes, die mit Buffer.BlockCopy bei jeder Iteration kopiert werden, wird verdoppelt. Interessanterweise übertrifft es bei Verwendung relativ kleiner Arrays (1000) den Faktor 10, aber bei größeren Arrays (1000000) ist der Unterschied nicht so groß, er ist jedoch immer schneller. Das Gute daran ist, dass es auch kleine Arrays gut abschneidet. Es wird schneller als der naive Ansatz bei etwa length = 100. Für ein Byte-Array mit einer Million Elementen war es 43-mal schneller. (Getestet mit 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

Diese einfache Implementierung verwendet sukzessive Verdopplung und ist recht gut (etwa 3-4 Mal schneller als die naive Version gemäß meinen Benchmarks):

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

Edit: Beim Lesen der anderen Antworten scheint es, als wäre ich nicht der einzige mit dieser Idee. Trotzdem lasse ich das hier, da es etwas sauberer ist und mit den anderen vergleichbar ist.

12
staafl

Wenn die Leistung von entscheidender Bedeutung ist, sollten Sie unsicheren Code in Betracht ziehen und direkt mit einem Zeiger auf das Array arbeiten.

Eine andere Option könnte Memset aus msvcrt.dll importieren und verwenden. Der Aufwand beim Aufrufen kann jedoch leicht größer sein als der Geschwindigkeitsgewinn. 

12
Jan

Wenn die Leistung absolut entscheidend ist, wird Enumerable.Repeat(n, m).ToArray() für Ihre Anforderungen zu langsam sein. Mit PLINQ oder Task Parallel Library können Sie möglicherweise eine schnellere Leistung erzielen.

using System.Threading.Tasks;

// ...

byte initialValue = 20;
byte[] data = new byte[size]
Parallel.For(0, size, index => data[index] = initialValue);
10
Juliet

Alle Antworten schreiben nur einzelne Bytes. Was ist, wenn Sie ein Byte-Array mit Wörtern füllen möchten? Oder schwimmt Ich finde ab und zu Verwendung. Nachdem ich ein paar Mal einen ähnlichen Code für 'memset' auf nicht-generische Weise geschrieben hatte und auf dieser Seite angekommen war, um einen guten Code für einzelne Bytes zu finden, habe ich die folgende Methode geschrieben.

Ich denke, dass PInvoke und C++/CLI jeweils ihre Nachteile haben. Und warum nicht die Laufzeit 'PInvoke' für Sie in mscorxxx? Array.Copy und Buffer.BlockCopy sind sicherlich nativer Code. BlockCopy ist nicht einmal "sicher" - Sie können eine lange Hälfte über eine andere oder eine DateTime kopieren, solange sich diese in Arrays befinden.

Zumindest würde ich für solche Dinge kein neues C++ - Projekt erstellen - es ist fast sicher Zeitverschwendung.

Hier ist im Wesentlichen eine erweiterte Version der von Lucero und TowerOfBricks vorgestellten Lösungen, mit der Longs, Ints usw. sowie einzelne Bytes gespeichert werden können.

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

Wenn Sie dies haben, können Sie einfach kurze Methoden hinzufügen, um den gewünschten Werttyp zu speichern und die private Methode aufzurufen, z. Suchen Sie einfach Ulong ersetzen in dieser Methode:

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

Oder gehen Sie dumm und machen Sie es mit jeder Art von Struktur (obwohl das obige MemsetPrivate nur für Strukturen funktioniert, die eine Marshalling-Größe haben, die eine Zweierpotenz ist):

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

Ich habe das zuvor erwähnte initblk geändert, um Ulongs zu verwenden, um die Leistung mit meinem Code zu vergleichen, und das schlägt im Stillen fehl. Der Code wird ausgeführt, der resultierende Puffer enthält jedoch nur das niedrigstwertige Byte des Ulong.

Trotzdem verglich ich das Performance-Schreiben als großen Puffer mit für, initblk und meiner Memset-Methode. Die Zeiten sind in ms insgesamt über 100 Wiederholungen und schreiben 8-Byte-Ulongs, unabhängig davon, wie oft die Pufferlänge passt. Die for-Version wird manuell für die 8 Bytes eines einzelnen ulong-Objekts abgewickelt.

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

Ich habe den ersten Anruf jedes Mal ausgeschlossen, da sowohl initblk als auch memset einen Treffer erzielen. Ich glaube, es waren etwa 0,22 ms für den ersten Anruf. Etwas überraschend ist mein Code beim Füllen kurzer Puffer schneller als bei initblk, da er eine halbe Seite mit Setup-Code hat. 

Wenn jemand Lust hat, dies zu optimieren, machen Sie wirklich weiter. Es ist möglich. 

6
Eric

Oder benutze P/Invoke-Methode :

[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

Sie könnten es tun, wenn Sie das Array initialisieren, aber ich glaube nicht, dass Sie danach fragen:

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

Sieht aus, als würde System.Runtime.CompilerServices.Unsafe.InitBlock jetzt dasselbe tun wie die OpCodes.Initblk-Anweisung, die Konrads Antwort erwähnt (er erwähnte auch einen source link ).

Der Code zum Ausfüllen des Arrays lautet wie folgt:

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

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

Mehrere Möglichkeiten getestet, in verschiedenen Antworten beschrieben. Siehe Quellen für den Test in c # test class

 benchmark report

1
constructor

.NET Core verfügt über eine integrierte Array.Fill () - Funktion. Leider fehlt .NET Framework diese Funktion. .NET Core hat zwei Varianten: Füllen Sie das gesamte Array und füllen Sie einen Teil des Arrays ab einem Index aus.

Aufbauend auf den obigen Ideen ist hier eine allgemeinere Fill-Funktion, die das gesamte Array verschiedener Datentypen ausfüllt. Dies ist die schnellste Funktion, wenn Sie mit anderen in diesem Beitrag beschriebenen Methoden vergleichen.

Diese Funktion steht zusammen mit der Version, die einen Teil eines Arrays füllt, in einem Open Source- und kostenlosen NuGet-Paket ( HPCsharp auf nuget.org ) zur Verfügung. Ebenfalls enthalten ist eine etwas schnellere Version von Fill mit SIMD/SSE-Anweisungen, die nur Speicherschreibvorgänge ausführt, während BlockCopy-basierte Methoden Speicherlesevorgänge und -schreibvorgänge ausführen.

    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