本文告訴你們如何使用 Marshal 作出能夠快速釋放內存的大數組。html
最近在作 3D ,須要不斷申請一段大內存數組,而後就釋放他,可是 C# 對於大內存不是馬上釋放,因此就存在必定的性能問題。
在博客園看到了一位大神使用 Marshal 作出快速申請的大數組,因而我就學他的方法來弄一個。本文告訴你們這個類是如何使用git
在使用的時候,先來看下原來的 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 的相同app
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)); }
遍歷:.net
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 ,可是沒法接住,軟件仍是會崩潰
那麼如何釋放內存?由於這個申請是沒有通過管理的,若是沒有手動釋放,那麼就出現內存泄露。
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#+無unsafe的非託管大數組(large unmanaged array in c# without 'unsafe' keyword) - BIT祝威 - 博客園
實際在哪些地方使用?實際上由於不少時候都是使用實例化池,可是實例化池在進入遊戲的時候,可讓gc不會讓程序暫停,可是會在遊戲進入下一關的時候,沒法快速清理數據。因此這時就可使用 Marshal 作實例化池,瞬間就能夠清空。
上面的方法暫時不告訴你們如何作,由於涉及到公司的使用。
我搭建了本身的博客 https://lindexi.gitee.io/ 歡迎你們訪問,裏面有不少新的博客。只有在我看到博客寫成熟以後纔會放在csdn或博客園,可是一旦發佈了就再也不更新
若是在博客看到有任何不懂的,歡迎交流,我搭建了 dotnet 職業技術學院 歡迎你們加入
本做品採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。歡迎轉載、使用、從新發布,但務必保留文章署名林德熙(包含連接:http://blog.csdn.net/lindexi_gd ),不得用於商業目的,基於本文修改後的做品務必以相同的許可發佈。若有任何疑問,請與我聯繫。