Что эквивалентно memset в C#?

Мне нужно заполнить byte[] с одним ненулевым значением. Как я могу сделать это в C#, не просматривая каждый byte в массиве?

Обновление: комментарии, кажется, разделили это на два вопроса -

  1. Есть ли метод Framework для заполнения байта [], который может быть похож на memset
  2. Какой самый эффективный способ сделать это, когда мы имеем дело с очень большим массивом?

Я полностью согласен с тем, что использование простого цикла прекрасно работает, как указали Эрик и другие. Суть вопроса состояла в том, чтобы посмотреть, смогу ли я узнать что-то новое о C#:). Я думаю, что метод Джульетты для параллельной операции должен быть даже быстрее, чем простой цикл.

Тесты: благодаря Микаэлю Свенсону: http://techmikael.blogspot.com/2009/12/filling-array-with-default-value.html

Оказывается простой for цикл - это путь, если вы не хотите использовать небезопасный код.

Извиняюсь за то, что не был яснее в моем оригинальном посте. Эрик и Марк оба правы в своих комментариях; нужно иметь более сфокусированные вопросы наверняка. Спасибо за все предложения и ответы.

17 ответов

Вы могли бы использовать Enumerable.Repeat:

byte[] a = Enumerable.Repeat((byte)10, 100).ToArray();

Первый параметр - это элемент, который вы хотите повторить, а второй параметр - количество повторений.

Это нормально для небольших массивов, но вы должны использовать метод зацикливания, если вы имеете дело с очень большими массивами и производительность является проблемой.

На самом деле, существует мало известная операция IL, называемая Initblk ( английская версия), которая делает именно это. Итак, давайте использовать его как метод, который не требует "небезопасных". Вот вспомогательный класс:

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;

}

А что такое производительность? Вот мой результат для Windows/.NET и Linux/Mono (разные ПК).

Mono/for:     00:00:01.1356610
Mono/initblk: 00:00:00.2385835 

.NET/for:     00:00:01.7463579
.NET/initblk: 00:00:00.5953503

Так что стоит задуматься. Обратите внимание, что полученный IL не подлежит проверке.

Основываясь на ответе Лусеро, вот более быстрая версия. Это удвоит количество байтов, скопированных с помощью Buffer.BlockCopy каждая итерация Интересно, что при использовании сравнительно небольших массивов (1000) он превосходит его в 10 раз, но для больших массивов (1000000) разница невелика, но всегда быстрее. Хорошая вещь об этом - то, что это работает хорошо даже до маленьких массивов. Он становится быстрее, чем простой подход, при длине около 100. Для массива байтов в один миллион элементов он был в 43 раза быстрее. (протестировано на 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;
    }
}

Немного поздно, но следующий подход может быть хорошим компромиссом без возврата к небезопасному коду. В основном он инициализирует начало массива, используя обычный цикл, а затем возвращается к Buffer.BlockCopy(), который должен быть настолько быстрым, насколько это возможно при использовании управляемого вызова.

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;
  }
}

Похоже System.Runtime.CompilerServices.Unsafe.InitBlock теперь делает то же самое, что и OpCodes.Initblk инструкция, которую упоминает ответ Конрада (он также упомянул ссылку на источник).

Код для заполнения массива выглядит следующим образом:

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

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

Эта простая реализация использует последовательное удвоение и работает довольно хорошо (примерно в 3-4 раза быстрее, чем наивная версия согласно моим тестам):

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);
}

Изменить: после прочтения других ответов, кажется, я не единственный с этой идеей. Тем не менее, я оставляю это здесь, так как он немного чище и работает наравне с другими.

С появлением Span<T>(это только ядро ​​dotnet, но это будущее dotnet) у вас есть еще один способ решения этой проблемы:

var array = new byte[100];
var span = new Span<byte>(array);

span.Fill(255);

Если производительность критична, вы можете использовать небезопасный код и работать напрямую с указателем на массив.

Другим вариантом может быть импорт memset из msvcrt.dll и использование этого. Тем не менее, издержки от вызова могут легко превысить выигрыш в скорости.

Или используйте 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); 
}

Если производительность абсолютно критична, то Enumerable.Repeat(n, m).ToArray() будет слишком медленным для ваших нужд. Возможно, вам удастся добиться более высокой производительности с помощью PLINQ или Task Parallel Library:

using System.Threading.Tasks;

// ...

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

Все ответы пишутся только на один байт - что если вы хотите заполнить байтовый массив словами? Или плавает? Я использую это время от времени. Поэтому, после того, как несколько раз написал подобный код для memset неуниверсальным образом и, зайдя на эту страницу, чтобы найти хороший код для отдельных байтов, я приступил к написанию метода ниже.

Я думаю, что у PInvoke и C++/CLI есть свои недостатки. И почему бы не иметь для вас среду выполнения PInvoke в mscorxxx? Array.Copy и Buffer.BlockCopy, безусловно, являются нативным кодом. BlockCopy даже не является "безопасным" - вы можете скопировать длинную половину на другую или на DateTime, если они находятся в массивах.

По крайней мере, я бы не стал подавать новый проект C++ для таких вещей - это почти наверняка пустая трата времени.

Итак, вот в основном расширенная версия решений, представленных Lucero и TowerOfBricks, которые можно использовать для установки длинных, целочисленных значений и т. Д., А также отдельных байтов.

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);
    }
}

Имея это, вы можете просто добавить короткие методы, чтобы получить тип значения, с которым вам нужно установить memset, и вызвать закрытый метод, например, просто найти replace ulong в этом методе:

    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);
    }

Или иди с глупостью и делай это с любым типом структуры (хотя вышеприведенный MemsetPrivate работает только для структур, которые собраны в размер, равный степени двойки):

    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);
    }

Я изменил вышеупомянутый initblk, чтобы взять ulongs для сравнения производительности с моим кодом, и это молча завершается сбоем - код выполняется, но результирующий буфер содержит наименее значимый байт только ulong.

Тем не менее я сравнил производительность записи как большой буфер с for, initblk и моим методом memset. Время в миллисекундах составляет более 100 повторений, записывая 8 байт, независимо от того, сколько раз соответствует длине буфера. Версия for раскручивается вручную для 8 байтов одного 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

Я исключал первый вызов каждый раз, так как initblk и memset получили удар, я думаю, что он был около.22ms для первого вызова. Немного удивительно, что мой код быстрее заполняет короткие буферы, чем initblk, потому что он наполовину заполнен установочным кодом.

Если кто-то хочет оптимизировать это, продолжайте. Возможно.

Испытано несколько способов, описанных в разных ответах. Смотрите источники теста в классе C# test

сравнительный отчет

Вы можете сделать это, когда инициализируете массив, но я не думаю, что это то, что вы просите:

byte[] myBytes = new byte[5] { 1, 1, 1, 1, 1};

.NET Core имеет встроенную функцию Array.Fill(), но, к сожалению, в.NET Framework ее нет. .NET Core имеет два варианта: заполнить весь массив и заполнить часть массива, начиная с индекса.

Основываясь на вышеизложенных идеях, вот более общая функция Fill, которая заполнит весь массив данных нескольких типов. Это самая быстрая функция при сравнении с другими методами, обсуждаемыми в этом посте.

Эта функция вместе с версией, заполняющей часть массива, доступна в бесплатном пакете NuGet с открытым исходным кодом ( HPCsharp на nuget.org). Также включена немного более быстрая версия Fill с использованием инструкций SIMD/SSE, которая выполняет только запись в память, тогда как методы на основе BlockCopy выполняют чтение и запись в память.

    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);
        }
    }

Большинство ответов относится к байтовому memset, но если вы хотите использовать его для float или любой другой структуры, вы должны умножить индекс на размер ваших данных. Потому что Buffer.BlockCopy будет копировать на основе байтов. Этот код будет работать для значений с плавающей запятой

public static void MemSet(float[] array, float 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 * sizeof(float), Math.Min(block, length-index)* sizeof(float));
        index += block;
        block *= 2;
    }
}

У объекта Array есть метод Clear. Я готов поспорить, что метод Clear работает быстрее, чем любой код, который вы можете написать на C#.

Вы можете попробовать следующие коды, это сработает.

      byte[]   test =   new   byte[65536]; 
Array.Clear(test,0,test.Length);
Другие вопросы по тегам