簡體   English   中英

c# System.OutOfMemoryException: 'System.OutOfMemoryException' 類型的異常被拋出。

[英]c# System.OutOfMemoryException: 'Exception of type 'System.OutOfMemoryException' was thrown.'

我得到這個 System.OutOfMemoryException。 是:我已添加

<runtime>
    <gcAllowVeryLargeObjects enabled = "true" />
</runtime>

到位於項目文件夾中的App.Config文件。 我有一個 64 位 Windows 10,我的 32GB 內存中大約有 20GB 可用內存。 Visual-Studio 2022。圖片

我還嘗試在 Visual Studio 中的任何地方設置 x64。

生成我的異常的代碼是:

List<int> dataList = new List<int>();
for (int j = 0; j < int.MaxValue - 10; j++)
{
    dataList.Add(j);
    if ((j %100000) == 0 )
    { Console.WriteLine(j.ToString("N0")); }
}
Console.WriteLine($"Total items in the List: {dataList.Count.ToString("N0")}. ");

控制台上的最后一個打印輸出是:2,146,400,000。 我從來沒有寫過dataList.Count的部分。 可以做些什么來增加分配給 C# 和/或 Visual Studio 的 memory?

控制台上的最后一個打印輸出是:2,146,400,000。

List<T>類型使用加倍算法來管理容量。 每次達到容量時,它都會分配一個大小為舊數組兩倍的新內部數組,然后手動將舊數組中的元素復制到新數組中。

所以我們學到的第一件事是,當你知道最終大小時(就像你在這里所做的那樣),讓列表自己增長是非常低效的。 相反,請在考慮所需容量的情況下構建列表:

var listSize = int.MaxValue - 10;
var dataList = new List<int>(listSize);
for (int j = 0; j < listSize; j++)

這不僅可以節省額外的分配,還可以節省在緩沖區之間復制元素的所有工作。 它會預先告訴你(通過例外)如果你會用完 RAM,而不是讓計算機在最終失敗之前做一堆工作。

我們應該注意的第二件事是每次列表翻倍時,它都需要足夠的 memory 用於arrays 要將下一個元素添加到一個完整容量略多於 2,146,400,000 的列表中,我們需要短暫地容納略多於 6,439,200,000 個元素的空間(當前數組加上新數組,它是大小的兩倍)。 由於 .Net 整數每個都是 32 位(4 字節),這大約是 24GB 的 RAM。

接下來要了解的是可用 memory 和可用地址空間之間的區別。 自從我上次深入研究以來,我聽說垃圾收集器在這方面做得更好,但這里仍然存在問題。 您的進程可能能夠使用系統上所有可用的 memory(然后是一些,感謝分頁),但它仍然必須解決它。 地址空間不足也會引發 OutOfMemoryException。

每次列表翻倍時,當垃圾收集器從舊的內部數組中釋放 RAM 時,RAM 執行 go 回到操作系統,但該進程的地址空間中仍然存在一個漏洞。 垃圾收集器能夠通過一個稱為“壓縮”的過程來清理這些漏洞,但它在這一步並不總是很好,即使它工作了,這也是另一個低效率的水平。 同樣,提前設置容量(如果你知道的話)真的很有幫助。


但是,如果我們可以做得更好。 讓我們看一個替代方案。 我建議Enumerable.Range()

var items = Enumerable.Range(0, int.MaxValue - 10);
foreach(var j in items)
{
    if (j % 100000 == 0)
    {
        Console.WriteLine(j.ToString("N0")); 
    }
    // do something else with every j
}

The above code will have effectively the same output, and uses less than 50 bytes of RAM ( 4 bytes for j , 4 bytes for the 100000 constant, 4 bytes each for MaxValue and 10 , a 20 byte object reference to an internal items state machine , a 4 byte integer for the internal state machine's current state, and a 4 byte integer for the terminal state -- I may be missing something else, but it's small.)

這節省了超過 515,000,000 倍!

事實上,如果編譯器決定為這些整數中的任何一個使用寄存器,它甚至可能不會使用那么多。

即便如此,也假定了某種已定義的序列。 我們沒有理由不能也使用傳統for循環,完全沒有列表或范圍,這樣可以節省更多。


更新:

我可能還缺少其他東西,但它很小。

Yes, I'm missing the size of the "N0" format string (~32 bytes -- 20 byte object reference, 4 byte integer for the length, and two 4 byte unicode characters) and the size of each of the output strings,它也必須存在於 memory 中......但一次仍然只有一個。 我估計這些字符串中最大的一個為 76 個字節,加上N0的 32 個字節,這意味着我們總共使用了大約 152 個字節。 所以節省的只是1.69億的一個因素。 僅有的。

但實際上,垃圾收集器不會在每次循環迭代后清理這些字符串對象,因此實際測量的使用量會有所增加。

鑒於List Capacity 屬性接受並返回一個 int,這可能是 List 被限制在大約 2,147,483,647 個元素的線索(盡管我在文檔中沒有找到任何東西來驗證這一點)。

因此,一種解決方案可能是擁有一個列表列表,例如:

     const int k_list_size_max = 2;
     const int k_data_items_max = 2000000000;

     UInt64 total_items;

     List<int> data_items_list;

     List<List<int>> data_list = new List<List<int>>( k_list_size_max );

     total_items = 0;

     for ( int i = 0; i < k_list_size_max; i++ )
     {

        data_items_list = new List<int>( k_data_items_max );

        for ( int j = 0; j < k_data_items_max; j++ )
        {

           data_items_list.Add( j );

        }

        Debug.WriteLine( $"List[{i}] data_items_list.Count = {data_items_list.Count}" );

        total_items += ( UInt64 )data_items_list.Count;

     }

     Debug.WriteLine( $"Total items: {total_items}. " );

  

Output:

List[0] data_items_list.Count = 2000000000
List[1] data_items_list.Count = 2000000000
Total items: 4000000000. 

可以做些什么來增加分配給 C# 和/或 visual-Studio 的 memory?

由於您的目標似乎是分配 memory,因此如果您使用更大的結構,您可以做得更好。 只需定義一個帶有一些雙精度或 guid 的結構,然后列出這些結構。

那么您的 20 億個元素將輕松使用超過 32GiB。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM