Xiang Zhang
Xiang Zhang

Reputation: 2973

How can we copy two buffers both allocated by Marshal.AllocHGlobal?

There are ways in Marshal to copy raw buffer allocated by AllocHGlobal to/from C# array. But my question is, I have two buffers both allocated by AllocHGlobal, and I want to simply copy one buffer to another.

var buffer1 = Marshal.AllocHGlobal(size);
var buffer2 = Marshal.AllocHGlobal(size);
nativeOpOn(buffer1);
SomeCopy(buffer1, buffer2, size);

I know one possible solution is to P/Invoke system C lib for memcpy, but then I have to write different P/Invoke for support both Windows and Linux and MacOS, since they might have different P/Invoke stuff.

Upvotes: 3

Views: 1582

Answers (3)

Frederik Hoeft
Frederik Hoeft

Reputation: 1440

If you're running .NET Core or .NET 5+ then using the Unsafe.CopyBlock()/Unsafe.CopyBlockUnaligned() methods will be the fastest.

Running the following benchmarks using BenchmarkDotNet

// .NET 6.0.300

[MemoryDiagnoser]
[RPlotExporter]
public unsafe class CopyTests
{
    private readonly void* source;
    private readonly void* destination;
    
    private const int SIZE = 1024;
    
    public CopyTests()
    {
        source = NativeMemory.Alloc(SIZE);
        destination = NativeMemory.Alloc(SIZE);
    }

    [Benchmark]
    [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
    public void TestUnsafeCopyBlockUnaligned()
    {
        Unsafe.CopyBlockUnaligned(destination, source, SIZE);
    }

    [Benchmark]
    [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
    public void TestBufferMemoryCopy()
    {
        Buffer.MemoryCopy(source, destination, SIZE, SIZE);
    }
}

yields the following results:

|                       Method |     Mean |    Error |   StdDev | Allocated |
|----------------------------- |---------:|---------:|---------:|----------:|
| TestUnsafeCopyBlockUnaligned | 16.63 ns | 0.350 ns | 0.360 ns |         - |
|         TestBufferMemoryCopy | 23.88 ns | 0.264 ns | 0.234 ns |         - |

or if you like pretty diagrams:

diagram

As you can see, Unsafe.CopyBlockUnaligned() is around 25% faster.

If you're using IntPtr you'd obviously have to call myIntPtr.ToPointer() first and also allow unsafe blocks in your project settings :)

Upvotes: 1

DmitryG
DmitryG

Reputation: 17850

You can use the following approaches:

1) managed buffer:

int size = sizeof(int); // for test purposes
IntPtr buffer1 = Marshal.AllocHGlobal(size);
IntPtr buffer2 = Marshal.AllocHGlobal(size);

Marshal.WriteInt32(buffer1, 0x12345678); // native op (for test purposes)

byte[] bytes = new byte[size]; // managed buffer
Marshal.Copy(buffer1, bytes, 0, size); //buffer1=>bytes
Marshal.Copy(bytes, 0, buffer2, size); //bytes=>buffer2
// use several iterations if needed

int res =  Marshal.ReadInt32(buffer2, 0); // for test purposes (res==0x12345678)

2) managed read/write operations via the Marshal.ReadByte/WriteByte methods or any Marshal.ReadXXXe/WriteXXX methods (depending of size). Just repeat these operations in cycle:

for(int i = 0; i < size; i++) 
    Marshal.WriteByte(buffer2, i, Marshal.ReadByte(buffer1, i));

3) unsafe block (read/write buffers via unsafe pointers)

Upvotes: 1

Matthew Watson
Matthew Watson

Reputation: 109587

If you are using .Net 4.6 or later, you can use Buffer.MemoryCopy():

unsafe
{
    Buffer.MemoryCopy(p1.ToPointer(), p2.ToPointer(), destSizeBytes, sourceBytesToCopy);
}

Of course, it is unsafe.

Upvotes: 4

Related Questions