什么是在C#中的memset相当于?
我需要用一个非零值填充一个byte[]
。 如何在C#中执行此操作,而不循环数组中的每个byte
?
更新:评论似乎已经分裂成两个问题 –
- 是否有一个框架方法来填充一个可能类似于
memset
的byte [] - 当我们处理一个非常大的数组时,最有效的方法是什么?
我完全同意,像Eric和其他人所指出的那样,使用一个简单的循环就行得通。 问题的关键是要看看我是否可以学习一些有关C#的新东西:)我认为Juliet的并行操作方法应该比简单的循环更快。
基准:感谢Mikael Svenson: http : //techmikael.blogspot.com/2009/12/filling-array-with-default-value.html
事实certificate,简单for
循环是要走的路,除非你想使用不安全的代码。
对于我原来的文章没有更清楚的道歉。 Eric和Mark在评论中都是正确的。 需要有更多的重点问题。 感谢大家的build议和回应。
您可以使用Enumerable.Repeat
:
byte[] a = Enumerable.Repeat((byte)10, 100).ToArray();
第一个参数是要重复的元素,第二个参数是重复的次数。
这对于小数组是可以的,但是如果你处理非常大的数组并且性能是一个问题,你应该使用循环方法。
实际上,很less有人知道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; }
什么是performance? 这是我对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将无法validation。
有点晚,但下面的方法可能是一个很好的妥协,而不会恢复到不安全的代码。 基本上它使用传统的循环初始化数组的开始,然后恢复到Buffer.BlockCopy()
,这应该尽可能快地使用托pipe调用。
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; } }
build立在Lucero的回答上 ,这是一个更快的版本。 它会使每次迭代使用Buffer.BlockCopy
复制的字节数Buffer.BlockCopy
。 有趣的是,当使用相对较小的数组(1000)时,它的性能优于10倍,但对于较大的数组(1000000)来说差别不是那么大,但总是比较快。 关于它的好处是,它甚至可以执行到小arrays。 在长度= 100左右,它变得比天真的方法更快。对于一百万个元素字节的数组,它快了43倍。 (在Intel i7,.Net 2.0上testing)
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; } }
如果性能至关重要,则可以考虑使用不安全的代码,并直接使用指向数组的指针。
另一种select可能是从msvcrt.dll导入memset并使用它。 然而,调用它的开销可能会比速度的增加更大。
这个简单的实现使用连续的加倍,性能相当好(根据我的基准,比天真版本快大约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); }
编辑:在阅读其他答案,似乎我不是唯一一个这个想法。 不过,我要离开这里,因为它有点干净,并且和其他人一样。
如果性能绝对至关重要,那么Enumerable.Repeat(n, m).ToArray()
对于您的需求来说太慢了。 您可以使用PLINQ或任务并行库启动更快的性能:
using System.Threading.Tasks; // ... byte initialValue = 20; byte[] data = new byte[size] Parallel.For(0, size, index => data[index] = initialValue);
或者使用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); }
所有的答案都只写单字节 – 如果你想用字填充字节数组? 还是浮动? 我现在可以用它。 所以,在用非generics的方式写了类似的代码到memset之后,到达这个页面find单个字节的好代码后,我开始写下面的方法。
我认为PInvoke和C ++ / CLI各有缺点。 为什么不把运行时的“PInvoke”换成mscorxxx? Array.Copy和Buffer.BlockCopy肯定是本地代码。 BlockCopy甚至不是“安全的” – 只要数组在数组中,您可以在另一个中间或者在DateTime上复制一个长的文件。
至less我不会为这样的事情进行新的C ++项目 – 几乎肯定是浪费时间。
所以这里基本上是由Lucero和TowerOfBricks提供的解决scheme的扩展版本,它可以用来存储长整数,整数等以及单个字节。
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 ie 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所需要的值types,然后调用私有方法,比如在这个方法中findreplace 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); }
或者去愚蠢的做任何types的结构(尽pipe上面的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的最低有效字节。
尽pipe如此,我还是把性能写作作为一个缓冲区,用于initblk和我的memset方法。 时间以ms为单位,总共超过100次重复写入8个字节的长度,无论多less次适合缓冲区长度。 对于单个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,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填充短缓冲区的速度更快,看到它有半页的完整的设置代码。
如果有人想优化这个,真的去做吧。 这是可能的。
你可以做到这一点,当你初始化数组,但我不认为这就是你要求的:
byte[] myBytes = new byte[5] { 1, 1, 1, 1, 1};
testing几种方式,在不同的答案中描述。 请参阅c# testing类中的testing源