programing

C# 퍼포먼스 - IntPtr 및 Marshal 대신 안전하지 않은 포인터 사용

prostudy 2022. 6. 28. 22:41
반응형

C# 퍼포먼스 - IntPtr 및 Marshal 대신 안전하지 않은 포인터 사용

질문.

C 플 C # c C # c 。C 앱은 서드파티 DLL에서 많은 함수를 호출하기 때문에 C#에 이러한 기능의 P/Invoke 래퍼를 작성했습니다.C해야 하는 C#을 사용했습니다.IntPtr및 네이티브 데이터(예: 및 구조)를 관리 변수에 복사합니다.

안타깝게도 C# 앱은 C 버전보다 훨씬 느리다는 것이 입증되었습니다.빠른 성능 분석 결과 위의 마샬링 기반 데이터 복사가 병목 현상인 것으로 나타났습니다.대신 포인터를 사용하도록 C# 코드를 고쳐 쓰는 것으로 속도를 높일 생각입니다.저는 C#의 안전하지 않은 코드와 포인터에 대한 경험이 없기 때문에 다음 질문에 대한 전문가의 의견이 필요합니다.

  1. 를 사용하는 것의 결점은 무엇입니까?unsafe와 .IntPtr ★★★★★★★★★★★★★★★★★」Marshal? ing?사람들은 성도를 선호하는 것 같은데 왜 그런지 모르겠어요.
  2. P/Incalling에 포인터를 사용하는 것이 Marshaling을 사용하는 것보다 정말 빠릅니까?대략 어느 정도의 속도 향상을 기대할 수 있습니까?이것에 대한 벤치마크 테스트를 찾을 수 없었습니다.

코드 예시

상황을 더 명확히 하기 위해 작은 예제 코드를 해킹했습니다(실제 코드는 훨씬 복잡합니다).이 예제가 "안전하지 않은 코드와 포인터" 대 "IntPtr과 Marshal"에 대해 설명할 때 무엇을 의미하는지 보여주길 바랍니다.

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 어플리케이션

메인.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);
  }
}

조금 오래된 스레드이지만 최근 C#의 마샬링으로 과도한 퍼포먼스 테스트를 실시했습니다.시리얼 포트에서 며칠에 걸쳐 많은 데이터를 마킹 해제해야 합니다.메모리 누수가 없는 것이 중요했습니다(최소한의 누수는 200만 콜 후에 현저하게 되기 때문입니다).또, 단지 그것을 위해서, 매우 큰 구조(10 kb이상의)를 사용한 통계 퍼포먼스(사용시간) 테스트도 많이 실시했습니다(아니오, 10 kb 구조:-).

다음 3가지 언마샬링 전략을 테스트했습니다(마샬링도 테스트했습니다).거의 모든 경우에서 첫 번째 제품(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;
    }
}

상호운용성 고려사항에서는 마샬링이 필요한 이유와 시기, 비용을 설명합니다.견적:

  1. 마샬링은 발신자와 착신자가 같은 데이터인스턴스에서 동작할 수 없는 경우에 발생합니다.
  2. 마샬링이 반복되면 어플리케이션의 퍼포먼스에 악영향을 미칠 수 있습니다.

따라서 질문에 대한 답변은 다음과 같습니다.

...마샬링을 사용하는 것보다 P/호출에 대한 포인터를 사용하는 것이 매우 빠릅니다...

먼저 관리 대상 코드가 관리 대상 외의 메서드 반환 값 인스턴스에서 작동할 수 있는지 여부를 자문합니다.답변이 '예'인 경우 마샬링과 관련된 퍼포먼스 비용은 필요하지 않습니다.대략적인 시간 절약은 O(n) 함수입니다.여기서 마샬링된 인스턴스의 크기n 입니다.또, 관리 대상 블록과 관리 대상 블록의 양쪽 모두의 데이터를 동시에 메모리에 보관하지 않는 것으로( 「IntPtr 와 Marshal」의 예에서는), 오버헤드와 메모리의 부하를 경감합니다.

안전하지 않은 코드 및 포인터 사용의 결점은 무엇입니까?

단점은 포인터를 통해 메모리에 직접 액세스하는 것과 관련된 위험입니다.C 또는 C++에서 포인터를 사용하는 것보다 안전한 것은 없습니다.필요할 때 이치에 맞게 사용하세요.자세한 내용은 이쪽입니다.

이 예에서는 관리 코드 오류 후 할당된 관리되지 않는 메모리의 해제는 보장되지 않는다는 "안전" 문제가 있습니다.베스트 프랙티스는

CreateMyData(out myData1, length);

if(myData1!=IntPtr.Zero) {
    try {
        // -> use myData1
        ...
        // <-
    }
    finally {
        DestroyMyData(myData1);
    }
}

아직 책을 읽고 계신 분들을 위해

내가 어떤 대답에서도 본 적이 없는 것 같아 안전하지 않은 코드가 보안상의 위험을 내포하고 있어큰 위험은 아니지만 활용하기엔 상당히 어려운 일입니다.단, 저처럼 PCI 준거 조직에서 일하는 경우 안전하지 않은 코드는 이러한 이유로 정책에 의해 허용되지 않습니다.

관리 코드는 일반적으로 매우 안전합니다.이는 CLR이 메모리의 위치와 할당을 처리하여 사용자가 접근하거나 쓸 수 없도록 하기 때문입니다.

안전하지 않은 키워드를 사용하고 '/unsafe'로 컴파일하고 포인터를 사용하는 경우 이러한 검사를 생략하고 다른 사용자가 응용 프로그램을 사용하여 응용 프로그램이 실행 중인 시스템에 대해 일정 수준의 무단 액세스 권한을 얻을 수 있습니다.버퍼 오버런 공격과 같은 것을 사용하면 프로그램 카운터가 액세스할 수 있는 메모리 영역에 명령을 쓰도록 코드를 속이거나(예: 코드 주입), 기계를 크래시할 수 있습니다.

수년 전 SQL Server는 실제로 예상보다 훨씬 긴 TDS 패킷으로 전달된 악성 코드의 희생양이 되었습니다.패킷을 읽는 메서드는 길이를 확인하지 않고 예약된 주소 공간을 지나 내용을 계속 씁니다.추가 길이와 내용은 다음 메서드의 주소에서 전체 프로그램을 메모리에 쓸 수 있도록 세심하게 제작되었습니다.그런 다음 공격자는 액세스 수준이 가장 높은 컨텍스트 내에서 SQL 서버에 의해 자체 코드가 실행되도록 했습니다.트랜스포트 레이어 스택의 취약성이 이 지점보다 낮기 때문에 암호화를 해제할 필요도 없습니다.

이 오래된 스레드에 제 경험을 더하고 싶습니다.사운드 레코딩 소프트웨어에서 Marshaling을 사용했습니다.믹서로부터 실시간 사운드 데이터를 네이티브 버퍼로 수신해, 바이트[]로 마샬링했습니다.퍼포먼스 킬러였어요우리는 작업을 완료하기 위한 유일한 방법으로서 안전하지 않은 구조물로 이동해야만 했다.

대형 네이티브 구조를 가지고 있지 않고 모든 데이터가 두 번 채워지는 것에 개의치 않는 경우 - Marshaling이 더 우아하고 훨씬 더 안전한 접근 방식입니다.

오늘도 같은 질문이 있어서 구체적인 측정값을 찾고 있었지만 찾을 수 없었습니다.그래서 제가 직접 시험을 쳤죠.

이 테스트는 10k x 10k RGB 이미지의 픽셀 데이터를 복사하는 것입니다.이미지 데이터는 300MB(3*10^9바이트)입니다.일부 방법은 이 데이터를 10회 복사하고 다른 방법은 더 빠르기 때문에 100회 복사합니다.사용되는 복사 방법은 다음과 같습니다.

  • 바이트 포인터를 통한 어레이 액세스
  • 보안관님복사(): a) 1 * 300 MB, b) 1e9 * 3 바이트
  • Buffer.BlockCopy(): a) 1 * 300 MB, b) 1e9 * 3 바이트

테스트 환경:
CPU: 인텔 Core i7-3630QM @ 2.40GHz
OS: Windows 7 Pro x 64 SP1
Visual Studio 2015.3, 코드는 C++/CLI, 타겟 .net 버전은 4.5.2로 디버깅용으로 컴파일되었습니다.

테스트 결과:
어떤 방법으로든 1코어의 CPU 부하는 100%입니다(총 CPU 부하의 12.5%).
속도와 실행 시간 비교:

method                        speed   exec.time
Marshal.Copy (1*300MB)      100   %        100%
Buffer.BlockCopy (1*300MB)   98   %        102%
Pointer                       4.4 %       2280%
Buffer.BlockCopy (1e9*3B)     1.4 %       7120%
Marshal.Copy (1e9*3B)         0.95%      10600%

실행시간 및 계산된 평균 throughput은 아래 코드에 코멘트로 기재되어 있습니다.

//------------------------------------------------------------------------------
static void CopyIntoBitmap_Pointer (array<unsigned char>^ i_aui8ImageData,
                                    BitmapData^ i_ptrBitmap,
                                    int i_iBytesPerPixel)
{
  char* scan0 = (char*)(i_ptrBitmap->Scan0.ToPointer ());

  int ixCnt = 0;
  for (int ixRow = 0; ixRow < i_ptrBitmap->Height; ixRow++)
  {
    for (int ixCol = 0; ixCol < i_ptrBitmap->Width; ixCol++)
    {
      char* pPixel = scan0 + ixRow * i_ptrBitmap->Stride + ixCol * 3;
      pPixel[0] = i_aui8ImageData[ixCnt++];
      pPixel[1] = i_aui8ImageData[ixCnt++];
      pPixel[2] = i_aui8ImageData[ixCnt++];
    }
  }
}

//------------------------------------------------------------------------------
static void CopyIntoBitmap_MarshallLarge (array<unsigned char>^ i_aui8ImageData,
                                          BitmapData^ i_ptrBitmap)
{
  IntPtr ptrScan0 = i_ptrBitmap->Scan0;
  Marshal::Copy (i_aui8ImageData, 0, ptrScan0, i_aui8ImageData->Length);
}

//------------------------------------------------------------------------------
static void CopyIntoBitmap_MarshalSmall (array<unsigned char>^ i_aui8ImageData,
                                         BitmapData^ i_ptrBitmap,
                                         int i_iBytesPerPixel)
{
  int ixCnt = 0;
  for (int ixRow = 0; ixRow < i_ptrBitmap->Height; ixRow++)
  {
    for (int ixCol = 0; ixCol < i_ptrBitmap->Width; ixCol++)
    {
      IntPtr ptrScan0 = IntPtr::Add (i_ptrBitmap->Scan0, i_iBytesPerPixel);
      Marshal::Copy (i_aui8ImageData, ixCnt, ptrScan0, i_iBytesPerPixel);
      ixCnt += i_iBytesPerPixel;
    }
  }
}

//------------------------------------------------------------------------------
void main ()
{
  int iWidth = 10000;
  int iHeight = 10000;
  int iBytesPerPixel = 3;
  Bitmap^ oBitmap = gcnew Bitmap (iWidth, iHeight, PixelFormat::Format24bppRgb);
  BitmapData^ oBitmapData = oBitmap->LockBits (Rectangle (0, 0, iWidth, iHeight), ImageLockMode::WriteOnly, oBitmap->PixelFormat);
  array<unsigned char>^ aui8ImageData = gcnew array<unsigned char> (iWidth * iHeight * iBytesPerPixel);
  int ixCnt = 0;
  for (int ixRow = 0; ixRow < iHeight; ixRow++)
  {
    for (int ixCol = 0; ixCol < iWidth; ixCol++)
    {
      aui8ImageData[ixCnt++] = ixRow * 250 / iHeight;
      aui8ImageData[ixCnt++] = ixCol * 250 / iWidth;
      aui8ImageData[ixCnt++] = ixCol;
    }
  }

  //========== Pointer ==========
  // ~ 8.97 sec for 10k * 10k * 3 * 10 exec, ~ 334 MB/s
  int iExec = 10;
  DateTime dtStart = DateTime::Now;
  for (int ixExec = 0; ixExec < iExec; ixExec++)
  {
    CopyIntoBitmap_Pointer (aui8ImageData, oBitmapData, iBytesPerPixel);
  }
  TimeSpan tsDuration = DateTime::Now - dtStart;
  Console::WriteLine (tsDuration + "  " + ((double)aui8ImageData->Length * iExec / tsDuration.TotalSeconds / 1e6));

  //========== Marshal.Copy, 1 large block ==========
  // 3.94 sec for 10k * 10k * 3 * 100 exec, ~ 7617 MB/s
  iExec = 100;
  dtStart = DateTime::Now;
  for (int ixExec = 0; ixExec < iExec; ixExec++)
  {
    CopyIntoBitmap_MarshallLarge (aui8ImageData, oBitmapData);
  }
  tsDuration = DateTime::Now - dtStart;
  Console::WriteLine (tsDuration + "  " + ((double)aui8ImageData->Length * iExec / tsDuration.TotalSeconds / 1e6));

  //========== Marshal.Copy, many small 3-byte blocks ==========
  // 41.7 sec for 10k * 10k * 3 * 10 exec, ~ 72 MB/s
  iExec = 10;
  dtStart = DateTime::Now;
  for (int ixExec = 0; ixExec < iExec; ixExec++)
  {
    CopyIntoBitmap_MarshalSmall (aui8ImageData, oBitmapData, iBytesPerPixel);
  }
  tsDuration = DateTime::Now - dtStart;
  Console::WriteLine (tsDuration + "  " + ((double)aui8ImageData->Length * iExec / tsDuration.TotalSeconds / 1e6));

  //========== Buffer.BlockCopy, 1 large block ==========
  // 4.02 sec for 10k * 10k * 3 * 100 exec, ~ 7467 MB/s
  iExec = 100;
  array<unsigned char>^ aui8Buffer = gcnew array<unsigned char> (aui8ImageData->Length);
  dtStart = DateTime::Now;
  for (int ixExec = 0; ixExec < iExec; ixExec++)
  {
    Buffer::BlockCopy (aui8ImageData, 0, aui8Buffer, 0, aui8ImageData->Length);
  }
  tsDuration = DateTime::Now - dtStart;
  Console::WriteLine (tsDuration + "  " + ((double)aui8ImageData->Length * iExec / tsDuration.TotalSeconds / 1e6));

  //========== Buffer.BlockCopy, many small 3-byte blocks ==========
  // 28.0 sec for 10k * 10k * 3 * 10 exec, ~ 107 MB/s
  iExec = 10;
  dtStart = DateTime::Now;
  for (int ixExec = 0; ixExec < iExec; ixExec++)
  {
    int ixCnt = 0;
    for (int ixRow = 0; ixRow < iHeight; ixRow++)
    {
      for (int ixCol = 0; ixCol < iWidth; ixCol++)
      {
        Buffer::BlockCopy (aui8ImageData, ixCnt, aui8Buffer, ixCnt, iBytesPerPixel);
        ixCnt += iBytesPerPixel;
      }
    }
  }
  tsDuration = DateTime::Now - dtStart;
  Console::WriteLine (tsDuration + "  " + ((double)aui8ImageData->Length * iExec / tsDuration.TotalSeconds / 1e6));

  oBitmap->UnlockBits (oBitmapData);

  oBitmap->Save ("d:\\temp\\bitmap.bmp", ImageFormat::Bmp);
}

관련 정보:
memcpy() 및 memmove()가 포인터의 증가보다 빠른 이유는 무엇입니까?
Array.Copy vs Buffer.BlockCopy, 답변: https://stackoverflow.com/a/33865267
https://github.com/dotnet/coreclr/issues/2430 "어레이.복사 및 버퍼BlockCopy x2에서 x3으로 속도가 1kB 미만 느림
https://github.com/dotnet/coreclr/blob/master/src/vm/comutilnative.cpp, 718번 라인 작성:Buffer.BlockCopy()사용하다memmove

서드파티 DLL에 코드가 호출된다고 하셨기 때문에 안전하지 않은 코드가 고객님의 시나리오에 더 적합하다고 생각합니다.에서 가변장 어레이가 중단되는 특수한 상황이 발생하였습니다.알겠습니다만, 항상 이러한 종류의 사용이 발생하는 것은 알고 있습니다만, 반드시 그렇지만은 않습니다.예를 들어 다음과 같은 몇 가지 질문이 있을 수 있습니다.

가변 크기 어레이를 포함하는 구조를 C#에 정렬하려면 어떻게 해야 합니까?

만약... 내가 말한다면...이 경우 서드파티 라이브러리를 약간 수정하면 다음 사용법을 고려할 수 있습니다.

using System.Runtime.InteropServices;

public static class Program { /*
    [StructLayout(LayoutKind.Sequential)]
    private struct MyData {
        public int Length;
        public byte[] Bytes;
    } */

    [DllImport("MyLib.dll")]
    // __declspec(dllexport) void WINAPI CreateMyDataAlt(BYTE bytes[], int length);
    private static extern void CreateMyDataAlt(byte[] myData, ref int length);

    /* 
    [DllImport("MyLib.dll")]
    private static extern void DestroyMyData(byte[] myData); */

    public static void Main() {
        Console.WriteLine("=== C# test, using IntPtr and Marshal ===");
        int length = 100*1024*1024;
        var myData1 = new byte[length];
        CreateMyDataAlt(myData1, ref length);

        if(0!=length) {
            // MyData myData2 = (MyData)Marshal.PtrToStructure(myData1, typeof(MyData));

            Console.WriteLine("Length: {0}", 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}", myData1[0], myData1[length-1]); /*
            }
            else {
                Console.WriteLine("myData.Bytes is IntPtr.Zero");
            } */
        }
        else {
            Console.WriteLine("myData is empty");
        }

        // DestroyMyData(myData1);
        Console.ReadKey(true);
    }
}

보시다시피, 원래의 마샬링 코드는 코멘트 아웃 되어 있습니다.CreateMyDataAlt(byte[], ref int)변경된 외부 관리되지 않는 함수의 경우CreateMyDataAlt(BYTE [], int)데이터 복사 및 포인터 체크가 불필요하게 되는 경우도 있습니다.즉, 코드가 더 단순해지고 실행 속도가 더 빨라질 수 있습니다.

그럼, 수정이 뭐가 그렇게 달라졌나요?이제 바이트 배열은 와전 없이 직접 마샬링됩니다.struct관리되지 않는 쪽으로 넘어갔습니다.관리 대상 외 코드에 메모리를 할당하는 것이 아니라 데이터에 데이터를 채우는 것(실장 상세 생략)으로, 콜 후에 필요한 데이터가 관리 대상 측에 제공됩니다.데이터가 채워지지 않았으므로 사용해서는 안 된다는 것을 나타내려면 다음과 같이 설정할 수 있습니다.length관리 대상 측에 알리기 위해 0을 클릭합니다.바이트 어레이는 관리 대상 측에 할당되어 있기 때문에 나중에 수집될 수 있으므로 이를 처리할 필요가 없습니다.

두 가지 답변이 있습니다.

  1. 안전하지 않은 코드는 CLR에 의해 관리되지 않음을 의미합니다.사용하는 자원을 관리할 필요가 있습니다.

  2. 성능에 영향을 미치는 요인이 너무 많기 때문에 성능을 조정할 수 없습니다.하지만 분명히 포인터를 사용하는 것이 훨씬 더 빠를 것이다.

언급URL : https://stackoverflow.com/questions/17549123/c-sharp-performance-using-unsafe-pointers-instead-of-intptr-and-marshal

반응형