【问题标题】:Copying a `System.Guid` to `byte[]` without allocating将 `System.Guid` 复制到 `byte[]` 而不分配
【发布时间】:2016-01-22 09:24:08
【问题描述】:

我正在开发的应用程序非常注重性能,因此需要将分配保持在最低限度以保持 GC 停止。

我惊讶地发现System.Guid 没有公开任何将其byte[] 表示复制到现有缓冲区的方法。现有的唯一方法Guid.ToByteArray() 执行new byte[] 分配,否则没有它就无法获取底层字节。

所以我正在寻找一种将Guid 复制到已经存在的byte[] 缓冲区而不分配任何内存的方法(因为Guid 已经是一个值类型)。

【问题讨论】:

  • 你真的需要Guid吗?也许只是直接创建[DllImport("ole32.dll", SetLastError=true)] static extern int CoCreateGuid(byte[] guid);(或pinvoke.net/default.aspx/rpcrt4/UuidCreateSequential.html)?
  • 不幸的是,是的。问题不是我需要创建一个 Guid,而是我已经有一个 Guid 的实例,我需要在不分配中间缓冲区的情况下进入现有字节数组。
  • 请注意,在 gc 语言中,这种分配几乎没有成本(增加托儿所指针)。除非您在一个操作中处理大量数字,否则 pinvoke 甚至固定可能不会更广泛
  • 分配不是伤害你的东西。这是最终的垃圾收集成本。

标签: c# memory optimization


【解决方案1】:

我确定的解决方案来自Jil project by Kevin Montrose 的一些帮助。我没有采用那个确切的解决方案,但它启发了我想出一些我认为相当优雅的东西。

注意:以下代码使用Fixed Size Buffers,并要求使用/unsafe 开关构建您的项目(并且很可能需要完全信任才能运行)。

[StructLayout(LayoutKind.Explicit)]
unsafe struct GuidBuffer
{
    [FieldOffset(0)]
    fixed long buffer[2];

    [FieldOffset(0)]
    public Guid Guid;

    public GuidBuffer(Guid guid)
        : this()
    {
        Guid = guid;
    }

    public void CopyTo(byte[] dest, int offset)
    {
        if (dest.Length - offset < 16)
            throw new ArgumentException("Destination buffer is too small");

        fixed (byte* bDestRoot = dest)
        fixed (long* bSrc = buffer)
        {
            byte* bDestOffset = bDestRoot + offset;
            long* bDest = (long*)bDestOffset;

            bDest[0] = bSrc[0];
            bDest[1] = bSrc[1];
        }
    }
}

用法很简单:

var myGuid = Guid.NewGuid(); // however you get it
var guidBuffer = new GuidBuffer(myGuid);

var buffer = new buffer[16];
guidBuffer.CopyTo(buffer, 0);

通过计时,副本的平均持续时间为 1-2 个滴答。对于大多数应用程序来说应该足够快。

但是,如果您想获得绝对的最佳性能,一种可能性(由 Kevin 建议)是确保 offset 参数为 long-aligned(在 8-字节边界)。我的特定用例更喜欢内存而不是速度,但如果速度是最重要的事情,那将是一个很好的解决方法。

【讨论】:

    【解决方案2】:

    如果速度是主要考虑因素,您可以通过直接使用Guid 而不是通过GuidBuffer 结构来节省大量时间。这是我正在使用的扩展方法。

    public static unsafe void Encode(this byte[] array, int offset, Guid value)
    {
        if (array.Length - offset < 16) throw new ArgumentException("buffer too small");
    
        fixed (byte* pArray = array)
        {
            var pGuid = (long*)&value;
            var pDest = (long*)(pArray + offset);
            pDest[0] = pGuid[0];
            pDest[1] = pGuid[1];
        }
    }
    

    用法:

    var guid  = Guid.NewGuid();
    var array = new byte[16];
    array.Encode(0, guid);
    

    【讨论】:

    • 我不是 100% 确定,因为它已经有一段时间了,但是有一些原因我不能依赖直接使用 System.Guid。如果目前不是这种情况,那么很好的提示!
    • @rossipedia,如果你碰巧记得症结所在,我会很感激知道那是什么,即使它是 OBE。也就是说,ReSharper 帮助我获得了上面列出的最终版本。如果没有这些帮助,我不确定我是否能做到。
    • @rossipedia,顺便说一句,如果没有你的答案,我就无法做到这一点。所以谢谢你。
    猜你喜欢
    • 2020-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多