Подтвердить что ты не робот

Производительность С# - использование небезопасных указателей вместо IntPtr и маршала

Вопрос

Я переношу приложение C на С#. Приложение C вызывает множество функций из сторонней DLL, поэтому я написал P/Invoke для этих функций на С#. Некоторые из этих функций C выделяют данные, которые я должен использовать в приложении С#, поэтому я использовал IntPtr 's, Marshal.PtrToStructure и Marshal.Copy, чтобы скопировать собственные данные (массивы и структуры) в управляемые переменные.

К сожалению, приложение С# оказалось намного медленнее, чем версия C. Быстрый анализ производительности показал, что вышеупомянутое копирование данных на основе маршалинга является узким местом. Я планирую ускорить код С#, переписывая его, чтобы вместо этого использовать указатели.. Поскольку у меня нет опыта работы с небезопасным кодом и указателями на С#, мне нужно мнение экспертов относительно следующих вопросы

  • Каковы недостатки использования кода и указателей unsafe вместо IntPtr и Marshal ing? Например, это более опасно (каламбур) каким-либо образом? Люди, похоже, предпочитают маршалинг, но я не знаю, почему.
  • Является ли использование указателей для P/Invoking действительно быстрее, чем использование маршалинга? Сколько можно ожидать ускорения? Я не мог найти тестов для этого.

Пример кода

Чтобы сделать ситуацию более ясной, я взломал небольшой примерный код (реальный код намного сложнее). Я надеюсь, что этот пример показывает, что я имею в виду, когда говорю о "небезопасном коде и указателях" против "IntPtr и маршал".

Библиотека C (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

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

Приложение С#, которое использует IntPtr и 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);
  }
}

Приложение С#, которое использует unsafe код и указатели

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

Ответ 1

Это немного старый поток, но я недавно сделал чрезмерные тесты производительности с маршалингом на С#. Мне нужно отключить много данных из последовательного порта в течение многих дней. Для меня было важно не иметь утечек памяти (потому что наименьшая утечка станет существенной после нескольких миллионов вызовов), и я также сделал много статистических тестов (с использованием времени) с очень большими структурами ( > 10kb) только для ради этого (нет, у вас никогда не должно быть 10kb-структуры:-))

Я проверил следующие три стратегии unmarshalling (я также проверил сортировку). Почти во всех случаях первый (MarshalMatters) превосходил другие два. Маршал. Копи всегда был медленнее, остальные два были в основном очень близки в гонке.

Во-первых:

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

Во-вторых:

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

Третье:

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

Ответ 2

Два ответа,

  • Небезопасный код означает, что он не управляется CLR. Вы должны заботиться о ресурсах, которые он использует.

  • Вы не можете масштабировать производительность, так как на нее воздействует так много факторов. Но определенно использование указателей будет намного быстрее.

Ответ 3

Просто хотел добавить свой опыт в этот старый поток: Мы использовали Marshaling в программном обеспечении для записи звука - мы получили звуковые данные в реальном времени из микшера в нативные буферы и объединили его в байт []. Это был настоящий убийца. Мы были вынуждены перейти на небезопасные структуры как единственный способ выполнить задачу.

Если у вас нет больших нативных структур и не против, что все данные заполняются дважды - Marshaling более изящный и гораздо более безопасный.