C# 快速釋放內存的大數組

本文告訴你們如何使用 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 ),不得用於商業目的,基於本文修改後的做品務必以相同的許可發佈。若有任何疑問,請與我聯繫

相關文章
相關標籤/搜索