C#中的memset相当于什么?

What is the equivalent of memset in C#?

我需要用一个非零值填充一个byte[]。在C语言中,如果不循环遍历数组中的每个byte,我如何才能做到这一点?

更新:评论似乎把这个问题分成了两个问题-

  • 是否有一个框架方法来填充一个可能类似于memset的字节[]
  • 当我们处理一个非常大的数组时,最有效的方法是什么?
  • 正如埃里克和其他人指出的,我完全同意使用一个简单的循环是可行的。问题的关键是看我是否能学到一些关于c:的新知识。我认为朱丽叶的并行运算方法应该比简单的循环更快。

    Benchmarks:感谢Mikael Svenson:http://techmikael.blogspot.com/2009/12/filling-array-with-default-value.html

    事实证明,除非您想使用不安全的代码,否则简单的for循环就是解决问题的方法。

    很抱歉在我原来的帖子里说得不清楚。埃里克和马克的评论都是正确的,需要有更集中的问题。感谢大家的建议和回应。


    您可以使用Enumerable.Repeat

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

    第一个参数是要重复的元素,第二个参数是重复的次数。

    对于小数组来说这是可以的,但是如果您处理的是非常大的数组,并且性能是一个问题,那么应该使用循环方法。


    实际上,很少有一个叫做initblk(英文版)的IL操作可以做到这一点。所以,让我们把它用作一种不需要"不安全"的方法。这是帮助程序类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    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(不同的PC)的结果。

    1
    2
    3
    4
    5
    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(),这应该是使用托管调用所能获得的最快速度。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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;
      }
    }


    基于卢塞罗的答案,这里有一个更快的版本。它将使每次迭代使用Buffer.BlockCopy复制的字节数翻倍。有趣的是,当使用相对较小的阵列(1000个)时,它的性能比它高出10倍,但对于较大的阵列(1000000个),差异并不大,但它总是更快。它的优点是即使在小的数组中也能很好地执行。它比幼稚的方法更快,长度大约为100。对于一个一百万个字节的数组,它的速度是43倍。(在Intel i7.NET 2.0上测试)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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;
        }
    }

    这个简单的实现使用连续的加倍,并且性能相当好(根据我的基准,比原始版本快3-4倍):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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);
    }

    编辑:在阅读其他答案时,我似乎不是唯一有这个想法的人。不过,我还是把它留在这里,因为它比较干净,而且性能和其他的一样。


    如果性能至关重要,可以考虑使用不安全的代码并直接使用指向数组的指针。

    另一个选项可能是从msvcrt.dll导入memset并使用它。然而,调用的开销可能很容易大于速度的增加。


    如果性能是绝对关键的,那么Enumerable.Repeat(n, m).ToArray()对于您的需求来说将太慢。您可以使用plinq或任务并行库来获得更快的性能:

    1
    2
    3
    4
    5
    6
    7
    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甚至不是"安全的"——只要它们在数组中,就可以在另一个数组的一半或日期时间上复制较长的数据。

    至少我不会为这样的事情提交新的C++项目——这几乎是浪费时间。

    这里基本上是Lucero和Towerofbricks提供的解决方案的扩展版本,可以用于memset long、ints等以及单字节。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    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:

    1
    2
    3
    4
        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只适用于封送为二次幂的结构):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
        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,将ulong与我的代码进行性能比较,结果代码运行,但产生的缓冲区只包含ulong的最低有效字节。

    尽管如此,我还是将性能写入与for、initblk和memset方法进行了比较。这些时间以毫秒计,总共有100多次重复写入8字节的ulons,不管多少次符合缓冲区长度。for版本是针对单个ulong的8个字节手动循环展开的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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都会命中我认为第一个调用大约是.22毫秒。稍微令人惊讶的是,我的代码比initblk填充短缓冲区更快,因为它有半页的设置代码。

    如果有人想优化这一点,那就真的去做吧。这是可能的。


    或使用P/Invoke方式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    [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);
    }


    看起来System.Runtime.CompilerServices.Unsafe.InitBlock现在和konrad的答案提到的OpCodes.Initblk指令(他还提到了一个源链接)做了同样的事情。

    填充数组的代码如下:

    1
    2
    3
    4
    byte[] a = new byte[N];
    byte valueToFill = 255;

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


    您可以在初始化数组时执行此操作,但我认为这不是您所要求的:

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


    测试了几种不同答案中描述的方法。见C测试类中的测试来源

    benchmark report


    .NET核心有一个内置的array.fill()函数,但遗憾的是.NET框架缺少它。.NET核心有两种变体:填充整个数组并填充从索引开始的数组的一部分。

    基于上述思想,这里有一个更通用的填充函数,它将填充多个数据类型的整个数组。当与本文讨论的其他方法进行基准测试时,这是最快的功能。

    这个函数,以及填充部分的版本,一个数组可以在一个开放源码和免费的nuget包中使用(nuget.org上的hpcharp)。还包括使用只执行内存写入的simd/sse指令的稍快版本的填充,而基于blockcopy的方法执行内存读取和写入。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
        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);
            }
        }

    数组对象有一个名为clear的方法。我敢打赌clear方法比用C语言编写的任何代码都快。