C#性能 - 使用不安全的指针而不是IntPtr的元帅 [英] C# performance - Using unsafe pointers instead of IntPtr and Marshal

查看:151
本文介绍了C#性能 - 使用不安全的指针而不是IntPtr的元帅的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问题

我移植C应用程序到C#。的C应用程序调用大量来自第三方的DLL函数,所以我写的P / Invoke包装在C#中这些功能。有些C函数分配,我有在C#应用程序使用的数据,所以我用的的IntPtr ,<一个href=\"http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal.ptrtostructure.aspx\"><$c$c>Marshal.PtrToStructure和<一个href=\"http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal.copy.aspx\"><$c$c>Marshal.Copy本机的数据(数组和结构)复制到托管变量。

不幸的是,C#应用程序被证明是比C版慢得多。快速的性能分析表明,上述基于编组数据复制是瓶颈。 我考虑重写它使用指针,而不是加快C#code。由于我没有与不安全code和C#中三分球的经验,我需要专家的意见关于以下的问题


  1. 什么是使用不安全 code和指针,而不是的IntPtr 和<$ C $的弊端C>元帅 ING?例如,在更不安全的(双关语意)以任何方式?人们似乎preFER编组,但我不知道为什么。

  2. 是使用指针P /调用真的比使用编组快?多少加速可近似预期?我找不到任何基准测试此。

例子code

为使情况更加清晰,我砍死在一起的小例子code(真正的code要复杂得多)。我希望这个例子显示了我的意思,当我谈论不安全code和指针与IntPtr的元帅。

C库(DLL)的

MyLib.h

 的#ifndef _MY_LIB_H_
#定义_MY_LIB_H_迈德特结构
{
  INT长;
  无符号字符*字节;
};__declspec(dllexport)的无效​​CreateMyData(结构迈德特** myData的,INT长度);
__declspec(dllexport)的无效​​DestroyMyData(结构迈德特* MYDATA的);#ENDIF // _MY_LIB_H_

MyLib.c

 的#include&LT;&stdlib.h中GT;
#包括MyLib.h无效CreateMyData(结构迈德特** myData的,INT长度)
{
  INT I;  * MYDATA的=(结构迈德特*)malloc的(的sizeof(结构迈德特));
  如果(* myData的!= NULL)
  {
    (* MYDATA的) - GT;长度=长度;
    (* MYDATA的) - GT;字节=(无符号字符*)malloc的(长*的sizeof(字符));
    如果((* MYDATA的) - GT;!字节= NULL)
      对于(i = 0; I&LT;长度; ++ I)
        (* MYDATA的) - GT;字节[I] =(unsigned char型)(I%256);
  }
}无效DestroyMyData(结构迈德特* MYDATA的)
{
  如果(MYDATA的!= NULL)
  {
    如果(myData-&GT;!字节= NULL)
      免费(myData-&GT;字节);
    免费(MYDATA的);
  }
}

C应用程序

MAIN.C

 的#include&LT;&stdio.h中GT;
#包括MyLib.h无效的主要()
{
  迈德特结构* MYDATA的= NULL;
  INT长度= 100 * 1024 * 1024;  的printf(=== C ++测试=== \\ n);
  CreateMyData(安培; MYDATA的,长度);
  如果(MYDATA的!= NULL)
  {
    的printf(长度数:%d \\ n,myData-&GT;的长度);
    如果(myData-&GT;!字节= NULL)
      的printf(第一:%d个,最后数:%d \\ n,myData-&GT;字节[0],myData-&GT;字节[myData-&GT;长度 - 1]);
    其他
      输出(myData-&GT;字节为空);
  }
  其他
    的printf(MYDATA的是NULL \\ n);
  DestroyMyData(MYDATA的);
  的getchar();
}

C#应用程序,它使用的IntPtr 元帅

Program.cs的

 使用系统;
使用System.Runtime.InteropServices;公共静态类节目
{
  [StructLayout(LayoutKind.Sequential)]
  私人结构迈德特
  {
    公众诠释长度;
    公共IntPtr的字节;
  }  函数[DllImport(中是指mylib.dll)]
  私人静态外部无效CreateMyData(出IntPtr的myData的,INT长度);  函数[DllImport(中是指mylib.dll)]
  私人静态外部无效DestroyMyData(IntPtr的MYDATA的);  公共静态无效的主要()
  {
    Console.WriteLine(=== C#的测试,使用的IntPtr和元帅===);
    INT长度= 100 * 1024 * 1024;
    IntPtr的myData1;
    CreateMyData(出myData1,长度);
    如果(myData1!= IntPtr.Zero)
    {
      迈德特myData2 =(迈德特)Marshal.PtrToStructure(myData1的typeof(迈德特));
      Console.WriteLine(长度:{0},myData2.Length);
      如果(myData2.Bytes!= IntPtr.Zero)
      {
        字节[]字节=新的字节[myData2.Length]
        Marshal.Copy(myData2.Bytes,字节,0,myData2.Length);
        Console.WriteLine(第一:{0},最后:{1},字节[0],字节[myData2.Length - 1]);
      }
      其他
        Console.WriteLine(myData.Bytes是IntPtr.Zero);
    }
    其他
      Console.WriteLine(myData的是IntPtr.Zero);
    DestroyMyData(myData1);
    Console.ReadKey(真);
  }
}

C#应用程序,它使用不安全 code和指针

Program.cs的

 使用系统;
使用System.Runtime.InteropServices;公共静态类节目
{
  [StructLayout(LayoutKind.Sequential)]
  私人不安全的结构迈德特
  {
    公众诠释长度;
    公共字节*字节;
  }  函数[DllImport(中是指mylib.dll)]
  私人不安全的静态外部无效CreateMyData(出迈德特* myData的,INT长度);  函数[DllImport(中是指mylib.dll)]
  私人不安全的静态外部无效DestroyMyData(迈德特* MYDATA的);  公共不安全静态无效的主要()
  {
    Console.WriteLine(=== C#的测试,使用不安全code ===);
    INT长度= 100 * 1024 * 1024;
    迈德特* myData的;
    CreateMyData(出MYDATA的,长度);
    如果(MYDATA的!= NULL)
    {
      Console.WriteLine(长度:{0},myData-&GT;长度);
      如果(myData-&GT;!字节= NULL)
        Console.WriteLine(第一:{0},最后:{1},myData-&GT;字节[0],myData-&GT;字节[myData-&GT;长度 - 1]);
      其他
        Console.WriteLine(myData.Bytes为空);
    }
    其他
      Console.WriteLine(myData的为空);
    DestroyMyData(MYDATA的);
    Console.ReadKey(真);
  }
}


解决方案

这是一个有点老的线程,但我最近在C#编组做出过多的性能测试。我需要通过多少天从一个串口解组大量数据。有没有内存泄漏(因为最小的泄漏将获得几百万来电之后显著),这是对我很重要,我也做了很多的统计性能(使用时间)具有非常大的结构测试(> 10KB)只是为它(一个没有,你永远不应该有一个10KB结构:-))的缘故。

我测试了以下三个解组策略(我还测试编组)。在几乎所有情况下,第一个(MarshalMatters)表现优于其他两种。
Marshal.Copy总是最笨的了,另外两个大多是非常接近的比赛。

首先:

 公共类MarshalMatters
{
    公共静态ŧReadUsingMarshalUnsafe&LT; T&GT;(字节[]数据),其中T:结构
    {
        不安全
        {
            固定的(字节* p值=&放大器;数据[0])
            {
                回报(T)Marshal.PtrToStructure(新的IntPtr(P)的typeof(T));
            }
        }
    }    公共不安全静态的byte [] WriteUsingMarshalUnsafe&LT; selectedT&GT;(selectedT结构),其中selectedT:结构
    {
        字节[]的字节数组=新的字节[Marshal.SizeOf(结构)];
        固定(字节* byteArrayPtr =字节阵列)
        {
            Marshal.StructureToPtr(结构,(IntPtr的)byteArrayPtr,真正的);
        }
        返回的字节数组;
    }
}

二:

 公共类Adam_Robinson
{    私有静态ŧBytesToStruct&LT; T&GT;(字节[] RAWDATA)其中T:结构
    {
        ŧ结果=默认(T);
        的GCHandle手柄= GCHandle.Alloc(RAWDATA,GCHandleType.Pinned);
        尝试
        {
            IntPtr的rawDataPtr = handle.AddrOfPinnedObject();
            结果=(T)Marshal.PtrToStructure(rawDataPtr的typeof(T));
        }
        最后
        {
            handle.Free();
        }
        返回结果;
    }    ///&LT;总结&gt;
    ///没有复制。没有不安全的。获取的GCHandle通过的Alloc内存
    ///&LT; /总结&gt;
    ///&LT; typeparam NAME =selectedT&GT;&LT; / typeparam&GT;
    ///&LT; PARAM NAME =结构&GT;&LT; /参数&GT;
    ///&LT;&回报GT;&LT; /回报&GT;
    公共静态的byte [] StructToBytes&LT; T&GT;(T结构),其中T:结构
    {
        INT大小= Marshal.SizeOf(结构);
        字节[] = RAWDATA新的字节[大小]
        的GCHandle手柄= GCHandle.Alloc(RAWDATA,GCHandleType.Pinned);
        尝试
        {
            IntPtr的rawDataPtr = handle.AddrOfPinnedObject();
            Marshal.StructureToPtr(结构,rawDataPtr,FALSE);
        }
        最后
        {
            handle.Free();
        }
        返回RAWDATA;
    }
}

第三:

  ///&LT;总结&gt;
/// http://stackoverflow.com/questions/2623761/marshal-ptrtostructure-and-back-again-and-generic-solution-for-endianness-swap
///&LT; /总结&gt;
公共类DanB
{
    ///&LT;总结&gt;
    ///使用Marshal.Copy!不要在不安全的运行。使用AllocHGlobal获得新的内存和副本。
    ///&LT; /总结&gt;
    公共静态的byte [] GetBytes会&LT; T&GT;(T结构),其中T:结构
    {
        VAR大小= Marshal.SizeOf(结构); //或Marshal.SizeOf&LT; selectedT&GT;();在.NET 4.5.1
        字节[] = RAWDATA新的字节[大小]
        IntPtr的PTR = Marshal.AllocHGlobal(大小);        Marshal.StructureToPtr(结构,PTR,真正的);
        Marshal.Copy(PTR,RAWDATA,0,大小);
        Marshal.FreeHGlobal(PTR);
        返回RAWDATA;
    }    公共静态ŧFromBytes&LT; T&GT;(字节[]字节)其中T:结构
    {
        VAR结构=新T();
        INT大小= Marshal.SizeOf(结构); //或Marshal.SizeOf&LT; selectedT&GT;();在.NET 4.5.1
        IntPtr的PTR = Marshal.AllocHGlobal(大小);        Marshal.Copy(字节,0,PTR,大小);        结构=(T)Marshal.PtrToStructure(PTR,structure.GetType());
        Marshal.FreeHGlobal(PTR);        返回结构;
    }
}

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

解决方案

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.

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

这篇关于C#性能 - 使用不安全的指针而不是IntPtr的元帅的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆