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:
memset
ähneln könnteIch 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.
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.
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.
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;
}
}
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;
}
}
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.
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.
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);
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.
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);
}
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};
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);
Mehrere Möglichkeiten getestet, in verschiedenen Antworten beschrieben. Siehe Quellen für den Test in c # test class
.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);
}
}