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 -

  1. y a-t-il une méthode Cadre pour remplir un octet[] qui pourrait s'apparenter à memset
  2. 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.

71
demandé sur Jim Fell 2009-12-13 22:59:00

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.

52
répondu Mark Byers 2010-07-17 15:30:37

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.

40
répondu konrad.kruczynski 2016-03-11 21:02:07

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;
  }
}
21
répondu Lucero 2010-03-25 19:40:22

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;
    }
}
20
répondu TowerOfBricks 2017-05-23 12:09:55

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.

11
répondu Jan 2009-12-13 20:06:51

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.

11
répondu staafl 2013-09-06 13:47:54

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);
10
répondu Juliet 2013-09-15 13:43:45

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); 
}
6
répondu Agnius Vasiliauskas 2013-09-27 21:44:58

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.

5
répondu Eric 2015-03-16 15:57:58

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};
4
répondu Cory Charlton 2009-12-13 20:02:54

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);
3
répondu Gman 2018-01-08 19:43:29

a testé plusieurs façons, décrites dans différentes réponses. Voir les sources d'essai dans c# classe d'essai

"

benchmark report

0
répondu constructor 2016-03-14 19:16:38

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#.

-1
répondu Bing Bang 2018-03-26 20:01:39