'System.OutOfMemoryException' đã được ném khi vẫn còn nhiều bộ nhớ trống


92

Đâ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 này. 2,5 GB trống khi tôi bắt đầu chạy, rõ ràng là có đủ không gian trên PC để xử lý 762mb trong số 100000000 số ngẫu nhiên. Tôi cần lưu trữ càng nhiều số ngẫu nhiên càng tốt với bộ nhớ khả dụng. Khi tôi đi vào sản xuất sẽ có 12GB trên hộp và tôi muốn tận dụng nó.

CLR có hạn chế tôi ở 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ỏ điều này thành các phần nhỏ hơn và tăng dần yêu cầu bộ nhớ của mình sẽ hữu ích nếu vấn đề là do phân mảnh bộ nhớ , nhưng không, tôi không thể vượt qua tổng kích thước ArrayList là 256mb bất kể tôi có điều chỉnh blockSize gì đi chăng nữa.

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;

6
Tôi khuyên bạn nên cấu trúc lại ứng dụng của mình để 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 cả trăm triệu con số trong bộ nhớ cùng một lúc?
Eric Lippert

2
bạn đã không vô hiệu hóa tệp trang của bạn hoặc một cái gì đó ngớ ngẩn như vậy, phải không?
jalf

@EricLippert, tôi đang gặp phải vấn đề này khi giải quyết vấn đề P vs. NP ( claymath.org/millenium-problems/p-vs-np-problem ). Bạn có đề xuất nào để giảm mức sử dụng bộ nhớ làm việc không? (ví dụ serializing và khối lưu trữ dữ liệu trên đĩa cứng, sử dụng C ++ kiểu dữ liệu, vv)
devinbost

@bosit đây là một trang web hỏi đáp. Nếu bạn có câu hỏi kỹ thuật cụ thể về mã thực tế, hãy đăng nó 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 còn hợp lệ nữa.
RBT

Câu trả lời:


140

Bạn có thể muốn đọc phần 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ớ khả dụng quá nhỏ. Lý do phổ biến nhất là trong không gian địa chỉ hiện tại, không có phần bộ nhớ liền kề nào đủ lớn để phục vụ cấp phát mong muốn. Nếu bạn có 100 khối, mỗi khối 4 MB, điều đó sẽ không giúp ích được gì cho bạn khi bạn cần một khối 5 MB.

Những điểm chính:

  • theo quan điểm của tôi, bộ lưu trữ dữ liệu mà chúng tôi gọi là “bộ nhớ xử lý” được hình dung tốt nhất dưới dạng một tệp lớn trên đĩa .
  • RAM có thể được xem như một sự tối ưu hóa hiệu suất đơn thuần
  • Tổng dung lượng bộ nhớ ảo mà chương trình của bạn sử dụng thực sự không liên quan nhiều đế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ì lỗi, nó dẫn đến hiệu suất kém vì toàn bộ chi phí của việc lưu trữ thực sự trên đĩa đột nhiên trở nên có liên quan.

"Nếu bạn có 100 khối, mỗi khối 4 MB, điều đó sẽ không giúp ích được gì cho bạn khi bạn cần một khối 5 MB" - Tôi nghĩ sẽ tốt hơn nếu bạn có một sửa đổi nhỏ: "Nếu bạn có 100 khối " lỗ " .
OfirD

31

Kiểm tra để đảm bảo rằng bạn đang xây dựng quy trình 64 bit chứ không phải quy trình 32 bit, đây là chế độ biên dịch mặc định của Visual Studio. Để thực hiện việc này, hãy nhấp chuột phải vào dự án của bạn, Thuộc tính -> Xây dựng -> mục tiêu nền tảng: 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 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 con trỏ 64-bit, vì vậy không gian địa chỉ tối đa lý thuyết (kích thước bộ nhớ ảo) của chúng là 16 exabyte (2 ^ 64). Trên thực tế, Windows x64 giới hạn bộ nhớ ảo của các tiến trình là 8TB. Giải pháp cho vấn đề giới hạn bộ nhớ sau đó là biên dịch ở định dạng 64-bit.

Tuy nhiên, kích thước của đối tượng trong Visual Studio vẫn bị giới hạn ở 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 theo mặc định, bạn không thể 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ể thực hiện bằng cách thêm mã sau vào tệp app.config của mình:

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

Bạn đã cứu tôi. Cảm ơn bạn!
Tee Zad Awk

25

Bạn không có khối bộ nhớ liên tục để cấp phát 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 lỗ hổng đủ 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ĐH 64 bit.
  3. Hoặc sửa đổi thuật toán để nó sẽ 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) bộ nhớ.

7

Như bạn có thể đã tìm ra, vấn đề là bạn đang cố gắng cấp phát một khối bộ nhớ lớn liền kề, khối này 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ể mà 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 hàm tạo đã được nạp chồng để chỉ đị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 xem qua danh sách, bạn sẽ tạo một phiên bản Ngẫu nhiên mới bằng cách sử dụng hạt giống ban đầu:

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à mặc dù blog được liên kết trong câu trả lời của Fredrik Mörk chỉ ra rằng 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 2GB CLR (được đề cập trong nhận xét từ ShuggyCoUk trên cùng một blog), đề cập đến sự 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 giải quyết vấn đề này bằng cách sử dụng CreateFileMappinghàm ).

Giới hạn 2GB có nghĩa là randomNumbers phải nhỏ hơn 2GB. Vì mảng là các lớp và có một số chi phí là bản thân của chúng, điều này có nghĩa là một mảng doublesẽ cần phải nhỏ hơn 2 ^ 31. Tôi không chắc chiều dài sẽ phải nhỏ hơn bao nhiêu thì 2 ^ 31, nhưng Chi phí 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 ổ cứng. Bạn có thể có 2GB không gian địa chỉ, nhưng khi bạn tạo và hủy các đối tượng, sẽ có khoảng cách giữa các giá trị. Nếu những khoảng trống này quá nhỏ so 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 đượcSystem.OutOfMemoryException. Ví dụ: nếu bạn tạo đối tượng 2 triệu, 1024 byte, thì bạn đang sử dụng 1,9GB. Nếu bạn xóa mọi đố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 rộng trên không gian địa chỉ với các khối mở 2024 byte ở giữa. Nếu bạn cần tạo một đối tượng có dung lượng .2GB, bạn sẽ không thể thực hiện được vì không có khối đủ lớn để chứa nó và không thể lấy thêm không gian (giả sử là môi trường 32 bit). Các giải pháp khả thi 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 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ớ thì điều này sẽ không thành vấn đề. Cũng thế,

Vì hầu hết các chương trình yêu cầu bộ nhớ hoạt động từ HĐH 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ị mắc 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 dự kiến. Hy vọng rằng nó sẽ giúp một ai đó. Tôi đã đăng nó vì tôi đã System.OutOfMemoryExceptionchạy chương trình x64 trên hệ thống có RAM 24GB mặc dù mảng của tôi chỉ chứa 2GB nội dung.


5

Tôi khuyên bạn không nên tùy chọn khởi động windows / 3GB. Ngoài mọi thứ khác (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 vấn đề 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, vì vậy khá nhiều người trong số họ cho rằng con trỏ chế độ người dùng luôn trỏ đến vùng địa chỉ 2GB thấp hơn. Có nghĩa là chúng có thể bị hỏng khủng khiếp với / 3GB.

Tuy nhiên, Windows thường giới hạn quy trình 32-bit trong 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 cấp phát. Có ngăn xếp và tất cả các tập hợp được tải, các biến tĩnh, v.v. Không có gì đảm bảo rằng sẽ có 800MB bộ nhớ không được phân bổ liền kề ở bất kỳ đâu.

Phân bổ 2 phần 400MB có lẽ sẽ tốt hơn. Hoặc 4 khối 200MB. Việc phân bổ nhỏ hơn sẽ dễ dàng tìm thấy chỗ trống hơn trong một không gian bộ nhớ bị phân mảnh.

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


Chia công việc thành nhiều phần nhỏ hơn dường như không giúp bạn xem được cập nhật của tôi ở trên.
m3ntat

4

Thay đổi từ 32 thành 64 bit phù hợp với tôi - đáng thử nếu bạn đang sử dụng máy tính 64 bit và nó không cần phải chuyển.



1

Cửa sổ 32 bit có giới hạn bộ nhớ xử lý 2GB. Tùy chọn khởi động / 3GB mà những người khác đã đề cập sẽ tạo ra 3GB này với chỉ 1gb còn lại để sử dụng nhân hệ điều hành. 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ì cần phải có hệ điều hành 64bit. Đ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 yêu cầu cho thẻ video có thể khiến một phần lớn của bộ nhớ đó không thể sử dụng được - thường là khoảng 500MB.


1

Thay vì phân bổ một mảng lớn, bạn có thể thử sử dụng một trình vòng lặp không? Chúng được thực thi chậm, nghĩa là các giá trị chỉ được tạo ra khi chúng được yêu cầu trong một câu lệnh 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 bao nhiêu số ngẫu nhiên mà bạn muốn, nhưng chỉ tạo chúng khi chúng được yêu cầu thông qua một câu lệnh foreach. Bạn sẽ không hết bộ nhớ theo cách đó.

Ngoài ra, Nếu bạn phải có tất cả chúng ở một nơi, hãy lưu trữ chúng trong một tệp thay vì trong bộ nhớ.


Cách 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 kho số ngẫu nhiên trong bất kỳ thời gian nhàn rỗi nào của phần còn lại của ứng dụng của tôi vì ứng dụng này chạy trên đồng hồ 24 giờ hỗ trợ nhiều vùng địa lý (nhiều lần chạy mô phỏng monte carlo), khoảng 70% 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 bỏ bất kỳ lợi ích nào mà tôi có thể tạo bộ đệm vào bộ nhớ đệm số ngẫu nhiên này.
m3ntat

0

Chà, tôi gặp sự cố tương tự với tập dữ liệu lớn và việc cố ép ứng dụng sử dụng quá nhiều dữ liệu không thực sự là lựa chọn phù hợp. Mẹo tốt nhất mà tôi có thể cho bạn là xử lý dữ liệu của bạn thành từng đoạn nhỏ nếu có thể. Bởi vì xử lý quá nhiều dữ liệu, vấn đề sớm muộn sẽ quay trở lại. Thêm vào đó, bạn không thể biết cấu hình của từng máy sẽ chạy ứng dụng của mình nên luôn có nguy cơ ngoại lệ xảy ra trên máy khác.


Thực ra tôi biết cấu hình của máy, cái này chỉ chạy trên một máy chủ và tôi có thể viết cái này cho những thông số kỹ thuật đó. Đây là một mô phỏng monte carlo lớn và tôi đang cố gắng tối ưu hóa bằng cách điền các số ngẫu nhiên vào trước.
m3ntat

0

Tôi đã gặp sự cố tương tự, đó là do StringBuilder.ToString ();


đừng để trình tạo chuỗi trở nên quá lớn
Ricardo Rix

0

Chuyển đổi giải pháp của bạn thành x64. Nếu bạn vẫn gặp sự cố, hãy cấp độ dài tối đa cho mọi thứ có ngoại lệ như bên dưới:

 var jsSerializer = new JavaScriptSerializer();
 jsSerializer.MaxJsonLength = Int32.MaxValue;

0

Nếu bạn không cần Quy trình lưu trữ Visual Studio:

Bỏ chọn tùy chọn: Dự án-> Thuộc tính-> Gỡ lỗi-> Bật quá trình lưu trữ Visual Studio

Và sau đó xây dựng.

Nếu bạn vẫn gặp sự cố:

Đi tới Project-> Properties-> Build Events-> Post-Build Event Command line và dán như sau:

call "$(DevEnvDir)..\..\vc\vcvarsall.bat" x86
"$(DevEnvDir)..\..\vc\bin\EditBin.exe" "$(TargetPath)"  /LARGEADDRESSAWARE

Bây giờ, hãy xây dựng dự án.


-2

Tăng giới hạn quy trình của Windows lên 3gb. (thông qua boot.ini hoặc trình quản lý khởi động Vista)


có thật không? bộ nhớ quy trình tối đa mặc định là gì? Và làm thế nào để thay đổi nó? Nếu tôi chơi trò chơi hoặc thứ gì đó trên PC của mình, nó có thể dễ dàng sử dụng hơn 2 GB trên một EXE / Process duy nhất, tôi không nghĩ đây là vấn đề ở đây.
m3ntat

/ 3GB là quá mức cần thiết cho điều này và có thể gây ra nhiều bất ổn vì nhiều trình điều khiển cho rằng con trỏ không gian người dùng luôn trỏ đến 2GB thấp hơn.
jalf

1
m3ntat: Không, trong Windows 32 bit, một quy trình duy nhất bị giới hạn ở 2GB. 2GB còn lại của không gian địa chỉ được sử dụng bởi hạt nhân.
jalf

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.