在不分配内存的情况下将`System.Guid`复制到`byte[]`中

7
我正在处理的应用程序非常注重性能,因此需要将分配降至最低,以避免GC暂停。
我惊讶地发现,System.Guid没有公开将其byte[]表示复制到现有缓冲区的任何方法。唯一存在的方法Guid.ToByteArray()执行new byte[]分配,否则没有办法获取底层字节。
因此,我正在寻找一种方法,将Guid复制到已存在的byte[]缓冲区中,而不分配任何内存(因为Guid已经是值类型)。

1
你真的需要 Guid 吗?也许直接创建 [DllImport("ole32.dll", SetLastError=true)] static extern int CoCreateGuid(byte[] guid);(或者 http://www.pinvoke.net/default.aspx/rpcrt4/UuidCreateSequential.html)会更好? - Alexei Levenkov
很不幸,是的。问题不在于我需要创建一个Guid,而是我已经有一个Guid实例,我需要将其放入现有的字节数组中,而不需要分配中间缓冲区。 - rossipedia
请注意,在垃圾回收语言中,这种分配几乎不会产生任何成本(增加幼儿园指针)。除非您在处理一个大型数字,否则PInvoke甚至Fixed可能会更昂贵。 - user1496062
2
分配并不会伤害你,真正花费的是最终的垃圾回收。 - rossipedia
2个回答

5
我采用的解决方案来自Jil项目,由Kevin Montrose提供帮助。虽然我没有采用完全相同的解决方案,但它启发了我想出了一些比较优雅的方法。 注意:以下代码使用固定大小缓冲区,需要在编译项目时使用/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);

Timing this yielded an average duration of 1-2 ticks for the copy. Should be fast enough for most any application.
然而,如果您想获得绝对最佳的性能,一个可能的方法(由Kevin建议)是确保offset参数是长整型对齐(在8字节边界上)。我的特定用例偏向于内存而不是速度,但如果速度是最重要的事情,那么这是一个很好的方法。

3

如果速度是最重要的考虑因素,您可以直接使用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
@rossipedia,如果你还记得那个难点是什么,即使它已经过时了,我也很感激知道。话虽如此,ReSharper帮助我很多,让我得以完成上面列出的最终版本。我不确定如果没有这个帮助我是否能够做到。 - Matt Davis
@rossipedia,顺便说一下,如果没有你的答案作为跳板,我是做不到的。所以谢谢你。 - Matt Davis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接