![](/img/trans.png)
[英]Reading large file - System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown. in C#
[英]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.)
事實上,如果編譯器決定為這些整數中的任何一個使用寄存器,它甚至可能不會使用那么多。
即便如此,也假定了某種已定義的序列。 我們沒有理由不能也使用傳統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.