Copying a `System.Guid` to `byte[]` without allocating
Asked Answered
R

3

7

The application I'm working on is very focused on performance, and as such needs to keep allocations down to a minimum to keep GC stalls down.

I was surprised to find that System.Guid does not expose any method to copy it's byte[] representation into an existing buffer. The only existing method, Guid.ToByteArray(), performs a new byte[] allocation, and otherwise there's no way to get at the underlying bytes without it.

So what I'm looking for is some way to copy a Guid to a an already existing byte[] buffer without allocating any memory (since Guid is a value type already).

Rogovy answered 22/10, 2015 at 23:3 Comment(4)
D you really need Guid? Maybe just directly create [DllImport("ole32.dll", SetLastError=true)] static extern int CoCreateGuid(byte[] guid); (or pinvoke.net/default.aspx/rpcrt4/UuidCreateSequential.html) ?Padraig
Unfortunately, yes. The problem is not that I need to create a Guid, but that I already have an instance of a Guid that I need to get into an existing byte array without allocating an intermediate buffer.Rogovy
Note in a gc language this allocation costs almost nothing ( incrementing the nursery pointer) . pinvoke or even fixed are likely ot be far more expansive unless your working on a larrge numbe rin one opSecessionist
The allocation isn't what hurts you. It's the eventual garbage collection that costs.Rogovy
R
5

The solution I settled on came from some help from the Jil project by Kevin Montrose. I didn't go with that exact solution, but it inspired me to come up with something that I think is fairly elegant.

Note: The following code uses Fixed Size Buffers and requires that your project be built with the /unsafe switch (and in all likelihood requires Full Trust to run).

[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];
        }
    }
}

Usage is simple:

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.

However, if you want to eke out the absolute best performance, one possibility (suggested by Kevin) is to ensure that the offset parameter is long-aligned (on an 8-byte boundary). My particular use case favors memory over speed, but if speed is the most important thing that would be a good way to go about it.

Rogovy answered 22/10, 2015 at 23:3 Comment(0)
P
3

If speed is the main consideration, you can shave a good bit of time by using the Guid directly instead going through the GuidBuffer struct. Here's the extension method I'm using.

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];
    }
}

Usage:

var guid  = Guid.NewGuid();
var array = new byte[16];
array.Encode(0, guid);
Perichondrium answered 3/4, 2019 at 6:18 Comment(3)
I'm not 100% sure as it's been a while, but there was some reason why I couldn't rely on using System.Guid directly. If that's not currently the case, then great tip!Rogovy
@rossipedia, if you happen to remember the sticking point, I would appreciate knowing what that was, even if it's OBE. That said, ReSharper helped me a lot to get to the final version I've listed above. I'm not sure I could have done it without that help.Perichondrium
@rossipedia, and by the way, I couldn't have done it without spring boarding off your answer. So thank you.Perichondrium
W
1

It looks like Guid has some built-in options if you have a destination span to which you want to write the bytes.

 public bool TryWriteBytes(Span<byte> destination)

 public bool TryWriteBytes(Span<byte> destination, bool bigEndian, out int bytesWritten)

https://learn.microsoft.com/en-us/dotnet/api/system.guid.trywritebytes?view=net-8.0

Welborn answered 12/6, 2024 at 15:47 Comment(1)
Only on .NET 8 and later.Natator

© 2022 - 2025 — McMap. All rights reserved.