1. 程式人生 > 程式設計 >C#如何快速釋放記憶體的大陣列詳解

C#如何快速釋放記憶體的大陣列詳解

前言

本文告訴大家如何使用 Marshal 做出可以快速釋放記憶體的大陣列。最近在做 3D ,需要不斷申請一段大記憶體陣列,然後就釋放他,但是 C# 對於大記憶體不是立刻釋放,所以就存在一定的效能問題。在部落格園看到了一位大神使用 Marshal 做出快速申請的大陣列,於是我就學他的方法來弄一個。本文告訴大家這個類是如何使用。

在使用的時候,先來看下原來的 C# 的大陣列效能。可以看到在不停gc,效能不好

C#如何快速釋放記憶體的大陣列詳解

 static void Main(string[] args)
 {
  for (int i = 0; i < 10000; i++)
  {
  Foo();
  }
  Console.ReadKey();
 }

 private static void Foo()
 {
  var foo = new byte[1000000000];
 }

介紹

在使用 Marshal 之前需要知道這是什麼,其實 Marshal 就是一個提供 COM 互操作的方法。

使用

下面使用一個快速申請 int 陣列來告訴大家如何使用。

是否還記得 C 的申請陣列?其實下面的方法和 C 的相同

  int n = 100000;//長度
  IntPtr buffer = Marshal.AllocHGlobal(sizeof(int) * n);

這時就可以使用 buffer 作為陣列

下面對他的第 k 個元素修改

  IntPtr buffer = Marshal.AllocHGlobal(sizeof(int) * n);
  int k = 2;

  IntPtr t = buffer + k * sizeof(int);
  var p = Marshal.PtrToStructure<int>(t);
  Console.WriteLine("p " + p); //196713 這時的值是不確定

  p = 2;
  Marshal.StructureToPtr(p,t,false);

  p = Marshal.PtrToStructure<int>(t);
  Console.WriteLine("p " + p);//2

  //遍歷
  Console.WriteLine("遍歷");
  for (int i = 0; i < 10; i++)
  {
  t = buffer + i * sizeof(int);
  Console.WriteLine(Marshal.PtrToStructure<int>(t));
  }

遍歷:

43909312
44502144
2
0
0
24
1357220181
196712
550912
543686656

可以從上面的程式碼看到,主要使用的兩個方法是 StructureToPtr 和 PtrToStructure ,而 StructureToPtr 就是從指定型別寫到指標,希望大家還知道如何使用指標,PtrToStructure 就是從指標指向的地方開始讀資料,讀指定型別的資料。所以可以從 Marshal 把一個型別使用另一個型別來讀取,但是一般需要讀取的型別都需要是確定型別大小的,如 char 可以、string 不可以。

反過來,StructureToPtr 是對指定指標寫入指定的型別,同樣也是需要確定這個型別的大小,如可以寫入 char 但是不可以寫入 string。這就是對陣列讀寫的方法。

那麼遍歷的時候什麼輸出一些詭異的值,實際上因為沒有初始化,裡面的值是不確定的。我覺得用這個做隨機數也不錯。

使用 Marshal 是比較安全,因為 ms 做了很多處理,但是也會讓程式閃退,如下面的程式碼

 private static void Foo()
 {
  int n = 100000;//長度
  IntPtr buffer = Marshal.AllocHGlobal(sizeof(int) * n);

  try
  {
  var t = buffer + (n * 10) * sizeof(int);
  var p = Marshal.PtrToStructure<int>(t);
  }
  catch (Exception e)
  {
  Console.WriteLine(e);
  }

  Marshal.FreeHGlobal(buffer);
 }

會出現異常 System.AccessViolationException,這個異常是無法 catch 的,所以使用的時候最好封裝一下

“System.AccessViolationException”型別的未經處理的異常在 未知模組

嘗試讀取或寫入受保護的記憶體。這通常指示其他記憶體已損壞

如果需要 catch 那麼請在 app.config 新增下面的程式碼

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 <runtime>
 <legacyCorruptedStateExceptionsPolicy enabled="true" />
 </runtime>
</configuration>

然後在 Main 函式新增 HandleProcessCorruptedStateExceptions ,請看程式碼

 [HandleProcessCorruptedStateExceptions]
 static void Main(string[] args)
 {
  AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

  for (int i = 0; i < 100000; i++)
  {
  try
  {
   Foo();
  }
  catch (Exception e)
  {
   Console.WriteLine(e);
   
  }
  }
  Console.WriteLine("完成");
  Console.ReadKey();
 }

這時可以看到進入 UnhandledException ,但是無法接住,軟體還是會崩潰

C#如何快速釋放記憶體的大陣列詳解

釋放記憶體

那麼如何釋放記憶體?因為這個申請是沒有經過管理的,如果沒有手動釋放,那麼就出現記憶體洩露。

 static void Main(string[] args)
 {
  for (int i = 0; i < 10000; i++)
  {
  Foo();
  }
  Console.ReadKey();
 }

 private static void Foo()
 {
  int n = 100000;//長度
  IntPtr buffer = Marshal.AllocHGlobal(sizeof(int) * n);
  
 }

上面的程式碼很快就可以看到記憶體佔用到2G,所以需要手動釋放

  Marshal.FreeHGlobal(buffer);

原來的 byte 陣列需要使用 1G 記憶體,而且速度很慢,而現在使用這個方法只需要 7M 記憶體,速度很快

C#如何快速釋放記憶體的大陣列詳解

所以在需要進行大陣列申請的時候,需要不停釋放,就可以使用這個方法。

如果想使用封裝好的,請看下面的大神弄好的類

參見:C#+無unsafe的非託管大陣列(large unmanaged array in c# without ‘unsafe' keyword)

實際使用

實際在哪些地方使用?實際上因為很多時候都是使用例項化池,但是例項化池在進入遊戲的時候,可以讓gc不會讓程式暫停,但是會在遊戲進入下一關的時候,無法快速清理資料。所以這時就可以使用 Marshal 做例項化池,瞬間就可以清空。

上面的方法暫時不告訴大家如何做,因為涉及到公司的使用。

總結

到此這篇關於C#如何快速釋放記憶體的大陣列的文章就介紹到這了,更多相關C#快速釋放記憶體的大陣列內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!