C#中的memset相当于什么?

时间:2009-12-13 19:59:01

标签: c# memset equivalent

我需要使用单个非零值填充byte[]。如何在C#中执行此操作而不循环遍历数组中的每个byte

更新:评论似乎将此分为两个问题 -

  1. 是否有一个Framework方法来填充可能类似于memset
  2. 的byte []
  3. 当我们处理一个非常大的数组时,最有效的方法是什么?
  4. 我完全同意,正如Eric和其他人指出的那样,使用简单的循环可以正常工作。问题的关键在于看看我是否可以学习一些关于C#的新东西:)我认为Juliet的并行操作方法应该比简单的循环更快。

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

    事实证明,除非您想使用不安全的代码,否则简单的for循环是可行的。

    抱歉我的原帖不清楚。埃里克和马克的评论都是正确的;需要有更多专注的问题。感谢大家的建议和回应。

16 个答案:

答案 0 :(得分:55)

您可以使用Enumerable.Repeat

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

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

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

答案 1 :(得分:41)

实际上,鲜为人知的IL操作称为InitblkEnglish version)。所以,让我们使用它作为一种不需要“不安全”的方法。这是辅助类:

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)的结果。

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将无法验证。

答案 2 :(得分:21)

有点晚了,但是如果不回复到不安全的代码,以下方法可能是一个很好的折衷方案。基本上它使用传统循环初始化数组的开头,然后恢复为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;
  }
}

答案 3 :(得分:20)

Lucero's answer上构建,这是一个更快的版本。它会使每次迭代使用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;
    }
}

答案 4 :(得分:12)

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

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

答案 5 :(得分:12)

这个简单的实现使用连续加倍,并且表现相当好(根据我的基准测试,比天真版本快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);
}

编辑:在阅读其他答案后,似乎我不是唯一有这个想法的人。不过,我现在离开这里了,因为它有点干净,而且与其他人相当。

答案 6 :(得分:10)

如果性能绝对至关重要,那么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);

答案 7 :(得分:6)

Or use P/Invoke way

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

答案 8 :(得分:6)

所有答案都只写单个字节 - 如果你想用字填充字节数组怎么办?还是漂浮?我偶尔会发现它的用途。在将相似的代码写入&#39; memset&#39;之后以非通用的方式几次到达此页面以找到单个字节的良好代码,我开始编写下面的方法。

我认为PInvoke和C ++ / CLI都有它们的缺点。为什么不拥有运行时&#39; PInvoke&#39;你进mscorxxx? Array.Copy和Buffer.BlockCopy肯定是本机代码。 BlockCopy甚至不安全&#39; - 只要它们在数组中,你就可以复制一半到另一半,或者在DateTime上复制。

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

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

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所需的值类型并调用私有方法,例如只需在此方法中找到替换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仅适用于编组为2的幂的结构的结构):

    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字节ulongs。 for version是针对单个ulong的8个字节手动循环展开的。

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都受到了打击,我认为第一次调用时大约是0.22ms。稍微令我惊讶的是,我的代码填充短缓冲区比initblk更快,看到它有半页的设置代码。

如果有人想要优化这一点,请继续。这是可能的。

答案 9 :(得分:4)

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

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

答案 10 :(得分:4)

看起来System.Runtime.CompilerServices.Unsafe.InitBlock现在与Konrad的答案提及的OpCodes.Initblk指令完全相同(他还提到了source link)。

填写数组的代码如下:

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

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

答案 11 :(得分:3)

测试了几种方式,在不同的答案中描述。 请参阅c#test class

中的测试来源

benchmark report

答案 12 :(得分:0)

.NET Core具有内置的Array.Fill()函数,但遗憾的是.NET Framework缺少它。 .NET Core有两种变体:填充整个数组,并从索引开始填充数组的一部分。

在上述思想的基础上,这是一个更通用的Fill函数,它将填充几种数据类型的整个数组。当对照本文中讨论的其他方法进行基准测试时,这是最快的功能。

此功能以及填充数组一部分的版本在开源和免费的NuGet软件包(HPCsharp on nuget.org)中可用。还包括一个使用SIMD / SSE指令的Fill快一点的版本,该指令仅执行内存写入,而基于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);
        }
    }

答案 13 :(得分:0)

随着Span<T>(仅是dotnet核心,但it is the future of dotnet)的出现,您还有另一种解决此问题的方法:

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

span.Fill(255);

答案 14 :(得分:0)

大多数答案是针对字节memset的,但如果要将其用于浮点数或任何其他结构,则应将索引乘以数据大小。因为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;
    }
}

答案 15 :(得分:-1)

Array对象有一个名为Clear的方法。我敢打赌,Clear方法比你在C#中编写的任何代码都要快。