Câu hỏi 'System.OutOfMemoryException' bị ném khi vẫn còn nhiều bộ nhớ trống


Đây là mã của tôi:

int size = 100000000;
double sizeInMegabytes = (size * 8.0) / 1024.0 / 1024.0; //762 mb
double[] randomNumbers = new double[size];

Ngoại lệ: Ngoại lệ của loại 'System.OutOfMemoryException' đã được ném.

Tôi có bộ nhớ 4GB trên máy 2.5GB này miễn phí khi tôi bắt đầu chạy, có đủ không gian trên PC để xử lý 762mb của 100000000 số ngẫu nhiên. Tôi cần phải lưu trữ càng nhiều số ngẫu nhiên càng tốt cho bộ nhớ có sẵn. Khi tôi đi đến sản xuất sẽ có 12GB trên hộp và tôi muốn sử dụng nó.

CLR có buộc tôi vào bộ nhớ tối đa mặc định để bắt đầu không? và làm cách nào để yêu cầu thêm?

Cập nhật

Tôi nghĩ rằng việc chia nhỏ thành những phần nhỏ hơn và tăng dần các yêu cầu bộ nhớ của tôi sẽ giúp ích nếu vấn đề là do sự phân mảnh bộ nhớ, nhưng nó không Tôi không thể vượt qua tổng kích thước ArrayList là 256MB bất kể những gì tôi thực hiện.

private static IRandomGenerator rnd = new MersenneTwister();
private static IDistribution dist = new DiscreteNormalDistribution(1048576);
private static List<double> ndRandomNumbers = new List<double>();

private static void AddNDRandomNumbers(int numberOfRandomNumbers) {
    for (int i = 0; i < numberOfRandomNumbers; i++) {
      ndRandomNumbers.Add(dist.ICDF(rnd.nextUniform()));                
  }
}

Từ phương pháp chính của tôi:

int blockSize = 1000000;

while (true) {
  try
  {
    AddNDRandomNumbers(blockSize);                    
  }
  catch (System.OutOfMemoryException ex)
  {
    break;
  }
}            
double arrayTotalSizeInMegabytes = (ndRandomNumbers.Count * 8.0) / 1024.0 / 1024.0;

76
2017-07-20 13:50


gốc


Tôi muốn giới thiệu rearchitechting ứng dụng của bạn để bạn không phải sử dụng quá nhiều bộ nhớ. Bạn đang làm gì mà bạn cần một trăm triệu con số tất cả trong bộ nhớ cùng một lúc? - Eric Lippert
bạn đã không vô hiệu hóa pagefile của bạn hoặc một cái gì đó ngớ ngẩn như thế, phải không? - jalf
@EricLippert, tôi đang chạy vào vấn đề này khi làm việc về vấn đề P so với NP (claymath.org/millenium-problems/p-vs-np-problem). Bạn có gợi ý nào để giảm mức sử dụng bộ nhớ hoạt động không? (ví dụ: Nối tiếp và lưu trữ các khối dữ liệu trên đĩa cứng, sử dụng kiểu dữ liệu C ++, v.v.) - devinbost
@bosit đây là trang web câu hỏi và câu trả lời. Nếu bạn có câu hỏi kỹ thuật cụ thể về mã thực tế, hãy đăng câu hỏi đó dưới dạng câu hỏi. - Eric Lippert
@bostIT liên kết cho vấn đề P so với NP trong nhận xét của bạn không hợp lệ nữa. - RBT


Các câu trả lời:


Bạn có thể muốn đọc điều này: "“Hết bộ nhớ” không đề cập đến bộ nhớ vật lý"của Eric Lippert.

Tóm lại, và rất đơn giản, "Hết bộ nhớ" không thực sự có nghĩa là lượng bộ nhớ có sẵn quá nhỏ. Lý do phổ biến nhất là trong không gian địa chỉ hiện tại, không có bộ nhớ tiếp giáp đủ lớn để phục vụ phân bổ mong muốn. Nếu bạn có 100 khối, mỗi khối lớn 4 MB, sẽ không giúp bạn khi bạn cần một khối 5 MB.

Những điểm chính: 

  • lưu trữ dữ liệu mà chúng tôi gọi là "bộ nhớ xử lý" theo ý kiến ​​của tôi được hiển thị tốt nhất dưới dạng tập tin lớn trên đĩa.
  • RAM có thể được xem như là một tối ưu hóa hiệu suất
  • Tổng dung lượng bộ nhớ ảo mà chương trình của bạn tiêu thụ thực sự không liên quan đến hiệu suất của nó
  • "hết RAM" hiếm khi dẫn đến lỗi "hết bộ nhớ". Thay vì một lỗi, nó dẫn đến hiệu suất kém bởi vì toàn bộ chi phí thực tế là bộ nhớ thực sự trên đĩa đột nhiên trở nên có liên quan.

116
2017-07-20 13:58





Bạn không có một khối bộ nhớ liên tục để phân bổ 762MB, bộ nhớ của bạn bị phân mảnh và bộ cấp phát không thể tìm thấy một lỗ đủ lớn để cấp phát bộ nhớ cần thiết.

  1. Bạn có thể thử làm việc với / 3GB (như những người khác đã đề xuất)
  2. Hoặc chuyển sang hệ điều hành 64 bit.
  3. Hoặc sửa đổi thuật toán để nó không cần một bộ nhớ lớn. có thể phân bổ một vài phần nhỏ hơn (tương đối) của bộ nhớ.

23
2017-07-20 13:59





Kiểm tra xem bạn đang xây dựng quy trình 64 bit chứ không phải là một quá trình 32 bit, là chế độ biên dịch mặc định của Visual Studio. Để thực hiện điều này, nhấn chuột phải vào dự án của bạn, Properties -> Build -> platform target: x64. Như bất kỳ quy trình 32-bit nào, các ứng dụng Visual Studio được biên dịch trong 32 bit đều có giới hạn bộ nhớ ảo là 2GB.

Các quy trình 64 bit không có giới hạn này, vì chúng sử dụng các con trỏ 64 bit, do đó không gian địa chỉ tối đa lý thuyết của chúng (kích thước bộ nhớ ảo của chúng) là 16 exabyte (2 ^ 64). Trong thực tế, Windows x64 giới hạn bộ nhớ ảo của các quá trình đến 8TB. Các giải pháp cho vấn đề giới hạn bộ nhớ là sau đó để biên dịch trong 64-bit.

Tuy nhiên, kích thước của đối tượng trong Visual Studio vẫn bị giới hạn ở mức 2GB, theo mặc định. Bạn sẽ có thể tạo một số mảng có kích thước kết hợp sẽ lớn hơn 2GB, nhưng bạn không thể mặc định tạo mảng lớn hơn 2GB. Hy vọng rằng, nếu bạn vẫn muốn tạo mảng lớn hơn 2GB, bạn có thể làm điều đó bằng cách thêm đoạn mã sau vào tệp app.config:

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

18
2018-06-26 13:56





Như bạn có thể đã tìm ra, vấn đề là bạn đang cố gắng phân bổ một khối bộ nhớ liền kề lớn, không hoạt động do phân mảnh bộ nhớ. Nếu tôi cần làm những gì bạn đang làm, tôi sẽ làm như sau:

int sizeA = 10000,
    sizeB = 10000;
double sizeInMegabytes = (sizeA * sizeB * 8.0) / 1024.0 / 1024.0; //762 mb
double[][] randomNumbers = new double[sizeA][];
for (int i = 0; i < randomNumbers.Length; i++)
{
    randomNumbers[i] = new double[sizeB];
}

Sau đó, để có được một chỉ mục cụ thể bạn sẽ sử dụng randomNumbers[i / sizeB][i % sizeB].

Một tùy chọn khác nếu bạn luôn truy cập các giá trị theo thứ tự có thể là sử dụng nhà xây dựng quá tải để xác định hạt giống. Bằng cách này bạn sẽ nhận được một số bán ngẫu nhiên (như DateTime.Now.Ticks) lưu trữ nó trong một biến, sau đó khi bạn bắt đầu đi qua danh sách, bạn sẽ tạo một cá thể ngẫu nhiên mới bằng cách sử dụng hạt giống gốc:

private static int randSeed = (int)DateTime.Now.Ticks;  //Must stay the same unless you want to get different random numbers.
private static Random GetNewRandomIterator()
{
    return new Random(randSeed);
}

Điều quan trọng cần lưu ý là khi blog được liên kết trong câu trả lời của Fredrik Mörk cho thấy vấn đề thường là do thiếu Không gian địa chỉ nó không liệt kê một số vấn đề khác, như giới hạn kích thước đối tượng CLR 2GB (được đề cập trong một bình luận từ ShuggyCoUk trên cùng một blog), làm nổi bật phân mảnh bộ nhớ và không đề cập đến tác động của kích thước tệp trang (và cách nó có thể được giải quyết với việc sử dụng CreateFileMapping chức năng).

Giới hạn 2 GB có nghĩa là randomNumbers  phải nhỏ hơn 2GB. Kể từ khi mảng là các lớp và có một số chi phí trên bản thân, điều này có nghĩa là một mảng double sẽ cần phải nhỏ hơn 2 ^ 31. Tôi không chắc chắn nhỏ hơn bao nhiêu thì 2 ^ 31 chiều dài sẽ là, nhưng Phần trên của một mảng .NET? cho biết 12 - 16 byte.

Phân mảnh bộ nhớ rất giống với phân mảnh HDD. Bạn có thể có 2GB không gian địa chỉ, nhưng khi bạn tạo và phá hủy các đối tượng sẽ có khoảng trống giữa các giá trị. Nếu những khoảng trống này quá nhỏ đối với đối tượng lớn của bạn và không thể yêu cầu thêm không gian, thì bạn sẽ nhận được System.OutOfMemoryException. Ví dụ: nếu bạn tạo 2 triệu, đối tượng 1024 byte thì bạn đang sử dụng 1,9 GB. Nếu bạn xóa tất cả các đối tượng mà địa chỉ không phải là bội số của 3 thì bạn sẽ sử dụng bộ nhớ .6GB, nhưng nó sẽ được trải ra trên không gian địa chỉ với 2024 byte khối mở ở giữa. Nếu bạn cần tạo một đối tượng bằng .2GB, bạn sẽ không thể thực hiện được vì không có khối nào đủ rộng để vừa với nó và không thể có thêm khoảng trống (giả sử môi trường 32 bit). Các giải pháp có thể cho vấn đề này là những thứ như sử dụng các đối tượng nhỏ hơn, giảm lượng dữ liệu bạn lưu trữ trong bộ nhớ hoặc sử dụng thuật toán quản lý bộ nhớ để hạn chế / ngăn chặn phân mảnh bộ nhớ. Cần lưu ý rằng trừ khi bạn đang phát triển một chương trình lớn sử dụng một lượng lớn bộ nhớ, đây sẽ không phải là vấn đề. Ngoài ra, vấn đề này có thể phát sinh trên hệ thống 64 bit vì cửa sổ bị hạn chế chủ yếu bởi kích thước tệp trang và lượng RAM trên hệ thống.

Vì hầu hết các chương trình yêu cầu bộ nhớ làm việc từ hệ điều hành và không yêu cầu ánh xạ tệp, chúng sẽ bị giới hạn bởi RAM của hệ thống và kích thước tệp trang. Như đã lưu ý trong nhận xét của Néstor Sánchez (Néstor Sánchez) trên blog, với mã được quản lý như C #, bạn bị kẹt với giới hạn tệp RAM / trang và không gian địa chỉ của hệ điều hành.


Đó là cách lâu hơn sau đó dự kiến. Hy vọng nó giúp ai đó. Tôi đăng nó vì tôi chạy vào System.OutOfMemoryException chạy chương trình x64 trên một hệ thống có RAM 24GB mặc dù mảng của tôi chỉ giữ 2GB nội dung.


7
2017-12-24 23:15





Tôi khuyên bạn nên chống lại tùy chọn khởi động / 3GB windows. Ngoài mọi thứ khác (nó quá mức cần thiết để làm điều này cho một ứng dụng hoạt động kém và có thể nó sẽ không giải quyết được sự cố của bạn), nó có thể gây ra nhiều bất ổn.

Nhiều trình điều khiển Windows không được thử nghiệm với tùy chọn này, do đó, một vài người trong số họ giả định rằng con trỏ chế độ người dùng luôn trỏ đến 2GB thấp hơn của không gian địa chỉ. Có nghĩa là họ có thể phá vỡ khủng khiếp với / 3GB.

Tuy nhiên, Windows thường giới hạn một quy trình 32-bit đến một không gian địa chỉ 2GB. Nhưng điều đó không có nghĩa là bạn nên mong đợi để có thể phân bổ 2GB!

Không gian địa chỉ đã được rải rác với tất cả các loại dữ liệu được phân bổ. Có ngăn xếp, và tất cả các hội đồng được nạp, các biến tĩnh và vân vân. Không có gì đảm bảo rằng sẽ có 800MB bộ nhớ không phân bổ liền kề ở bất cứ đâu.

Phân bổ 2 khối 400MB có lẽ sẽ tốt hơn. Hoặc 4 khối 200MB. Phân bổ nhỏ hơn dễ tìm hơn nhiều trong không gian bộ nhớ phân mảnh.

Dù sao, nếu bạn sẽ triển khai điều này đến một máy 12GB anyway, bạn sẽ muốn chạy này như là một ứng dụng 64-bit, mà nên giải quyết tất cả các vấn đề.


5
2017-07-20 14:05



Việc chia công việc thành các đoạn nhỏ hơn dường như không giúp tôi nhìn thấy cập nhật của tôi ở trên. - m3ntat


Thay đổi từ 32 đến 64 bit làm việc cho tôi - đáng thử nếu bạn đang ở trên một máy tính 64 bit và nó không cần phải cổng.


3
2017-08-31 21:04





Nếu bạn cần những cấu trúc lớn như vậy, có lẽ bạn có thể sử dụng các tệp Bộ nhớ Ánh xạ. Bài viết này có thể hữu ích: http://www.codeproject.com/KB/recipes/MemoryMappedGenericArray.aspx

LP, Dejan


2
2017-07-20 14:09





Các cửa sổ 32 bit có giới hạn bộ nhớ 2GB. Tùy chọn khởi động / 3GB mà những người khác đã đề cập sẽ làm cho 3GB này chỉ còn lại 1gb để sử dụng hạt nhân OS. Thực tế nếu bạn muốn sử dụng nhiều hơn 2GB mà không gặp rắc rối thì hệ điều hành 64bit là bắt buộc. Điều này cũng khắc phục được vấn đề theo đó mặc dù bạn có thể có 4GB RAM vật lý, không gian địa chỉ được gắn lại cho card màn hình có thể tạo ra một mâm cặp đáng nhớ của bộ nhớ đó không sử dụng được - thường khoảng 500MB.


1
2017-07-20 14:03





Thay vì phân bổ một mảng lớn, bạn có thể thử sử dụng một trình lặp không? Chúng được thực hiện chậm trễ, có nghĩa là các giá trị được tạo ra chỉ khi chúng được yêu cầu trong một tuyên bố foreach; bạn không nên hết bộ nhớ theo cách này:

private static IEnumerable<double> MakeRandomNumbers(int numberOfRandomNumbers) 
{
    for (int i = 0; i < numberOfRandomNumbers; i++)
    {
        yield return randomGenerator.GetAnotherRandomNumber();
    }
}


...

// Hooray, we won't run out of memory!
foreach(var number in MakeRandomNumbers(int.MaxValue))
{
    Console.WriteLine(number);
}

Ở trên sẽ tạo ra nhiều số ngẫu nhiên như bạn muốn, nhưng chỉ tạo ra chúng khi chúng được yêu cầu thông qua một tuyên bố foreach. Bạn sẽ không hết bộ nhớ theo cách đó.

Cách khác, Nếu bạn phải có tất cả chúng ở một nơi, lưu trữ chúng trong một tập tin chứ không phải trong bộ nhớ.


1
2017-07-20 17:05



Tiếp cận lặp lại nhưng tôi cần lưu trữ càng nhiều càng tốt dưới dạng kho lưu trữ số ngẫu nhiên trong bất kỳ thời gian rảnh nào trong phần còn lại của ứng dụng khi ứng dụng này chạy trên đồng hồ 24 giờ hỗ trợ nhiều khu vực địa lý (nhiều mô phỏng Monte Carlo), khoảng 70% của tải cpu tối đa trong ngày, thời gian còn lại trong ngày tôi muốn đệm các số ngẫu nhiên trong tất cả không gian bộ nhớ trống. Lưu trữ vào đĩa quá chậm và loại thất bại bất kỳ lợi ích nào tôi có thể làm đệm vào bộ nhớ cache số ngẫu nhiên này. - m3ntat