C# 使用繫結控制程式碼來減少程式的記憶體耗用

衣舞晨風發表於2016-12-25

許多應用程式中,繫結了一組型別(Type)或者型別成員(從MemberInfo派生),並將這些物件儲存在某種形式的一個集合中。以後,會搜尋這個集合,查詢特定的物件,然後呼叫這個物件。這是一個很好的機制,但是有個小問題:Type和MemberInfo派生的物件需要大量的記憶體。如果一個應用程式容納了太多這樣的類,但只是偶爾用一下它們,應用程式的記憶體就會急劇增長,對應用程式的效能產生影響。

在內部,CLR用一種更精簡的形式來表示這種資訊。CLR之所以為應用程式建立這些物件,只是為了簡化開發人員的工作。CLR在執行時並不需要這些大物件。如果需要快取大量Type和MemberInfo派生物件,開發人員可以使用執行時控制程式碼(runtime handle)來代替物件,從而減少工作集(佔用的記憶體)。FCL定義了3個執行時控制程式碼型別(都在System名稱空間中),RuntimeTypeHandle,RuntimeFieldHandle,RumtimeMethodHandle。三個型別都是值型別,他們只包含了一個欄位,也就是一個IntPtr;這樣一來,這些型別的例項就相當省記憶體。ItPtr欄位是一個控制程式碼,它引用了AppDomain的Loader堆中的一個型別,欄位或方法。轉換方法:

  • Type→RuntimeTypeHandle,通過查詢Type的只讀欄位屬性TypeHandle。
  • RuntimeTypeHandle→Type,通過呼叫Type的靜態方法GetTypeFromHanlde。
  • FieldInfo→RuntimeFieldHandle,通過查詢FieldInfo的例項只讀欄位FieldHandle。
  • RuntimeFieldHandle→FieldInfo,通過呼叫FieldInfo的靜態方法GetFieldFromHandle。
  • MethodInfo→RuntimeMethodHandle,通過查詢MethodInof的例項只讀欄位MethodHandle。
  • RuntimeMethodHandle→MethodInfo,通過呼叫MethodInfo的靜態方法GetMethodFromHandle。

下面的示例獲取許多的MethodInfo物件,把它們轉化成RuntimeMethodHandle例項,並演示轉換前後的記憶體差異。

 private void UseRuntimeHandleToReduceMemory()
        {
            Show("Before doing anything");
//從MSCorlib.dll中地所有方法構建methodInfos 物件快取
            List<MethodBase> methodInfos = new List<MethodBase>();
            foreach (Type t in typeof(object).Assembly.GetExportedTypes())
            {
                if (t.IsGenericType) continue;
                MethodBase[] mbs = t.GetMethods(c_bf);
                methodInfos.AddRange(mbs);
            }
            //顯示當繫結所有方法之後,方法的個數和堆的大小
            Console.WriteLine("# of Methods={0:###,###}", methodInfos.Count);
            Show("After building cache of MethodInfo objects");
//為所有MethodInfo物件構建RuntimeMethodHandle快取
            List<RuntimeMethodHandle> methodHandles = new List<RuntimeMethodHandle>();
            methodHandles = methodInfos.ConvertAll<RuntimeMethodHandle>(m => m.MethodHandle);
            Show("Holding MethodInfo and RuntimeMethodHandle");
            GC.KeepAlive(methodHandles);//阻止快取被過早垃圾回收

            methodInfos = null;//現在允許快取垃圾回收
            Show("After freeing MethodInfo objects");

            methodInfos = methodHandles.ConvertAll<MethodBase>(r => MethodBase.GetMethodFromHandle(r));
            Show("Size of heap after re-creating methodinfo objects");
            GC.KeepAlive(methodHandles);//阻止快取被過早垃圾回收
            GC.KeepAlive(methodInfos);//阻止快取被過早垃圾回收

            methodInfos = null;//現在允許快取垃圾回收
            methodHandles = null;//現在允許快取垃圾回收
            Show("after freeing MethodInfo and MethodHandle objects");
        }

結果如下:

Heap Size =     114,788 - Before doing anything
# of Methods=10,003
Heap Size =   2,205,652 - After building cache of MethodInfo objects
Heap Size =   2,245,744 - Holding MethodInfo and RuntimeMethodHandle
Heap Size =   2,171,976 - After freeing MethodInfo objects
Heap Size =   2,327,516 - Size of heap after re-creating methodinfo objects
Heap Size =     247,028 - after freeing MethodInfo and MethodHandle objects

本文整理自《NET CLR via C#》

作者:jiankunking 出處:http://blog.csdn.net/jiankunking

相關文章