如何将 Marshal.AllocHGlobal 分配的内存清零?
我通过 Marshal.AllocHGlobal 在我的应用程序中分配一些非托管内存。然后,我将一组字节复制到此位置,并将生成的内存段转换为 struct
,然后通过 Marshal.FreeHGlobal
再次释放内存。
方法如下:
public static T Deserialize<T>(byte[] messageBytes, int start, int length)
where T : struct
{
if (start + length > messageBytes.Length)
throw new ArgumentOutOfRangeException();
int typeSize = Marshal.SizeOf(typeof(T));
int bytesToCopy = Math.Min(typeSize, length);
IntPtr targetBytes = Marshal.AllocHGlobal(typeSize);
Marshal.Copy(messageBytes, start, targetBytes, bytesToCopy);
if (length < typeSize)
{
// Zero out additional bytes at the end of the struct
}
T item = (T)Marshal.PtrToStructure(targetBytes, typeof(T));
Marshal.FreeHGlobal(targetBytes);
return item;
}
这在大多数情况下都有效,但是如果我的字节数少于 struct
所需的大小,则将“随机”值分配给最后一个字段(我使用 目标结构上的 LayoutKind.Sequential
)。我想尽可能有效地将这些悬挂区域归零。
对于上下文,此代码正在反序列化从 Linux 上的 C++ 发送的高频多播消息。
这是一个失败的测试用例:
// Give only one byte, which is too few for the struct
var s3 = MessageSerializer.Deserialize<S3>(new[] { (byte)0x21 });
Assert.AreEqual(0x21, s3.Byte);
Assert.AreEqual(0x0000, s3.Int); // hanging field should be zero, but isn't
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
private struct S3
{
public byte Byte;
public int Int;
}
重复运行此测试会导致第二个断言每次都以不同的值失败。
编辑
最后,我使用了leppie 的建议:不安全
并使用stackalloc
。这分配了一个根据需要归零的字节数组,并将吞吐量提高了 50% 到 100%,具体取决于消息大小(较大的消息会带来更大的好处)。
最终方法的结果类似于:
public static T Deserialize<T>(byte[] messageBytes, int startIndex, int length)
where T : struct
{
if (length <= 0)
throw new ArgumentOutOfRangeException("length", length, "Must be greater than zero.");
if (startIndex < 0)
throw new ArgumentOutOfRangeException("startIndex", startIndex, "Must be greater than or equal to zero.");
if (startIndex + length > messageBytes.Length)
throw new ArgumentOutOfRangeException("length", length, "startIndex + length must be <= messageBytes.Length");
int typeSize = Marshal.SizeOf(typeof(T));
unsafe
{
byte* basePtr = stackalloc byte[typeSize];
byte* b = basePtr;
int end = startIndex + Math.Min(length, typeSize);
for (int srcPos = startIndex; srcPos < end; srcPos++)
*b++ = messageBytes[srcPos];
return (T)Marshal.PtrToStructure(new IntPtr(basePtr), typeof(T));
}
}
不幸的是,这仍然需要调用 Marshal.PtrToStructure
将字节转换为目标类型。
I am allocating some unmanaged memory in my application via Marshal.AllocHGlobal
. I'm then copying a set of bytes to this location and converting the resulting segment of memory to a struct
before freeing the memory again via Marshal.FreeHGlobal
.
Here's the method:
public static T Deserialize<T>(byte[] messageBytes, int start, int length)
where T : struct
{
if (start + length > messageBytes.Length)
throw new ArgumentOutOfRangeException();
int typeSize = Marshal.SizeOf(typeof(T));
int bytesToCopy = Math.Min(typeSize, length);
IntPtr targetBytes = Marshal.AllocHGlobal(typeSize);
Marshal.Copy(messageBytes, start, targetBytes, bytesToCopy);
if (length < typeSize)
{
// Zero out additional bytes at the end of the struct
}
T item = (T)Marshal.PtrToStructure(targetBytes, typeof(T));
Marshal.FreeHGlobal(targetBytes);
return item;
}
This works for the most part, however if I have fewer bytes than the size of the struct
requires, then 'random' values are assigned to the last fields (I am using LayoutKind.Sequential
on the target struct). I'd like to zero out these hanging fields as efficiently as possible.
For context, this code is deserializing high-frequency multicast messages sent from C++ on Linux.
Here is a failing test case:
// Give only one byte, which is too few for the struct
var s3 = MessageSerializer.Deserialize<S3>(new[] { (byte)0x21 });
Assert.AreEqual(0x21, s3.Byte);
Assert.AreEqual(0x0000, s3.Int); // hanging field should be zero, but isn't
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
private struct S3
{
public byte Byte;
public int Int;
}
Running this test repeatedly causes the second assert to fail with a different value each time.
EDIT
In the end, I used leppie's suggestion of going unsafe
and using stackalloc
. This allocated a byte array that was zeroed as needed, and improved throughput from between 50% and 100%, depending upon the message size (larger messages saw greater benefit).
The final method ended up resembling:
public static T Deserialize<T>(byte[] messageBytes, int startIndex, int length)
where T : struct
{
if (length <= 0)
throw new ArgumentOutOfRangeException("length", length, "Must be greater than zero.");
if (startIndex < 0)
throw new ArgumentOutOfRangeException("startIndex", startIndex, "Must be greater than or equal to zero.");
if (startIndex + length > messageBytes.Length)
throw new ArgumentOutOfRangeException("length", length, "startIndex + length must be <= messageBytes.Length");
int typeSize = Marshal.SizeOf(typeof(T));
unsafe
{
byte* basePtr = stackalloc byte[typeSize];
byte* b = basePtr;
int end = startIndex + Math.Min(length, typeSize);
for (int srcPos = startIndex; srcPos < end; srcPos++)
*b++ = messageBytes[srcPos];
return (T)Marshal.PtrToStructure(new IntPtr(basePtr), typeof(T));
}
}
Unfortunately this still requires a call to Marshal.PtrToStructure
to convert the bytes into the target type.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
如果您使用的是 Net Core 或 NET5,您现在可以调用 Unsafe.InitBlockUnaligned:
对于除微不足道的数据大小以外的任何数据,这比手动执行指针循环要快一个数量级,因为它使用特定于平台的内在函数来实现完整的硬件加速。您可以受益于 kernel32 解决方案,但跨平台且无需手动管理本机依赖项。
If you are on Net Core or NET5, you can now call Unsafe.InitBlockUnaligned:
For anything but trivial data sizes, this is order of magnitude faster than doing the pointer loop manually, as it uses platform specific intrinsic for full hardware acceleration. You get benefit of kernel32 solution, but cross platform and without need for managing native dependencies manually.
这在 Windows 上可以正常工作:
This will work fine on Windows:
是的,正如 Jon Seigel 所说,您可以使用 Marshal.WriteByte 将其清零。
在下面的示例中,我将复制结构之前的缓冲区。
Yes as Jon Seigel said, you can zero it out using Marshal.WriteByte
In the following example, I zero out the buffer before copying the struct.
为什么不直接检查
start + length
是否在typesize
之内?顺便说一句:我会在这里
不安全
并使用for循环将额外的内存清零。这也将为您带来使用
stackalloc
的好处,它比AllocGlobal
更安全、更快。Why not just check whether
start + length
is withintypesize
?BTW: I would just go
unsafe
here and use a for loop to to zero out the additional memory.That too will give you the benefit of using
stackalloc
which is much safer and faster thanAllocGlobal
.我以前从未在 C# 中做过这些事情,但我在 MSDN 中找到了 Marshal.WriteByte(IntPtr, Int32, Byte) 。尝试一下。
I've never done this stuff in C# before, but I found Marshal.WriteByte(IntPtr, Int32, Byte) in MSDN. Try that out.
我认为您会发现使用 64 位写入而不是 8 位写入(最后几个字节仍然需要它)会快几倍。
I think you will find it to be several times faster using 64 bit writes instead of 8 bit writes (which you still need for the last few bytes).
我认为将缓冲区清零的最佳方法是这样,如果您不想,或者不能走其他路:
I think the best way to zero out a buffer is this, if you don't want, or can't go the other way:
另一种选择是,如果内存用于已知的结构类型。
使用 C# 的值类型初始值设定项也可以。
Another option is, if the memory is for a known structure type.
Using C#'s value type initializer works too.