Dre*_*kes 14 .net memory unmanaged-memory
我通过我的应用程序分配一些非托管内存Marshal.AllocHGlobal.然后,我将一组字节复制到此位置,并将生成的内存段转换为a,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;
}
Run Code Online (Sandbox Code Playgroud)
这在大多数情况下都有效,但是如果我的字节数少于需求的大小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;
}
Run Code Online (Sandbox Code Playgroud)
重复运行此测试会导致第二个断言每次都以不同的值失败.
编辑
最后,我使用了leppie关于去unsafe和使用的建议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));
}
}
Run Code Online (Sandbox Code Playgroud)
不幸的是,这仍然需要调用Marshal.PtrToStructure将字节转换为目标类型.
小智 16
[DllImport("kernel32.dll")]
static extern void RtlZeroMemory(IntPtr dst, UIntPtr length);
...
RtlZeroMemory(targetBytes, typeSize);
Run Code Online (Sandbox Code Playgroud)
如果您使用的是 Net Core 或 NET5,您现在可以调用Unsafe.InitBlockUnaligned:
Unsafe.InitBlockUnaligned((byte*)ptr, 0, byteCount)
Run Code Online (Sandbox Code Playgroud)
对于除微不足道的数据大小之外的任何数据,这比手动执行指针循环要快一个数量级,因为它使用特定于平台的内在函数来实现完整的硬件加速。您可以受益于 kernel32 解决方案,但跨平台且无需手动管理本机依赖项。
小智 6
这将在Windows上正常工作:
namespace KernelPInvoke
{
/// <summary>
/// Implements some of the C functions declared in string.h
/// </summary>
public static class MemoryWrapper
{
[DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
static extern void CopyMemory(IntPtr destination, IntPtr source, uint length);
[DllImport("kernel32.dll", EntryPoint = "MoveMemory", SetLastError = false)]
static extern void MoveMemory(IntPtr destination, IntPtr source, uint length);
[DllImport("kernel32.dll", EntryPoint = "RtlFillMemory", SetLastError = false)]
static extern void FillMemory(IntPtr destination, uint length, byte fill);
}
var ptr = Marshal.AllocHGlobal(size);
try
{
MemoryWrapper.FillMemory(ptr, size, 0);
// further work...
}
finally
{
Marshal.FreeHGlobal(ptr);
}
}
Run Code Online (Sandbox Code Playgroud)
为什么不只检查是否start + length在范围内typesize?
顺便说一句:我会去unsafe这里并使用 for 循环将额外的内存清零。
stackalloc这也将为您带来比使用更安全和更快的好处AllocGlobal。