Jed*_*dja 78 c# memset equivalent
我需要byte[]
用一个非零值填充一个.如何在C#中执行此操作而不循环遍历byte
数组中的每个?
更新:评论似乎将此分为两个问题 -
memset
我完全同意,正如埃里克和其他人所指出的,使用一个简单的循环就可以了.问题的关键是看我是否可以学习一些关于C#的新东西:)我认为Juliet的并行操作方法应该比简单的循环更快.
基准: 感谢Mikael Svenson:http://techmikael.blogspot.com/2009/12/filling-array-with-default-value.html
事实证明,for
除非你想使用不安全的代码,否则简单的循环是可行的.
抱歉我的原帖不清楚.埃里克和马克的评论都是正确的; 需要有更多专注的问题.感谢大家的建议和回应.
Mar*_*ers 58
你可以使用Enumerable.Repeat
:
byte[] a = Enumerable.Repeat((byte)10, 100).ToArray();
Run Code Online (Sandbox Code Playgroud)
第一个参数是您想要重复的元素,第二个参数是重复它的次数.
这对于小型数组是可以的,但是如果要处理非常大的数组并且性能是一个问题,则应该使用循环方法.
kon*_*ski 41
实际上,很少有人知道称为Initblk(英文版)的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;
}
Run Code Online (Sandbox Code Playgroud)
什么是性能?这是我的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
Run Code Online (Sandbox Code Playgroud)
所以值得考虑.请注意,生成的IL将无法验证.
Luc*_*ero 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;
}
}
Run Code Online (Sandbox Code Playgroud)
小智 21
在Lucero的回答基础上,这是一个更快的版本.它将使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;
}
}
Run Code Online (Sandbox Code Playgroud)
Gma*_*man 13
看起来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);
Run Code Online (Sandbox Code Playgroud)
Roo*_*ook 13
随着Span<T>
(仅是 dotnet 核心,但它是 dotnet 的未来)的出现,您还有另一种方法来解决此问题:
var array = new byte[100];
var span = new Span<byte>(array);
span.Fill(255);
Run Code Online (Sandbox Code Playgroud)
Jan*_*Jan 12
如果性能至关重要,您可以考虑使用不安全的代码并直接使用指向该数组的指针.
另一个选项可能是从msvcrt.dll导入memset并使用它.但是,调用它的开销可能很容易大于速度增益.
sta*_*afl 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);
}
Run Code Online (Sandbox Code Playgroud)
编辑:在阅读其他答案后,似乎我不是唯一有这个想法的人.不过,我现在离开这里了,因为它有点干净,而且与其他人相提并论.
如果性能绝对至关重要,那么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);
Run Code Online (Sandbox Code Playgroud)
[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);
}
Run Code Online (Sandbox Code Playgroud)
所有答案都只写单个字节 - 如果你想用字填充字节数组怎么办?还是漂浮?我偶尔会发现它的用途.因此,在以非通用的方式向"memset"编写类似的代码几次并到达此页面以找到单个字节的良好代码之后,我开始编写下面的方法.
我认为PInvoke和C++/CLI各有其缺点.为什么不让你的运行时'PInvoke'进入mscorxxx?Array.Copy和Buffer.BlockCopy肯定是本机代码.BlockCopy甚至不是"安全的" - 只要它们在数组中,您就可以将一半长时间复制到另一半,或复制到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);
}
}
Run Code Online (Sandbox Code Playgroud)
有了这个,您可以简单地添加简短的方法来获取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);
}
Run Code Online (Sandbox Code Playgroud)
或者愚蠢并使用任何类型的结构(尽管上面的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);
}
Run Code Online (Sandbox Code Playgroud)
我更改了前面提到的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
Run Code Online (Sandbox Code Playgroud)
我每次都排除了第一次调用,因为initblk和memset都受到了打击,我认为第一次调用它的时间大约是0.22ms.稍微令我惊讶的是,我的代码填充短缓冲区比initblk更快,看到它有半页的设置代码.
如果有人想要优化这一点,请继续.这是可能的.
归档时间: |
|
查看次数: |
57538 次 |
最近记录: |