web-dev-qa-db-fra.com

Quel est l'équivalent de memset en C #?

J'ai besoin de remplir un byte[] avec une seule valeur non nulle. Comment puis-je faire cela en C # sans passer en boucle dans chaque byte du tableau?

_ {Update:} _ Les commentaires semblent avoir scindé ceci en deux questions -

  1. Existe-t-il une méthode Framework pour remplir un octet [] qui pourrait s'apparenter à memset
  2. Quel est le moyen le plus efficace de le faire lorsqu'il s'agit d'un très grand nombre?

Je suis tout à fait d’accord pour dire que l’utilisation d’une simple boucle fonctionne très bien, comme Eric et d’autres l’ont souligné. Le but de la question était de voir si je pouvais apprendre quelque chose de nouveau à propos de C # :) Je pense que la méthode de Juliette pour une opération parallèle devrait être encore plus rapide qu'une simple boucle.

Benchmarks: 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 solution à moins que vous ne souhaitiez utiliser du code non sécurisé.

Toutes mes excuses pour ne pas être plus clair dans mon post original. Eric et Mark ont ​​tous deux raison dans leurs commentaires. besoin d’avoir des questions plus ciblées à coup sûr. Merci pour les suggestions et les réponses de chacun.

72
Jedidja

Vous pouvez 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 indique le nombre de répétitions. 

Ceci est acceptable pour les petits tableaux, mais vous devriez utiliser la méthode de bouclage si vous avez affaire à de très grands tableaux et que les performances sont un sujet de préoccupation.

54
Mark Byers

En fait, il existe une opération IL peu connue appelée Initblk ( version anglaise ) qui fait exactement cela. Alors, utilisons-le comme une méthode qui ne nécessite pas de "non sécurité". Voici la classe d'assistance:

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

Donc, il convient de considérer. Notez que le IL résultant ne sera pas vérifiable.

40
konrad.kruczynski

Un peu en retard, mais l'approche suivante pourrait être un bon compromis sans revenir à un code non sécurisé. Fondamentalement, il initialise le début du tableau en utilisant une boucle conventionnelle, puis revient à Buffer.BlockCopy(), ce 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
Lucero

S'appuyant sur La réponse de Lucero , 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 que le nombre de tableaux relativement petits (1 000) est 10 fois supérieur à celui du nombre de fois, mais la différence n’est pas très grande pour les tableaux plus grands (1 000 000), mais elle est toujours plus rapide. L'avantage, c'est qu'il fonctionne bien, même dans les petits tableaux. Cela devient plus rapide que l'approche naïve autour de longueur = 100. Pour un tableau d'un million d'éléments, il é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
TowerOfBricks

Cette implémentation simple utilise des doublages successifs et fonctionne assez bien (environ 3 à 4 fois plus rapide que la version naïve selon mes critères):

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: à la lecture des autres réponses, il semble que je ne suis pas le seul à avoir cette idée. Néanmoins, je laisse cela ici, car c'est un peu plus propre et il fonctionne à égalité avec les autres.

12
staafl

Si les performances sont critiques, vous pouvez envisager d'utiliser du code non sécurisé et de travailler directement avec un pointeur sur le tableau.

Une autre option pourrait être l'importation de memset à partir de msvcrt.dll et l'utiliser. Cependant, le temps système que vous invoquez peut facilement dépasser le gain de vitesse. 

12
Jan

Si les performances sont absolument essentielles, alors Enumerable.Repeat(n, m).ToArray() sera trop lent pour vos besoins. Vous pourrez peut-être améliorer les performances plus rapidement avec PLINQ ou Bibliothèque de tâches parallèles :

using System.Threading.Tasks;

// ...

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

Toutes les réponses n'écrivent que des octets simples - et si vous voulez remplir un tableau d'octets avec des mots? Ou des flotteurs? Je trouve une utilisation pour cela de temps en temps. Ainsi, après avoir écrit plusieurs fois un code similaire à 'memset' de manière non générique et après être arrivé sur cette page pour trouver le bon code pour un octet, j'ai écrit 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? Array.Copy et Buffer.BlockCopy sont certainement du code natif. BlockCopy n'est même pas "sûr" - vous pouvez copier un long moitié sur un autre, ou sur un DateTime tant qu'ils sont dans des tableaux.

Au moins, je n'irais pas dans un nouveau projet C++ pour ce type de projet - c'est certainement une perte de temps.

Il s'agit donc d'une version étendue des solutions présentées par Lucero et TowerOfBricks, qui peut être utilisée pour les longs memset, les ints, etc. ainsi que pour les 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 cela, vous pouvez simplement ajouter des méthodes courtes pour prendre le type de valeur dont vous avez besoin pour memset et appeler la méthode privée, par exemple. il suffit de 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 bien, faites-le avec n'importe quel type de structure (bien que le MemsetPrivate ci-dessus ne fonctionne que pour les structures dont la taille 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é précédemment pour prendre ulongs afin de comparer les performances avec mon code et cela échoue en silence - le code s'exécute mais le tampon résultant contient uniquement l'octet le moins significatif de ulong.

Néanmoins, j'ai comparé la performance en écrivant comme un gros tampon avec for, initblk et ma méthode memset. Les temps sont exprimés en ms et totalisent plus de 100 répétitions en écrivant des ulongs sur 8 octets, quel que soit le nombre de fois que la longueur de la mémoire tampon convient. La version for est manuellement déroulée en boucle pour les 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, car initblk et memset ont tous deux pris un coup. Je crois qu'il était d'environ 0,22 ms pour le premier appel. Légèrement surprenant, mon code est plus rapide pour le remplissage de tampons courts que pour initblk, car il contient une demi-page de code d'installation. 

Si quelqu'un veut optimiser cela, allez-y vraiment. C'est possible. 

6
Eric

Ou utilisez la méthode 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
Agnius Vasiliauskas

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
Cory Charlton

On dirait que System.Runtime.CompilerServices.Unsafe.InitBlock fait maintenant la même chose que l'instruction OpCodes.Initblk mentionnée dans la réponse de Konrad (il a également mentionné un lien source ).

Le code pour remplir le tableau est le suivant:

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

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

Testé de plusieurs manières, décrit dans différentes réponses . Voir les sources de test dans c # test class

 benchmark report

1
constructor

.NET Core a une fonction intégrée Array.Fill (), mais malheureusement, le .NET Framework ne l’a pas. .NET Core a deux variantes: remplir le tableau entier et remplir une partie du tableau en commençant par un index.

Sur la base des idées ci-dessus, voici une fonction de remplissage plus générique qui remplira tout le tableau de plusieurs types de données. C’est la fonction la plus rapide lorsqu’on compare avec d’autres méthodes présentées dans cet article.

Cette fonction, ainsi que la version qui remplit une partie d'un tableau, sont disponibles dans un package NuGet gratuit et à code ouvert ( HPCsharp sur nuget.org ). Une version légèrement plus rapide de Fill utilisant des instructions SIMD/SSE et effectuant uniquement des écritures en mémoire est également incluse, alors que les méthodes basées sur BlockCopy effectuent des lectures et des écritures en mémoire.

    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