C#中memset的等价物是什么?

问题描述:

我需要填写一个byte[]单个非零值的值。我怎么能在C#中做到这一点,而不需要遍历数组中的每个byteC#中memset的等价物是什么?

更新:的意见似乎这个分成两个问题 -

  1. 是否有一个框架的方法,以填补一个byte [],可能是类似于memset
  2. 什么是最有效的当我们处理一个非常大的数组时,如何做到这一点?

我完全同意使用一个简单的循环工作得很好,正如Eric和其他人所指出的那样。问题的关键是看看我是否可以学习关于C#的新东西:)我认为Juliet的并行操作方法应该比简单的循环更快。

基准: 多亏了史云逊的Mikael:http://techmikael.blogspot.com/2009/12/filling-array-with-default-value.html

原来简单的for环路去,除非你想使用不安全的代码的方式。

道歉在我原来的帖子中没有更清晰。埃里克和马克在他们的评论中都是正确的;肯定需要更多重点突出的问题。感谢大家的建议和回应。

+0

请注意,对于字节,Mark的答案需要稍作修改。 byte [] image = Enumerable.Repeat((byte)255,[....])。ToArray(); 否则它会假定你想要返回int []。 – Jedidja 2009-12-13 20:12:56

+1

如果你必须走性能路线,我怀疑使用unsafe/fixed并且一次设置一个Int32或Int64,并且移动指针会是你在c#中可以达到的最快速度(并且对于左边的字节使用一个字节)。 – 2009-12-13 21:07:46

+0

测试性能的好处。肯定会这么做:) – Jedidja 2009-12-13 21:13:07

你可以使用Enumerable.Repeat

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

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

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

+0

投票。我学到了一些东西:-) – 2009-12-13 20:04:48

+1

谢谢:)所以不是我想要完成这项任务。 – Jedidja 2009-12-13 20:10:59

+36

请注意,这比简单地写一个循环要慢数十倍。有问题的阵列长达数百万个项目;性能问题可能相当密切。 – 2009-12-14 15:57:55

你可以做到这一点,当你初始化数组,但我不认为这是你的要求为:

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

这将是正确的:)我正在处理图像,所以字节[]是几十万/百万项大。 – Jedidja 2009-12-13 20:06:39

如果性能是至关重要的,你可以考虑使用不安全的代码,直接用指针工作到阵列。

另一种选择可能是从msvcrt.dll导入memset并使用它。然而,调用它的开销可能比速度的增加更大。

+2

是的,它比Repeat 或for-loop快约40倍。 – 2009-12-13 20:26:37

+0

导入memset?有趣...将不得不放弃一枪。 – Jedidja 2009-12-13 21:13:52

+0

@jedidja 你可以在这里找到一个代码示例:http://www.gamedev.net/community/forums/topic.asp?topic_id=389926 – Jan 2009-12-14 00:12:49

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

是的 - 伟大的观察,实际上我们正在使用Parallel.For其他图像处理代码。 – Jedidja 2009-12-14 16:42:58

+3

是的,但你不觉得肮脏的并行初始化代码吗?!? ;) – kenny 2010-03-04 12:30:09

+4

该代码与提供的不正确。 Parallel.For的第二个参数是toExclusive,意味着数组的最后一个字节不变。将'size - 1'更改为'size'。 – 2010-12-31 17:20:49

有点晚,但下面的方法可能是没有恢复到不安全的代码一个很好的妥协。基本上,它使用传统循环初始化阵列的开始,然后恢复为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; 
    } 
} 
+2

@downvoter我的回答有什么问题?任何改进建议? – Lucero 2016-03-06 22:42:23

+0

另外值得注意的是,Buffer.BlockCopy(索引和长度)的输入参数是字节数组索引和长度,所以为了将其复制到可以说整数数组中,您需要将Buffer.BlockCopy索引和长度乘以sizeof(int) 。 – 2017-01-10 10:47:24

建立在Lucero's answer,这里是一个更快的版本。它会使每个迭代使用Buffer.BlockCopy复制的字节数加倍。有趣的是,当使用相对较小的数组(1000)时,它的性能优于10倍,但对于较大的数组(1000000)来说差异并不那么大,但它总是比较快。关于它的好处是,它甚至可以执行到小阵列。在长度= 100左右,它比天真的方法更快。对于一百万个元素字节的数组,它快了43倍。 (英特尔酷睿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; 
    } 
} 

这种简单的实现,通过逐次加倍,并且表现相当好(根据我的基准,比天真的版本快约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); 
} 

编辑:在阅读其他答案时,似乎我不是唯一有这个想法的人。尽管如此,我仍然留在这里,因为它有点干净,并且与其他人相提并论。

+0

我写了一篇关于如何使用Array.Copy的博客文章。 http://coding.grax.com/2011/11/initialize-array-to-value-in-c-very.html – Grax 2014-04-04 16:01:34

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

其实,有鲜为人知称为InitblkEnglish version)IL操作这正是这么做的。因此,让我们将其用作不需要“不安全”的方法。这里的帮手类:

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将不可验证。

+0

非常整齐!感谢分享。 – Jedidja 2014-09-12 13:02:03

+3

我观察到了一个更大的差异。要初始化10MB阵列1000次,initblk需要0.5s,循环需要22s。这是我的基准:https://gist.github.com/thomaslevesque/6f653d8b3a82b1d038e1 – 2014-11-12 14:02:57

+0

这更加有趣:)用你的基准测试结果是:3.66s vs 0.3s。 – 2014-11-12 15:02:30

所有的答案都只写单字节 - 如果你想用字填充字节数组怎么办?还是漂浮?我现在觉得它很有用。因此,在用非泛型方式将类似代码写入'memset'几次并到达此页面以找到单字节的良好代码之后,我开始着手编写下面的方法。

我认为PInvoke和C++/CLI各有缺点。为什么不把你的运行时'PInvoke'换成mscorxxx? Array.Copy和Buffer.BlockCopy肯定是本地代码。 BlockCopy甚至不是'安全的' - 只要它们在数组中,您可以在另一个上复制一段时间,或者在DateTime上复制一段时间。

至少我不会为这样的东西去新的C++项目 - 这几乎可以肯定是浪费时间。

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

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工程结构元帅的大小为二的幂):

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的最低有效字节。然而,我比较写作缓冲区大小为,initblk和我的memset方法的性能。时间以ms为单位,总共超过100次重复,写入8个字节的长度,无论多少次都适合缓冲区长度。对于单个ulong的8个字节,for版本是手动循环展开的。

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,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 class

benchmark report

貌似System.Runtime.CompilerServices.Unsafe.InitBlock现在做同样的事情为OpCodes.Initblk指令康拉德的回答提到了(他也提到了source link)。

的代码来填充所述阵列中如下:

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

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

UMM,Array对象有一个称为清除方法。我敢打赌,Clear方法比你在c#中编写的任何代码都快。