Tại sao Khối Đối tượng Lớn và tại sao chúng ta quan tâm?


105

Tôi đã đọc về Generations và Large object heap. Nhưng tôi vẫn không hiểu ý nghĩa (hoặc lợi ích) của việc có đống đối tượng Lớn là gì?

Điều gì có thể đã xảy ra (về hiệu suất hoặc bộ nhớ) nếu CLR chỉ dựa vào Thế hệ 2 (Xem xét rằng ngưỡng cho Gen0 và Gen1 là nhỏ để xử lý các đối tượng lớn) để lưu trữ các đối tượng lớn?


6
Điều này đặt ra cho tôi hai câu hỏi cho các nhà thiết kế .NET: 1. Tại sao một chương trình chống phân mảnh LOH không được gọi trước khi một OutOfMemoryException được ném? 2. Tại sao không có các đối tượng LOH có một mối quan hệ đã ở lại với nhau (lớn thích cuối heap và nhỏ ở đầu)
Jacob Brewer

Câu trả lời:


195

Một bộ sưu tập rác không chỉ loại bỏ các đối tượng không được tham chiếu, nó còn thu gọn đống. Đó là một tối ưu hóa rất quan trọng. Nó không chỉ làm cho việc sử dụng bộ nhớ hiệu quả hơn (không có lỗ không sử dụng) mà còn làm cho bộ nhớ cache của CPU hiệu quả hơn nhiều. Bộ nhớ đệm thực sự là một vấn đề lớn trên các bộ vi xử lý hiện đại, chúng nhanh hơn so với bus bộ nhớ.

Việc thu gọn được thực hiện đơn giản bằng cách sao chép các byte. Tuy nhiên điều đó cần có thời gian. Đối tượng càng lớn, càng có nhiều khả năng chi phí sao chép nó cao hơn các cải tiến khả năng sử dụng bộ đệm CPU.

Vì vậy, họ đã chạy một loạt các điểm chuẩn để xác định điểm hòa vốn. Và đạt 85.000 byte là điểm giới hạn mà việc sao chép không còn cải thiện hiệu suất. Với một ngoại lệ đặc biệt đối với mảng kép, chúng được coi là 'lớn' khi mảng có hơn 1000 phần tử. Đó là một cách tối ưu hóa khác cho mã 32-bit, bộ phân bổ heap đối tượng lớn có thuộc tính đặc biệt là nó phân bổ bộ nhớ tại các địa chỉ được căn chỉnh thành 8, không giống như bộ cấp phát thế hệ thông thường chỉ phân bổ căn chỉnh cho 4. Việc căn chỉnh đó là một vấn đề lớn đối với , đọc hoặc viết một đôi không căn chỉnh rất tốn kém. Điều kỳ lạ là thông tin Microsoft thưa thớt không bao giờ đề cập đến các mảng dài, không chắc chắn về điều đó.

Fwiw, có rất nhiều lập trình viên lo lắng về việc đống đối tượng lớn không được nén chặt. Điều này luôn được kích hoạt khi họ viết các chương trình sử dụng hơn một nửa toàn bộ không gian địa chỉ có sẵn. Tiếp theo là sử dụng một công cụ như trình biên dịch bộ nhớ để tìm hiểu lý do tại sao chương trình bị đánh bom mặc dù vẫn còn rất nhiều bộ nhớ ảo chưa sử dụng. Một công cụ như vậy cho thấy các lỗ hổng trong LOH, các phần bộ nhớ không được sử dụng, nơi trước đây có một vật thể lớn sống nhưng đã bị thu gom rác. Đó là cái giá không thể tránh khỏi của LOH, lỗ chỉ có thể được sử dụng lại bằng cách phân bổ cho một đối tượng có kích thước bằng hoặc nhỏ hơn. Vấn đề thực sự là giả định rằng một chương trình nên được phép sử dụng tất cả bộ nhớ ảo bất kỳ lúc nào.

Nếu không, một vấn đề sẽ biến mất hoàn toàn bằng cách chỉ chạy mã trên hệ điều hành 64 bit. Quá trình 64 bit có sẵn 8 terabyte không gian địa chỉ bộ nhớ ảo, nhiều hơn 3 bậc so với quy trình 32 bit. Bạn chỉ không thể chạy ra khỏi lỗ.

Tóm lại, LOH làm cho mã chạy hiệu quả hơn. Với chi phí sử dụng không gian địa chỉ bộ nhớ ảo có sẵn kém hiệu quả hơn.


UPDATE, .NET 4.5.1 hiện hỗ trợ nén thuộc tính LOH , GCSettings.LargeObjectHeapCompactionMode . Vui lòng coi chừng hậu quả.


3
@Hans Passant, bạn có thể vui lòng làm rõ về hệ thống x64, ý bạn là vấn đề này hoàn toàn biến mất?
Johnny_

Một số chi tiết triển khai của LOH có ý nghĩa, nhưng một số làm tôi khó hiểu. Ví dụ, tôi có thể hiểu rằng nếu nhiều đối tượng lớn được tạo ra và bị bỏ rơi, nó thường có thể được mong muốn để xóa chúng en masse trong một bộ sưu tập Gen2 hơn từng phần trong bộ sưu tập Gen0, nhưng nếu người ta tạo ra và bỏ ví dụ như một mảng của 22.000 chuỗi mà không có tham chiếu bên ngoài nào tồn tại, lợi thế nào tồn tại khi có các bộ sưu tập Gen0 và Gen1 gắn thẻ tất cả 22.000 chuỗi là "sống" mà không quan tâm đến việc có bất kỳ tham chiếu nào tồn tại trong mảng không?
supercat

6
Tất nhiên vấn đề phân mảnh cũng giống như trên x64. Sẽ chỉ mất vài ngày nữa để chạy quá trình máy chủ của bạn trước khi nó bắt đầu hoạt động.
Lothar

1
Hmm, không, đừng bao giờ đánh giá thấp 3 bậc của độ lớn. Mất bao lâu để thu thập một đống rác 4 terabyte là điều bạn không thể tránh khỏi việc phát hiện ra rất lâu trước khi nó gần đạt được.
Hans Passant

2
@HansPassant Bạn có thể làm ơn thay đổi tuyên bố này không: "Mất bao lâu để thu thập một đống rác 4 terabyte là điều bạn không thể tránh khỏi việc phát hiện ra rất lâu trước khi nó gần đạt được điều đó."
relatively_random

9

Nếu kích thước của đối tượng lớn hơn một số giá trị được ghim (85000 byte trong .NET 1), thì CLR sẽ đặt nó trong Large Object Heap. Điều này tối ưu hóa:

  1. Phân bổ đối tượng (đối tượng nhỏ không bị trộn lẫn với đối tượng lớn)
  2. Thu gom rác (chỉ thu gom LOH trên GC đầy đủ)
  3. Chống phân mảnh bộ nhớ (LOH không bao giờ hiếm khi bị nén)

9

Sự khác biệt cơ bản của đống đối tượng nhỏ (SOH) và đống đối tượng lớn (LOH) là, bộ nhớ trong SOH được nén lại khi được thu thập, trong khi LOH thì không, như bài viết này minh họa. Việc nén các vật lớn sẽ tốn rất nhiều chi phí. Tương tự với các ví dụ trong bài viết, giả sử di chuyển một byte trong bộ nhớ cần 2 chu kỳ, sau đó nén một đối tượng 8MB trong máy tính 2GHz cần 8ms, đây là một chi phí lớn. Xem xét các đối tượng lớn (mảng trong hầu hết các trường hợp) là khá phổ biến trong thực tế, tôi cho rằng đó là lý do tại sao Microsoft ghim các đối tượng lớn trong bộ nhớ và đề xuất LOH.

BTW, theo bài đăng này , LOH thường không tạo ra các vấn đề về phân mảnh bộ nhớ.


1
Việc tải một lượng lớn dữ liệu vào các đối tượng được quản lý thường làm giảm chi phí 8ms để thu gọn LOH. Trong thực tế trong hầu hết các ứng dụng dữ liệu lớn, chi phí LOH là nhỏ so với phần còn lại của hiệu suất ứng dụng.
Shiv

3

Nguyên nhân chính là không có khả năng xảy ra (và có thể là thiết kế tồi) rằng một quy trình sẽ tạo ra nhiều đối tượng lớn có tuổi thọ ngắn để CLR phân bổ các đối tượng lớn vào một đống riêng biệt mà trên đó nó chạy GC theo một lịch trình khác với đống thông thường. http://msdn.microsoft.com/en-us/magazine/cc534993.aspx


Ngoài ra, việc đặt các đối tượng lớn lên trên, chẳng hạn, thế hệ 2 có thể làm giảm hiệu suất, vì sẽ mất nhiều thời gian để thu gọn bộ nhớ, đặc biệt nếu một lượng nhỏ được giải phóng và các đối tượng HUGE phải được sao chép sang một vị trí mới. LOH hiện tại không được nén vì lý do hiệu suất.
Christopher Currens

Tôi nghĩ đó chỉ là thiết kế tồi bởi vì GC không xử lý nó tốt.
CodesInChaos

@CodeInChaos Rõ ràng, có một số cải tiến khác trong .NET 4.5
Christian.K

1
@CodeInChaos: Mặc dù hệ thống có thể đợi cho đến khi tập hợp gen2 trước khi cố gắng lấy lại bộ nhớ từ các đối tượng LOH thậm chí tồn tại trong thời gian ngắn, nhưng tôi không thể thấy bất kỳ lợi thế hiệu suất nào khi khai báo các đối tượng LOH (và bất kỳ đối tượng nào mà chúng giữ tài liệu tham khảo) sống vô điều kiện trong các bộ sưu tập gen0 và gen1. Có một số tối ưu hóa được thực hiện bởi một giả định như vậy không?
supercat

@supercat Tôi đã xem liên kết được Myles McDonnell đề cập. Sự hiểu biết của tôi là: 1. Thu thập LOH xảy ra trong GC thế hệ 2. 2. Bộ sưu tập LOH không bao gồm nén (tính đến thời điểm bài báo được viết). Thay vào đó, nó sẽ đánh dấu các vật thể đã chết là có thể tái sử dụng và các lỗ này sẽ phục vụ việc phân bổ LOH trong tương lai nếu đủ lớn. Vì điểm 1, xem xét rằng GC thế hệ 2 sẽ chậm nếu có nhiều đối tượng trong thế hệ 2, tôi nghĩ tốt hơn là nên tránh sử dụng LOH càng nhiều càng tốt trong trường hợp này.
người hâm mộ robbie

0

Tôi không phải là chuyên gia về CLR, nhưng tôi sẽ tưởng tượng rằng việc có một heap chuyên dụng cho các đối tượng lớn có thể ngăn chặn việc quét GC không cần thiết đối với các heap thế hệ hiện có. Việc phân bổ một đối tượng lớn yêu cầu một lượng lớn bộ nhớ trống liền kề . Để cung cấp điều đó từ các "lỗ hổng" nằm rải rác trong đống thế hệ, bạn cần các giao dịch thường xuyên (chỉ được thực hiện với các chu kỳ GC).

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.