Quel est l'équivalent de memset en C#?
je dois remplir un byte[]
avec une seule valeur non-zéro . Comment puis-je faire ceci en C# sans faire de boucle à travers chaque byte
dans le tableau?
mise à Jour: Les commentaires semblent avoir partagé ce en deux questions -
- y a-t-il une méthode Cadre pour remplir un octet[] qui pourrait s'apparenter à
memset
- Quelle est la manière la plus efficace de le faire quand nous avons affaire à un très grand tableau?
je suis totalement d'accord que l'utilisation d'une simple boucle fonctionne bien, comme Eric et d'autres l'ont fait. Le but de la question était de voir si je pouvais apprendre quelque chose de nouveau sur C# :) je pense que la méthode de Juliet pour une opération parallèle devrait être encore plus rapide qu'une simple boucle.
Repères: Merci à Mikael Svenson: http://techmikael.blogspot.com/2009/12/filling-array-with-default-value.html
il s'avère que la simple boucle for
est la voie à suivre à moins que vous vouliez utiliser du code dangereux.
mes excuses de ne pas avoir été plus clair dans mon billet original. Eric et Mark ont tous les deux raison dans leurs commentaires; ils ont besoin de questions plus ciblées, c'est certain. Merci pour chacun suggestions et réponses.
13 réponses
vous pourriez utiliser Enumerable.Repeat
:
byte[] a = Enumerable.Repeat((byte)10, 100).ToArray();
Le premier paramètre est l'élément que vous souhaitez répéter, et le second paramètre est le nombre de fois à répéter.
C'est OK pour les petits tableaux, mais vous devriez utiliser la méthode de boucle si vous avez affaire avec de très grands tableaux et la performance est une préoccupation.
en fait, il y a peu d'opération IL connu appelé Initblk ( version anglaise ) qui fait exactement cela. Alors, utilisons-le comme une méthode qui ne nécessite pas de "danger". Voici la classe helper:
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;
}
et quelle est la performance? Voici mon résultat pour Windows/.NET et Linux / Mono (différents 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
ça vaut la peine d'y réfléchir. Notez que le résultat IL ne sera pas vérifiables.
un peu en retard, mais l'approche suivante pourrait être un bon compromis sans revenir à un code dangereux. Fondamentalement, il initialise le début du tableau à l'aide d'une boucle conventionnelle , puis revient à Buffer.BlockCopy()
, qui devrait être aussi rapide que vous pouvez obtenir en utilisant un appel géré.
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;
}
}
s'appuyant sur Lucero la réponse de , voici une version plus rapide. Il doublera le nombre d'octets copiés en utilisant Buffer.BlockCopy
à chaque itération. Il est intéressant de noter qu'elle est surclassée par un facteur de 10 lors de l'utilisation de tableaux relativement petits (1000), mais la différence n'est pas si grande pour les tableaux plus grands (1000000), elle est toujours plus rapide cependant. La bonne chose à ce sujet est qu'il fonctionne bien, même aux petits tableaux. Il devient plus rapide que l'approche naïve autour de longueur = 100. Pour un réseau d'un million de octets, c'était 43 fois plus rapide.
(testé sur 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;
}
}
si la performance est critique, vous pouvez envisager d'utiliser du code dangereux et de travailler directement avec un pointeur vers le tableau.
une autre option pourrait être l'importation de memset à partir de msvcrt.dll et utilise ça. Toutefois, la charge de l'invocation qui pourrait facilement être plus grand que le gain en vitesse.
cette simple implémentation utilise des doublages successifs, et fonctionne assez bien (Environ 3-4 fois plus vite que la version naïve selon mes 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: en lisant les autres réponses, il semble que je ne suis pas le seul avec cette idée. Pourtant, je laisse ça ici, car c'est un peu plus propre et il fonctionne sur un pied d'égalité avec les autres.
si la performance est absolument critique, alors Enumerable.Repeat(n, m).ToArray()
sera trop lent pour vos besoins. Vous pourriez être en mesure de gérer des performances plus rapides en utilisant PLINQ ou bibliothèque parallèle des tâches :
using System.Threading.Tasks;
// ...
byte initialValue = 20;
byte[] data = new byte[size]
Parallel.For(0, size, index => data[index] = initialValue);
Ou de l'utilisation de P/Invoke façon :
[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);
}
toutes les réponses n'écrivent que des octets - et si vous voulez remplir un tableau d'octets avec des mots? Ou les flotteurs? J'ai trouver une utilisation pour ça maintenant et puis. Donc après avoir écrit du code similaire à 'memset' de manière non-générique quelques fois et arriver à cette page pour trouver du bon code pour les octets simples, j'ai commencé à écrire la méthode ci-dessous.
je pense que PInvoke et C++/CLI ont chacun leurs inconvénients. Et pourquoi ne pas avoir le runtime 'PInvoke' pour vous dans mscorxxx? Tableau.Copie et Tampon.BlockCopy sont code natif certainement. BlockCopy n'est même pas " sûr " - vous pouvez copier un long à mi-chemin sur un autre, ou sur une DateTime aussi longtemps qu'ils sont dans les tableaux.
au moins je n'irais pas déposer un nouveau projet C++ pour des choses comme ça - c'est une perte de temps presque certainement.
donc voici essentiellement une version étendue des solutions présentées par Lucero et TowerOfBricks qui peuvent être utilisés pour memset longs, ints, etc ainsi que des octets simples.
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);
}
}
ayant ceci, vous pouvez simplement ajouter des méthodes courtes pour prendre le type de valeur dont vous avez besoin memset et appeler la méthode privée, par exemple juste trouver remplacer ulong dans cette méthode:
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);
}
ou faire l'idiot et le faire avec n'importe quel type de structure (bien que le MemsetPrivate ci-dessus travaille seulement pour des structures qui marient à une taille qui est une puissance de deux):
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);
}
j'ai changé le initblk mentionné avant de prendre ulongs pour comparer les performances avec mon code et silencieusement échoue - le code s'exécute, mais le tampon contient l'octet le moins significatif de l'ulong seulement.
néanmoins j'ai comparé la performance en écrivant comme un gros tampon Avec for, initblk et ma méthode memset. Les temps sont en ms total plus de 100 répétitions écrivant 8 octets ulongs peu importe combien de fois correspondent à la longueur du tampon. La version manuelle boucle déroulée du 8 octets d'un seul ulong.
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
j'ai exclu le premier appel à chaque fois, depuis initblk et memset prendre un coup de je crois que c'était environ .22ms pour le premier appel. Un peu surprenant mon code est plus rapide pour remplir des tampons courts qu'initblk, vu qu'il a obtenu une demi-page pleine de code de configuration.
si quelqu'un veut optimiser ceci, allez-y. Il est possible de faire.
vous pouvez le faire quand vous initialisez le tableau mais je ne pense pas que ce soit ce que vous demandez:
byte[] myBytes = new byte[5] { 1, 1, 1, 1, 1};
ressemble à System.Runtime.CompilerServices.Unsafe.InitBlock
fait maintenant la même chose que l'instruction OpCodes.Initblk
que la réponse de Konrad mentionne (il a également mentionné un lien source ).
Le code pour remplir le tableau est comme suit:
byte[] a = new byte[N];
byte valueToFill = 255;
System.Runtime.CompilerServices.Unsafe.InitBlock(ref a[0], valueToFill, (uint) a.Length);
a testé plusieurs façons, décrites dans différentes réponses. Voir les sources d'essai dans c# classe d'essai
"l'objet Array a une méthode appelée Clear. Je suis prêt à parier que la méthode Clear est plus rapide que n'importe quel code que vous pouvez écrire en C#.