C#靜態方法和實例方法的內存分配測試

在模塊化開發過程當中,當非Code first場景下,直接與數據庫交互進行DML操做時,模板內置DML語句哪一種代碼形式效率更高呢? 數據庫

首先咱們經過內存分析: 網絡

假設,找到一個數據表,將表模塊化出來後,並配置當前class DML方法。 模塊化

是直接將DML寫在對象內?仍是在class中設置靜態方法?或者擴展出來?哪一種更節約內存呢?因而,就有了下面的模擬測試: 測試

  1. 單純Class(不加DML方法,也不擴展)
  2. Class擴展DML方法
  3. Class中內置實例DML方法
  4. Class中內置靜態DML方法
  5. 模擬500000個對象,查詢內存大小以及內存中對象形式。

根據網絡牛人解釋以下: this

方法 (Method) 是一種類型定義,因此,它被存放在 Type Object 上,Type Object 是一個被分配在託管堆上的特殊類型,在同一個 AppDomain 中,每個類型,都對應一個全局的 Type Object。每一個引用類型的實例,都包含一個指向它的直接類型的 Type Object 的指針,每一個 Type Object 也存在相似的指針,用來標識它的直接父類型的 Type Object。 spa

當調用靜態方法時,CLR 會根據方法調用去尋找其對應的 Type Object,而後,把方法 JIT,JIT 以後的方法是本機代碼,能夠直接運行,而後,這部分代碼被加載進入內存,方法的參數被加載進入當前執行棧,原來的執行上下文地址也被記錄到執行棧;方法開始執行,執行完後,執行棧中的返回地址被讀出,而後 CLR 利用本機跳轉指令,跳轉到該返回至繼續執行。 指針

當調用實例方法時,CLR 會根據實例的 Type Object 指針找到對應的 Type Object,而後,把方法 JIT,JIT 以後的方法是本機代碼,能夠直接運行,而後,這部分代碼被加載進入內存,該實例對象,以及方法的參數被加載進入當前執行棧 (實例對象永遠是第一個參數,即 arg0,利用 ldarg0 指令進行讀取),原來的執行上下文地址也被記錄到執行棧;方法開始執行,執行完後,執行棧中的返回地址被讀出,而後 CLR 利用本機跳轉指令,跳轉到該返回至繼續執行。 code

若是方法已經被 JIT 過,則不會被第二次 JIT。 模塊化開發

方法在 IL 中是以字節流的形式存在的,因此,它仍然會佔據內存。 對象

方法 JIT 以後會被駐留在該進程的地址空間裏面,所以,它也會在運行時佔據內存。

方法的元數據存放在程序集 MethodRef 以及 MethodDef 表中。

定義在值類型上的實例方法就比較麻煩了,你們有興趣能夠想一想它怎麼執行的。由於值類型沒有 Type Object 指針。

若是值類型實現一個接口,在執行接口的方法實現的時候就更加麻煩了,你們也能夠想一想,歡迎討論!

最後,

你們都覺得" 靜態方法在堆上分配內 存,實例方法在堆棧上"

這句話徹底不靠譜,不要被迷惑了。。。只要提到方法,它就必定在 Type Object 上,也就是被分配在託管堆上。

代碼:

 

        static void size()
        {
            GC.Collect();
            GC.WaitForFullGCComplete();
            long start = GC.GetTotalMemory(true);
            List<ServerUser2> u2=new List<ServerUser2>();
            for (int i = 0; i < 500000; i++)
            {
                u2.Add(new ServerUser2($"User{i}", true, false, false));
            }
            GC.Collect();
            GC.WaitForFullGCComplete();
            long end = GC.GetTotalMemory(true);
            long size = end - start;
            Console.WriteLine($"純對象大小:{size}");
            ////////////////////////
            GC.Collect();
            GC.WaitForFullGCComplete();
            start = GC.GetTotalMemory(true);
            List<ServerUser3> u3 = new List<ServerUser3>();
            for (int i = 0; i < 500000; i++)
            {
                u3.Add(new ServerUser3($"User{i}", true, false, false));
            }
            GC.Collect();
            GC.WaitForFullGCComplete();
            end = GC.GetTotalMemory(true);
            size = end - start;
            Console.WriteLine($"擴展對象大小:{size}");
            ////////////////////////
            GC.Collect();
            GC.WaitForFullGCComplete();
            start = GC.GetTotalMemory(true);
            List<ServerUser4> u4 = new List<ServerUser4>();
            for (int i = 0; i < 500000; i++)
            {
                u4.Add(new ServerUser4($"User{i}", true, false, false));
            }
            GC.Collect();
            GC.WaitForFullGCComplete();
            end = GC.GetTotalMemory(true);
            size = end - start;
            Console.WriteLine($"普通方法對象大小:{size}");
            ////////////////////////
            GC.Collect();
            GC.WaitForFullGCComplete();
            start = GC.GetTotalMemory(true);
            List<ServerUser5> u5 = new List<ServerUser5>();
            for (int i = 0; i < 500000; i++)
            {
                u5.Add(new ServerUser5($"User{i}", true, false, false));
            }
            GC.Collect();
            GC.WaitForFullGCComplete();
            end = GC.GetTotalMemory(true);
            size = end - start;
            Console.WriteLine($"靜態方法對象大小:{size}");

            Console.WriteLine($"斷點分析對象");
        }
    public class ServerUser2
    {
        [DisplayName("User")]
        public string User { get; set; }
        [DisplayName("Admin")]
        public bool Admin { get; set; }
        [DisplayName("Export")]
        public bool Export { get; set; }
        [DisplayName("Impersonate")]
        public bool Impersonate { get; set; }

        public ServerUser2() { }

        public ServerUser2(string pUser, bool pAdmin, bool pExport, bool pImpersonate)
        {
            User = pUser;
            Admin = pAdmin;
            Export = pExport;
            Impersonate = pImpersonate;
        }


    }

    public class ServerUser3
    {
        [DisplayName("User")]
        public string User { get; set; }
        [DisplayName("Admin")]
        public bool Admin { get; set; }
        [DisplayName("Export")]
        public bool Export { get; set; }
        [DisplayName("Impersonate")]
        public bool Impersonate { get; set; }

        public ServerUser3() { }

        public ServerUser3(string pUser, bool pAdmin, bool pExport, bool pImpersonate)
        {
            User = pUser;
            Admin = pAdmin;
            Export = pExport;
            Impersonate = pImpersonate;
        }


    }


    public class ServerUser4
    {
        [DisplayName("User")]
        public string User { get; set; }
        [DisplayName("Admin")]
        public bool Admin { get; set; }
        [DisplayName("Export")]
        public bool Export { get; set; }
        [DisplayName("Impersonate")]
        public bool Impersonate { get; set; }

        public ServerUser4() { }

        public ServerUser4(string pUser, bool pAdmin, bool pExport, bool pImpersonate)
        {
            User = pUser;
            Admin = pAdmin;
            Export = pExport;
            Impersonate = pImpersonate;
        }
        public bool Insert(DbContext db)
        {
            return db.Database.ExecuteSqlCommand($"INSERT INTO [Server].[ServerUser] ( [User], [Admin], [Export], [Impersonate]) VALUES ( N'{User}', N'{Admin}', N'{Export}', N'{Impersonate}');") > 0;
        }

    }


    public class ServerUser5
    {
        [DisplayName("User")]
        public string User { get; set; }
        [DisplayName("Admin")]
        public bool Admin { get; set; }
        [DisplayName("Export")]
        public bool Export { get; set; }
        [DisplayName("Impersonate")]
        public bool Impersonate { get; set; }

        public ServerUser5() { }

        public ServerUser5(string pUser, bool pAdmin, bool pExport, bool pImpersonate)
        {
            User = pUser;
            Admin = pAdmin;
            Export = pExport;
            Impersonate = pImpersonate;
        }

        public static bool Insert(ServerUser5 item,DbContext db)
        {
            return db.Database.ExecuteSqlCommand($"INSERT INTO [Server].[ServerUser] ( [User], [Admin], [Export], [Impersonate]) VALUES ( N'{item.User}', N'{item.Admin}', N'{item.Export}', N'{item.Impersonate}');") > 0;
        }
    }

    public static class ServerUserDbo
    {
        public static bool Insert(this ServerUser3 item, DbContext db)
        {
            return db.Database.ExecuteSqlCommand($"INSERT INTO [Server].[ServerUser] ( [User], [Admin], [Export], [Impersonate]) VALUES ( N'{item.User}', N'{item.Admin}', N'{item.Export}', N'{item.Impersonate}');") > 0;
        }
    }

 

結論:

因此,咱們基本上能夠得出結論,相同對象數據的狀況下,對象方法的實現並不會佔用內存地址,咱們能夠理解爲它在被分配到堆後,實際上就是一個棧的指針地址。只要是相同類型的類(能夠是不一樣實例),調用的方法地址都是相同的,因此不須要爲實例再分配內存。

    那麼方法的實現方式上通常推薦如何作呢?

    爲了後期更好的維護,建議擴展出來,這樣,無論作任何的操做,均可以更好地複用。只是可讀性不是特別好。

    可是若是寫成實例方法,可能Class的代碼行就多了,不方便維護。

相關文章
相關標籤/搜索