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 -
memset
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.
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.
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.
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;
}
}
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;
}
}
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.
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.
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);
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.
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);
}
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};
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);
Testé de plusieurs manières, décrit dans différentes réponses . Voir les sources de test dans c # test class
.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);
}
}