同事分享了一記讓 32 位元 .NET 程式突破 2GB 記憶體上限的密技,讓我不禁獻上了膝蓋,當然要轉分享一下。

.NET 編譯成 32 位元與 64 位元最大的差異在於可用記憶體上限,32 位元的記憶體定址上限為 4GB,其中 2GB 配置給作業系統核心模式,應用程式為使用者模式只有 2GB 可用,實際執行需再扣除 Runtime 本身耗用的記憶體,依經驗只能用到 1.6GB 左右。所以若無特殊限制,程式最好編譯成 AnyCPU 或 x64 以充分享用記憶體。但實務上 .NET 程式一旦引用了 32 位元 Unmanaged 元件,就毫無選擇只能以 32 位元執行。

Windows 有個 /3GB 啟動參數,可調整只配置 1GB 給核心模式,留下 3GB 給應用程式使用,但 /3GB 的設定步驟繁瑣,要部署大量客戶端很有麻煩。Visual C++ 有個 EDITBIN 命令列工具,可修改 OBJ/DLL/EXE 檔案旗標,其中有個 /LARGEADDRESSAWARE 參數可針對特定 EXE 開放 3GB 模式,突破 1.6GB 上限。

以下是個簡單的測試程式,透過不斷產生 1M 長度字串消耗記憶體直接 OutOfMemoryException,並用以前介紹過的記憶體用量觀察函式測量佔用的 Managed Heap 記憶體:

using System;
using System.Collections.Generic;

namespace _32BitAppTest
{
    public class Program
    {
        static void Main(string[] args)
        {
            try
            {
                CreateBigData();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString()); 
            }
            Console.WriteLine("Press any key for exit... ");
            Console.ReadKey();
        }

        static void DumpMemSize(int count)
        {
            //強制回收記憶體清出空間,以充分利用所有記憶體 
            var memSz = GC.GetTotalMemory(true) / 1024 / 1024;
            Console.WriteLine(
                $"Managed Heap={memSz}MB, Count={count}");
        }

        static void CreateBigData()
        {
            var dic = new Dictionary<long, string>();
            var src = new string(' ', 1024 * 1024 - 1);
            for (int i = 0; i < 4096; i++)
            {
                dic.Add(i, src + (i % 10));
                if (i % 32 == 0)
                     DumpMemSize(i);                
            }
        }
    }
}

實測結果,大約用到 1.5GB 左右出現 OutOfMemoryException,跟一般認知的 2GB 上限相近。

用 EDITBIN /LARGEADDRESSAWARE 32BitAppTest.exe 開光後,同一支 32 位元 .NET程式便能吃到 3GB 記憶體,神奇吧!

如果要每次編譯後自動修改,可加在專案 Post-Build Event,以下是適用 VS2015/VS2017 的寫法:

使用以下腳本則可適用多個 VS 版本:參考

IF  EXIST  "%VS140COMNTOOLS%"  CALL  "%VS140COMNTOOLS%vsvars32.bat"
IF  EXIST  "%VS120COMNTOOLS%"  CALL  "%VS120COMNTOOLS%vsvars32.bat"
IF  EXIST  "%VS110COMNTOOLS%"  CALL  "%VS110COMNTOOLS%vsvars32.bat"
IF  EXIST  "%VS100COMNTOOLS%"  CALL  "%VS100COMNTOOLS%vsvars32.bat"
editbin.exe /LARGEADDRESSAWARE $(TargetPath)

補充,EDITBIN 為 Visual C++ 附屬工具,Visual Studio 記得要安裝 Visual C++ 才有的用。

【同場加映】

C/C++ Build Tools 有另一件工具 - DUMPBIN,可檢查 EXE 是否已設定 LARGEADDRESSAWARE 旗標,如下圖:

Tips of using EDITBIN /LARGEADDRESSAWARE to remove 2GB lmitation from 32bit application.


Comments

# by 陌生人

如果在64bit OS上使用32bit的軟體,是否就能變相的限制該軟體記憶體使用大小?謝謝。

# by Jeffrey

to 陌生人,32bit 軟體原本就有 2GB 可用記憶體上限,不管在 32 或 64 位元 Windows 都一樣。如果你所說的情境是某個有出 32 跟 64 位元版本的軟體,你不希望它佔用過多記憶體,那選 32 版本可以確實可以限制它無法耗用超過 2GB RAM。(註:不考慮本文所提的技巧)

Post a comment