Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# performance - Using unsafe pointers instead of IntPtr and Marshal

Question

I'm porting a C application into C#. The C app calls lots of functions from a 3rd-party DLL, so I wrote P/Invoke wrappers for these functions in C#. Some of these C functions allocate data which I have to use in the C# app, so I used IntPtr's, Marshal.PtrToStructure and Marshal.Copy to copy the native data (arrays and structures) into managed variables.

Unfortunately, the C# app proved to be much slower than the C version. A quick performance analysis showed that the above mentioned marshaling-based data copying is the bottleneck. I'm considering to speed up the C# code by rewriting it to use pointers instead. Since I don't have experience with unsafe code and pointers in C#, I need expert opinion regarding the following questions:

  1. What are the drawbacks of using unsafe code and pointers instead of IntPtr and Marshaling? For example, is it more unsafe (pun intended) in any way? People seem to prefer marshaling, but I don't know why.
  2. Is using pointers for P/Invoking really faster than using marshaling? How much speedup can be expected approximately? I couldn't find any benchmark tests for this.

Example code

To make the situation more clear, I hacked together a small example code (the real code is much more complex). I hope this example shows what I mean when I'm talking about "unsafe code and pointers" vs. "IntPtr and Marshal".

C library (DLL)

MyLib.h

#ifndef _MY_LIB_H_ #define _MY_LIB_H_  struct MyData  {   int length;   unsigned char* bytes; };  __declspec(dllexport) void CreateMyData(struct MyData** myData, int length); __declspec(dllexport) void DestroyMyData(struct MyData* myData);  #endif // _MY_LIB_H_ 

MyLib.c

#include <stdlib.h> #include "MyLib.h"  void CreateMyData(struct MyData** myData, int length) {   int i;    *myData = (struct MyData*)malloc(sizeof(struct MyData));   if (*myData != NULL)   {     (*myData)->length = length;     (*myData)->bytes = (unsigned char*)malloc(length * sizeof(char));     if ((*myData)->bytes != NULL)       for (i = 0; i < length; ++i)         (*myData)->bytes[i] = (unsigned char)(i % 256);   } }  void DestroyMyData(struct MyData* myData) {   if (myData != NULL)   {     if (myData->bytes != NULL)       free(myData->bytes);     free(myData);   } } 

C application

Main.c

#include <stdio.h> #include "MyLib.h"  void main() {   struct MyData* myData = NULL;   int length = 100 * 1024 * 1024;    printf("=== C++ test ===\n");   CreateMyData(&myData, length);   if (myData != NULL)   {     printf("Length: %d\n", myData->length);     if (myData->bytes != NULL)       printf("First: %d, last: %d\n", myData->bytes[0], myData->bytes[myData->length - 1]);     else       printf("myData->bytes is NULL");   }   else     printf("myData is NULL\n");   DestroyMyData(myData);   getchar(); } 

C# application, which uses IntPtr and Marshal

Program.cs

using System; using System.Runtime.InteropServices;  public static class Program {   [StructLayout(LayoutKind.Sequential)]   private struct MyData   {     public int Length;     public IntPtr Bytes;   }    [DllImport("MyLib.dll")]   private static extern void CreateMyData(out IntPtr myData, int length);    [DllImport("MyLib.dll")]   private static extern void DestroyMyData(IntPtr myData);    public static void Main()   {     Console.WriteLine("=== C# test, using IntPtr and Marshal ===");     int length = 100 * 1024 * 1024;     IntPtr myData1;     CreateMyData(out myData1, length);     if (myData1 != IntPtr.Zero)     {       MyData myData2 = (MyData)Marshal.PtrToStructure(myData1, typeof(MyData));       Console.WriteLine("Length: {0}", myData2.Length);       if (myData2.Bytes != IntPtr.Zero)       {         byte[] bytes = new byte[myData2.Length];         Marshal.Copy(myData2.Bytes, bytes, 0, myData2.Length);         Console.WriteLine("First: {0}, last: {1}", bytes[0], bytes[myData2.Length - 1]);       }       else         Console.WriteLine("myData.Bytes is IntPtr.Zero");     }     else       Console.WriteLine("myData is IntPtr.Zero");     DestroyMyData(myData1);     Console.ReadKey(true);   } } 

C# application, which uses unsafe code and pointers

Program.cs

using System; using System.Runtime.InteropServices;  public static class Program {   [StructLayout(LayoutKind.Sequential)]   private unsafe struct MyData   {     public int Length;     public byte* Bytes;   }    [DllImport("MyLib.dll")]   private unsafe static extern void CreateMyData(out MyData* myData, int length);    [DllImport("MyLib.dll")]   private unsafe static extern void DestroyMyData(MyData* myData);    public unsafe static void Main()   {     Console.WriteLine("=== C# test, using unsafe code ===");     int length = 100 * 1024 * 1024;     MyData* myData;     CreateMyData(out myData, length);     if (myData != null)     {       Console.WriteLine("Length: {0}", myData->Length);       if (myData->Bytes != null)         Console.WriteLine("First: {0}, last: {1}", myData->Bytes[0], myData->Bytes[myData->Length - 1]);       else         Console.WriteLine("myData.Bytes is null");     }     else       Console.WriteLine("myData is null");     DestroyMyData(myData);     Console.ReadKey(true);   } } 
like image 743
kol Avatar asked Jul 09 '13 13:07

kol


People also ask

What C is used for?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...

What is the full name of C?

In the real sense it has no meaning or full form. It was developed by Dennis Ritchie and Ken Thompson at AT&T bell Lab. First, they used to call it as B language then later they made some improvement into it and renamed it as C and its superscript as C++ which was invented by Dr.

Is C language easy?

C is a general-purpose language that most programmers learn before moving on to more complex languages. From Unix and Windows to Tic Tac Toe and Photoshop, several of the most commonly used applications today have been built on C. It is easy to learn because: A simple syntax with only 32 keywords.

Is C programming hard?

C is more difficult to learn than JavaScript, but it's a valuable skill to have because most programming languages are actually implemented in C. This is because C is a “machine-level” language. So learning it will teach you how a computer works and will actually make learning new languages in the future easier.


2 Answers

It's a little old thread, but I recently made excessive performance tests with marshaling in C#. I need to unmarshal lots of data from a serial port over many days. It was important to me to have no memory leaks (because the smallest leak will get significant after a couple of million calls) and I also made a lot of statistical performance (time used) tests with very big structs (>10kb) just for the sake of it (an no, you should never have a 10kb struct :-) )

I tested the following three unmarshalling strategies (I also tested the marshalling). In nearly all cases the first one (MarshalMatters) outperformed the other two. Marshal.Copy was always slowest by far, the other two were mostly very close together in the race.

Using unsafe code can pose a significant security risk.

First:

public class MarshalMatters {     public static T ReadUsingMarshalUnsafe<T>(byte[] data) where T : struct     {         unsafe         {             fixed (byte* p = &data[0])             {                 return (T)Marshal.PtrToStructure(new IntPtr(p), typeof(T));             }         }     }      public unsafe static byte[] WriteUsingMarshalUnsafe<selectedT>(selectedT structure) where selectedT : struct     {         byte[] byteArray = new byte[Marshal.SizeOf(structure)];         fixed (byte* byteArrayPtr = byteArray)         {             Marshal.StructureToPtr(structure, (IntPtr)byteArrayPtr, true);         }         return byteArray;     } } 

Second:

public class Adam_Robinson {      private static T BytesToStruct<T>(byte[] rawData) where T : struct     {         T result = default(T);         GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned);         try         {             IntPtr rawDataPtr = handle.AddrOfPinnedObject();             result = (T)Marshal.PtrToStructure(rawDataPtr, typeof(T));         }         finally         {             handle.Free();         }         return result;     }      /// <summary>     /// no Copy. no unsafe. Gets a GCHandle to the memory via Alloc     /// </summary>     /// <typeparam name="selectedT"></typeparam>     /// <param name="structure"></param>     /// <returns></returns>     public static byte[] StructToBytes<T>(T structure) where T : struct     {         int size = Marshal.SizeOf(structure);         byte[] rawData = new byte[size];         GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned);         try         {             IntPtr rawDataPtr = handle.AddrOfPinnedObject();             Marshal.StructureToPtr(structure, rawDataPtr, false);         }         finally         {             handle.Free();         }         return rawData;     } } 

Third:

/// <summary> /// http://stackoverflow.com/questions/2623761/marshal-ptrtostructure-and-back-again-and-generic-solution-for-endianness-swap /// </summary> public class DanB {     /// <summary>     /// uses Marshal.Copy! Not run in unsafe. Uses AllocHGlobal to get new memory and copies.     /// </summary>     public static byte[] GetBytes<T>(T structure) where T : struct     {         var size = Marshal.SizeOf(structure); //or Marshal.SizeOf<selectedT>(); in .net 4.5.1         byte[] rawData = new byte[size];         IntPtr ptr = Marshal.AllocHGlobal(size);          Marshal.StructureToPtr(structure, ptr, true);         Marshal.Copy(ptr, rawData, 0, size);         Marshal.FreeHGlobal(ptr);         return rawData;     }      public static T FromBytes<T>(byte[] bytes) where T : struct     {         var structure = new T();         int size = Marshal.SizeOf(structure);  //or Marshal.SizeOf<selectedT>(); in .net 4.5.1         IntPtr ptr = Marshal.AllocHGlobal(size);          Marshal.Copy(bytes, 0, ptr, size);          structure = (T)Marshal.PtrToStructure(ptr, structure.GetType());         Marshal.FreeHGlobal(ptr);          return structure;     } } 
like image 133
Xan-Kun Clark-Davis Avatar answered Sep 24 '22 18:09

Xan-Kun Clark-Davis


Considerations in Interoperability explains why and when Marshaling is required and at what cost. Quote:

  1. Marshaling occurs when a caller and a callee cannot operate on the same instance of data.
  2. repeated marshaling can negatively affect the performance of your application.

Therefore, answering your question if

... using pointers for P/Invoking really faster than using marshaling ...

first ask yourself a question if the managed code is able to operate on the unmanaged method return value instance. If the answer is yes then Marshaling and the associated performance cost is not required. The approximate time saving would be O(n) function where n of the size of the marshalled instance. In addition, not keeping both managed and unmanaged blocks of data in memory at the same time for the duration of the method (in "IntPtr and Marshal" example) eliminates additional overhead and the memory pressure.

What are the drawbacks of using unsafe code and pointers ...

The drawback is the risk associated with accessing the memory directly through pointers. There is nothing less safe to it than using pointers in C or C++. Use it if needed and makes sense. More details are here.

There is one "safety" concern with the presented examples: releasing of allocated unmanaged memory is not guaranteed after the managed code errors. The best practice is to

CreateMyData(out myData1, length);  if(myData1!=IntPtr.Zero) {     try {         // -> use myData1         ...         // <-     }     finally {         DestroyMyData(myData1);     } } 
like image 44
Serge Pavlov Avatar answered Sep 22 '22 18:09

Serge Pavlov