Thực hành tốt nhất để buộc thu gom rác trong C #


118

Theo kinh nghiệm của tôi, có vẻ như hầu hết mọi người sẽ nói với bạn rằng ép thu gom rác là không khôn ngoan nhưng trong một số trường hợp bạn đang làm việc với các đối tượng lớn không phải lúc nào cũng được thu thập ở thế hệ 0 nhưng bộ nhớ là một vấn đề, nó ok để buộc thu thập? Có phương pháp nào tốt nhất để làm như vậy không?

Câu trả lời:


112

Cách tốt nhất là không ép thu gom rác.

Theo MSDN:

"Có thể buộc thu gom rác bằng cách gọi Collect, nhưng hầu hết thời gian, điều này nên tránh vì nó có thể tạo ra các vấn đề về hiệu suất."

Tuy nhiên, nếu bạn có thể kiểm tra đáng tin cậy mã của mình để xác nhận rằng việc gọi Collect () sẽ không có tác động tiêu cực thì hãy tiếp tục ...

Chỉ cần cố gắng đảm bảo các đồ vật được dọn dẹp khi bạn không cần nữa. Nếu bạn có các đối tượng tùy chỉnh, hãy xem bằng cách sử dụng câu lệnh "using" và giao diện IDisposable.

Liên kết này có một số lời khuyên thực tế tốt liên quan đến việc giải phóng bộ nhớ / thu gom rác, v.v.:

http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx



4
Nếu các đối tượng của bạn trỏ đến bộ nhớ không được quản lý, bạn có thể cho bộ thu gom rác biết thông qua GC.AddMemoryPressure Api ( msdn.microsoft.com/en-us/library/… ). Điều này cung cấp cho trình thu gom rác nhiều thông tin hơn về hệ thống của bạn mà không can thiệp vào các thuật toán thu thập.
Govert

+1: * xem xét bằng cách sử dụng "tuyên bố sử dụng" và giao diện IDisposable. * Tôi thậm chí sẽ không coi việc ép buộc ngoại trừ như một phương sách cuối cùng - lời khuyên tốt (đọc là 'tuyên bố từ chối trách nhiệm'). Tuy nhiên, tôi buộc tập hợp trong một bài kiểm tra đơn vị để mô phỏng việc mất tham chiếu đang hoạt động trong hoạt động back-end - cuối cùng ném a TargetOfInvocationNullException.
IAbstract

33

Hãy xem xét nó theo cách này - sẽ hiệu quả hơn khi vứt rác nhà bếp khi thùng rác ở mức 10% hay để nó đầy trước khi lấy ra?

Bằng cách không để nó đầy, bạn đang lãng phí thời gian đi bộ đến và đi từ thùng rác bên ngoài. Điều này tương tự như những gì xảy ra khi luồng GC chạy - tất cả các luồng được quản lý đều bị treo trong khi nó đang chạy. Và nếu tôi không nhầm, luồng GC có thể được chia sẻ giữa nhiều AppDomains, vì vậy việc thu gom rác ảnh hưởng đến tất cả chúng.

Tất nhiên, bạn có thể gặp phải một tình huống mà bạn sẽ không sớm thêm bất cứ thứ gì vào thùng rác - giả sử, nếu bạn sắp đi nghỉ. Sau đó, sẽ là một ý kiến ​​hay nếu bạn vứt rác vào thùng rác trước khi ra ngoài.

Điều này ĐÚNG là một lần buộc GC có thể giúp ích - nếu chương trình của bạn chạy không tải, bộ nhớ đang sử dụng sẽ không được thu gom vì không có phân bổ.


5
Nếu bạn có một đứa trẻ sẽ chết nếu bạn để nó hơn một phút và bạn chỉ có một phút để xử lý rác, thì bạn muốn làm một chút mỗi lần thay vì tất cả cùng một lúc. Thật không may, phương thức GC :: Collect () không nhanh hơn nếu bạn gọi nó thường xuyên hơn. Vì vậy, đối với công cụ thời gian thực, nếu bạn không thể chỉ sử dụng cơ chế hủy bỏ và để GC gộp dữ liệu của mình, thì bạn không nên sử dụng hệ thống được quản lý - theo câu trả lời của tôi (có thể là bên dưới cơ chế này, lol).
Jin

1
Trong trường hợp của tôi, tôi đang chạy thuật toán A * (đường đi ngắn nhất) LẶP LẠI để điều chỉnh hiệu suất ... thuật toán này sẽ chỉ được chạy một lần (trên mỗi "bản đồ"). Vì vậy, tôi muốn GC được thực hiện trước mỗi lần lặp lại, bên ngoài "khối đo lường hiệu suất" của tôi, bởi vì tôi cảm thấy rằng mô hình hóa chặt chẽ hơn tình huống trong quá trình sản xuất, trong đó GC có thể / nên được bắt buộc sau khi điều hướng từng "bản đồ".
corlettk

Gọi GC trước khối được đo thực tế không mô hình hóa tình huống trong sản xuất, bởi vì trong sản xuất, GC sẽ được thực hiện trong những thời điểm không thể đoán trước. Để giảm thiểu điều đó, bạn nên thực hiện một phép đo dài bao gồm một số lần thực hiện GC và tính các đỉnh trong GC vào phân tích và thống kê của bạn.
Ran

32

Cách tốt nhất là không ép thu gom rác trong hầu hết các trường hợp. (Mọi hệ thống tôi đã từng làm việc đều buộc phải thu gom rác, đều có những vấn đề cơ bản mà nếu được giải quyết sẽ giúp loại bỏ nhu cầu buộc thu gom rác và tăng tốc hệ thống lên rất nhiều.)

Có một vài trường hợp khi bạn biết thêm về cách sử dụng bộ nhớ thì bộ thu dọn rác sẽ làm. Điều này khó có thể đúng trong một ứng dụng nhiều người dùng hoặc một dịch vụ đang phản hồi nhiều hơn một yêu cầu tại một thời điểm.

Tuy nhiên, trong một số xử lý kiểu hàng loạt, bạn biết nhiều hơn GC. Ví dụ: xem xét một ứng dụng đó.

  • Được cung cấp danh sách các tên tệp trên dòng lệnh
  • Xử lý một tệp đơn sau đó ghi kết quả ra tệp kết quả.
  • Trong khi xử lý tệp, tạo ra rất nhiều đối tượng được liên kết với nhau mà không thể được thu thập cho đến khi quá trình xử lý tệp hoàn tất (ví dụ: cây phân tích cú pháp)
  • Không giữ nhiều trạng thái giữa các tệp mà nó đã xử lý .

Bạn thể đưa ra một trường hợp (sau khi kiểm tra cẩn thận) rằng bạn nên buộc thu thập rác đầy đủ sau khi bạn xử lý từng tệp.

Một trường hợp khác là một dịch vụ thức dậy sau vài phút để xử lý một số mặt hàng và không giữ trạng thái nào khi nó đang ngủ . Sau đó, buộc một bộ sưu tập đầy đủ ngay trước khi đi ngủ thể đáng giá.

Lần duy nhất tôi muốn xem xét việc buộc một bộ sưu tập là khi tôi biết rằng rất nhiều đối tượng đã được tạo gần đây và rất ít đối tượng hiện được tham chiếu.

Tôi thà có một API thu gom rác khi tôi có thể cho nó gợi ý về loại thứ này mà không cần phải ép buộc GC của chính tôi.

Xem thêm " Rico Mariani's Performance Tidbits "


2
Tương tự: Playschool (hệ thống) giữ bút chì màu (tài nguyên). Tùy thuộc vào số lượng trẻ em (nhiệm vụ) và sự khan hiếm của màu sắc, giáo viên (.Net) quyết định cách phân bổ và chia sẻ giữa các trẻ em. Khi một màu hiếm được yêu cầu, giáo viên có thể phân bổ từ nhóm hoặc tìm một màu không được sử dụng. Giáo viên có quyền định kỳ thu thập bút màu không sử dụng (thu gom rác) để giữ cho mọi thứ ngăn nắp (tối ưu hóa việc sử dụng tài nguyên). Nói chung, phụ huynh (lập trình viên) không thể xác định trước chính sách ngăn nắp bút chì màu tốt nhất trong lớp học. Giấc ngủ ngắn theo lịch trình của một đứa trẻ không chắc sẽ là khoảnh khắc tốt để cản trở việc tô màu của những đứa trẻ khác.
AlanK,

1
@AlanK, tôi thích điều này, vì khi bọn trẻ về nhà trong ngày, đó là thời điểm rất tốt để người trợ giúp giáo viên thu dọn tốt mà không để bọn trẻ cản trở. (Hệ thống gần đây tôi đã làm việc trên vừa khởi động lại quá trình dịch vụ vào những thời điểm như vậy instread của forceing GC.)
Ian Ringrose

21

Tôi nghĩ ví dụ do Rico Mariani đưa ra là tốt: có thể thích hợp để kích hoạt GC nếu có sự thay đổi đáng kể trong trạng thái của ứng dụng. Ví dụ: trong trình chỉnh sửa tài liệu, có thể kích hoạt GC khi đóng tài liệu.


2
Hoặc ngay trước khi mở một đối tượng lớn liền kề đã cho thấy lịch sử lỗi và không đưa ra giải pháp hiệu quả để tăng mức độ chi tiết của nó.
crokusek

17

Có một số hướng dẫn chung trong lập trình là tuyệt đối. Một nửa thời gian, khi ai đó nói 'bạn đang làm sai', họ chỉ đang nói ra một lượng giáo điều nhất định. Trong C, nó từng sợ những thứ như mã hoặc luồng tự sửa đổi, trong các ngôn ngữ GC, nó đang buộc GC hoặc cách khác là ngăn GC chạy.

Như trường hợp của hầu hết các hướng dẫn và quy tắc chung (và thực hành thiết kế tốt), hiếm khi có ý nghĩa khi làm việc theo quy chuẩn đã được thiết lập. Bạn phải chắc chắn rằng bạn hiểu trường hợp này, trường hợp của bạn thực sự cần phải loại bỏ thông lệ và bạn hiểu những rủi ro và tác dụng phụ mà bạn có thể gây ra. Nhưng có những trường hợp như vậy.

Các bài toán lập trình rất đa dạng và đòi hỏi một cách tiếp cận linh hoạt. Tôi đã thấy các trường hợp có ý nghĩa khi chặn GC bằng các ngôn ngữ được thu thập rác và những nơi có ý nghĩa để kích hoạt nó hơn là đợi nó xảy ra một cách tự nhiên. 95% thời gian, một trong hai điều này sẽ là dấu hiệu cho thấy bạn chưa tiếp cận đúng vấn đề. Nhưng 1 lần trong năm 20, có lẽ có một trường hợp hợp lệ được thực hiện cho nó.


12

Tôi đã học được cách không cố gắng vượt qua việc thu gom rác. Như đã nói, tôi chỉ cần sử dụng usingtừ khóa khi xử lý các tài nguyên không được quản lý như tệp I / O hoặc kết nối cơ sở dữ liệu.


27
trình biên dịch? trình biên dịch có liên quan gì với GC? :)
KristoferA

1
Không có gì, nó chỉ biên dịch và tối ưu hóa mã. Và điều này chắc chắn không liên quan gì đến CLR ... hoặc thậm chí là .NET.
Kon

1
Các đối tượng chiếm nhiều bộ nhớ, chẳng hạn như hình ảnh rất lớn, cuối cùng có thể không được thu gom rác, trừ khi bạn thu gom rác một cách rõ ràng. Tôi nghĩ rằng đây (những vật thể lớn) là vấn đề của OP.
code4life

1
Gói nó trong một sử dụng sẽ đảm bảo rằng nó được lên lịch cho GC khi nó vượt ra khỏi phạm vi sử dụng. Trừ khi máy tính phát nổ, bộ nhớ đó rất có thể sẽ được dọn sạch.
Kon

9

Không chắc đó có phải là phương pháp hay nhất hay không, nhưng khi làm việc với số lượng lớn hình ảnh trong một vòng lặp (tức là tạo và loại bỏ nhiều đối tượng Đồ họa / Hình ảnh / Bitmap), tôi thường xuyên để GC.Collect.

Tôi nghĩ rằng tôi đã đọc ở đâu đó rằng GC chỉ chạy khi chương trình (hầu hết) không hoạt động và không ở giữa vòng lặp chuyên sâu, vì vậy điều đó có thể giống như một lĩnh vực mà GC thủ công có thể có ý nghĩa.


Bạn có chắc rằng bạn cần cái này? GC sẽ thu thập nếu nó cần bộ nhớ, ngay cả khi mã của bạn không hoạt động.
Konrad Rudolph

Không chắc chắn nó như thế nào trong .net 3.5 SP1 bây giờ, nhưng trước đây (1.1 và tôi tin rằng tôi đã thử nghiệm với 2.0) nó đã tạo ra sự khác biệt trong việc sử dụng bộ nhớ. GC sẽ tất nhiên luôn thu thập khi cần thiết, nhưng bạn vẫn có thể kết thúc lãng phí 100 Megs RAM khi bạn chỉ cần 20. sẽ cần một xét nghiệm few'more dù
Michael Stum

2
GC được kích hoạt trên phân bổ bộ nhớ khi thế hệ 0 đạt đến một ngưỡng nhất định (ví dụ 1MB), không phải khi "thứ gì đó không hoạt động". Nếu không, bạn có thể kết thúc với OutOfMemoryException trong một vòng lặp bằng cách chỉ cần cấp phát và loại bỏ ngay các đối tượng.
liggett78

5
100megs RAM sẽ không bị lãng phí nếu không có quy trình nào khác cần đến nó. Nó mang lại cho bạn một hiệu suất tốt hơn :-P
Orion Edwards

9

Một trường hợp gần đây tôi gặp phải yêu cầu các lệnh gọi thủ công GC.Collect()là khi làm việc với các đối tượng C ++ lớn được bao bọc trong các đối tượng C ++ được quản lý nhỏ, đến lượt chúng được truy cập từ C #.

Bộ thu gom rác không bao giờ được gọi vì lượng bộ nhớ được quản lý được sử dụng không đáng kể, nhưng lượng bộ nhớ không được quản lý được sử dụng là rất lớn. Việc gọi Dispose()các đối tượng theo cách thủ công sẽ yêu cầu tôi phải tự theo dõi khi nào các đối tượng không còn cần thiết nữa, trong khi việc gọi GC.Collect()sẽ dọn dẹp bất kỳ đối tượng nào không còn được giới thiệu .....


6
Một cách tốt hơn để giải quyết vấn đề này là gọi GC.AddMemoryPressure (ApproximateSizeOfUnmanagedResource)hàm tạo và sau đó GC.RemoveMemoryPressure(addedSize)trong trình hoàn thiện. Bằng cách này, bộ thu gom rác sẽ tự động chạy, có tính đến kích thước của các cấu trúc không được quản lý có thể được thu gom. stackoverflow.com/questions/1149181/…
HugoRune

Và một cách tốt hơn nữa để giải quyết vấn đề là thực sự gọi Dispose (), mà bạn vẫn phải thực hiện.
fabspro

2
Cách tốt nhất là sử dụng cấu trúc Đang sử dụng. Hãy thử / Cuối cùng .Dispose là một rắc rối
TamusJRoyce

7

Tôi nghĩ rằng bạn đã liệt kê các phương pháp hay nhất và đó là KHÔNG sử dụng nó trừ khi THỰC SỰ cần thiết. Tôi thực sự khuyên bạn nên xem mã của bạn chi tiết hơn, sử dụng các công cụ lập hồ sơ nếu cần để trả lời những câu hỏi này trước tiên.

  1. Bạn có điều gì đó trong mã của mình đang khai báo các mục ở phạm vi lớn hơn mức cần thiết
  2. Sử dụng bộ nhớ có thực sự quá cao không
  3. So sánh hiệu suất trước và sau khi sử dụng GC.Collect () để xem nó có thực sự hữu ích hay không.

5

Giả sử chương trình của bạn không bị rò rỉ bộ nhớ, các đối tượng tích lũy và không thể được GC-ed trong Gen 0 vì: 1) Chúng được tham chiếu trong một thời gian dài, vì vậy hãy vào Gen1 & Gen2; 2) Chúng là những vật thể lớn (> 80K) nên được đưa vào LOH (Large Object Heap). Và LOH không thực hiện việc thu gọn như trong Gen0, Gen1 & Gen2.

Kiểm tra bộ đếm hiệu suất của "Bộ nhớ .NET" bạn có thể thấy rằng vấn đề 1) thực sự không phải là vấn đề. Nói chung, cứ 10 GC Gen0 sẽ kích hoạt 1 GC Gen1 và cứ 10 GC Gen1 sẽ kích hoạt 1 Gen2 GC. Về mặt lý thuyết, GC1 & GC2 không bao giờ có thể là GC-ed nếu không có áp lực lên GC0 (nếu việc sử dụng bộ nhớ chương trình thực sự có dây). Nó không bao giờ xảy ra với tôi.

Đối với vấn đề 2), bạn có thể kiểm tra bộ đếm hiệu suất "Bộ nhớ .NET" để xác minh xem LOH có bị phình ra hay không. Nếu nó thực sự là một vấn đề với vấn đề của bạn, có lẽ bạn có thể tạo một nhóm đối tượng lớn như blog này gợi ý http://blogs.msdn.com/yunjin/archive/2004/01/27/63642.aspx .


4

Các đối tượng lớn được phân bổ trên LOH (đống đối tượng lớn), không phải trên gen 0. Nếu bạn đang nói rằng chúng không được thu gom rác với gen 0, bạn đã đúng. Tôi tin rằng chúng chỉ được thu thập khi chu kỳ GC đầy đủ (thế hệ 0, 1 và 2) xảy ra.

Nói như vậy, tôi tin rằng ở phía bên kia, GC sẽ điều chỉnh và thu thập bộ nhớ mạnh mẽ hơn khi bạn làm việc với các vật thể lớn và áp lực bộ nhớ đang tăng lên.

Thật khó để nói có nên thu hay không và trong hoàn cảnh nào. Tôi đã từng làm GC.Collect () sau khi loại bỏ các cửa sổ hộp thoại / biểu mẫu với nhiều điều khiển, v.v. (vì vào thời điểm biểu mẫu và các điều khiển của nó kết thúc ở thế hệ 2 do tạo nhiều phiên bản đối tượng nghiệp vụ / tải nhiều dữ liệu - không các đối tượng lớn rõ ràng), nhưng thực sự không nhận thấy bất kỳ tác động tích cực hay tiêu cực nào về lâu dài bằng cách làm như vậy.


4

Tôi muốn nói thêm rằng: Gọi GC.Collect () (+ WaitForPendingFinalizers ()) là một phần của câu chuyện. Như những người khác đã đề cập đúng đến, GC.COllect () là tập hợp không xác định và được để theo quyết định của chính GC (CLR). Ngay cả khi bạn thêm lệnh gọi vào WaitForPendingFinalizers, nó có thể không xác định được. Lấy mã từ liên kết msdn này và chạy mã với phép lặp vòng lặp đối tượng là 1 hoặc 2. Bạn sẽ tìm thấy ý nghĩa không xác định (đặt điểm ngắt trong bộ hủy của đối tượng). Chính xác, hàm hủy không được gọi khi chỉ có 1 (hoặc 2) đối tượng còn sót lại bởi Wait .. (). [Citation reqd.]

Nếu mã của bạn đang xử lý tài nguyên không được quản lý (ví dụ: xử lý tệp bên ngoài), bạn phải triển khai trình hủy (hoặc trình hoàn thiện).

Đây là một ví dụ thú vị:

Lưu ý : Nếu bạn đã thử ví dụ trên từ MSDN, đoạn mã sau sẽ xóa không khí.

class Program
{    
    static void Main(string[] args)
        {
            SomePublisher publisher = new SomePublisher();

            for (int i = 0; i < 10; i++)
            {
                SomeSubscriber subscriber = new SomeSubscriber(publisher);
                subscriber = null;
            }

            GC.Collect();
            GC.WaitForPendingFinalizers();

            Console.WriteLine(SomeSubscriber.Count.ToString());


            Console.ReadLine();
        }
    }

    public class SomePublisher
    {
        public event EventHandler SomeEvent;
    }

    public class SomeSubscriber
    {
        public static int Count;

        public SomeSubscriber(SomePublisher publisher)
        {
            publisher.SomeEvent += new EventHandler(publisher_SomeEvent);
        }

        ~SomeSubscriber()
        {
            SomeSubscriber.Count++;
        }

        private void publisher_SomeEvent(object sender, EventArgs e)
        {
            // TODO: something
            string stub = "";
        }
    }

Tôi đề nghị, trước tiên hãy phân tích đầu ra có thể là gì, sau đó chạy và sau đó đọc lý do bên dưới:

{Hàm hủy chỉ được gọi ngầm sau khi chương trình kết thúc. } Để làm sạch đối tượng một cách xác định, người ta phải triển khai IDisposable và thực hiện một lệnh gọi rõ ràng tới Dispose (). Đó là bản chất! :)


2

Một điều nữa, việc kích hoạt GC Collect một cách rõ ràng KHÔNG thể cải thiện hiệu suất chương trình của bạn. Nó hoàn toàn có thể làm cho nó tồi tệ hơn.

.NET GC được thiết kế tốt và điều chỉnh để thích ứng, có nghĩa là nó có thể điều chỉnh ngưỡng GC0 / 1/2 theo "thói quen" sử dụng bộ nhớ chương trình của bạn. Vì vậy, nó sẽ được điều chỉnh cho phù hợp với chương trình của bạn sau một thời gian chạy. Khi bạn gọi GC.Collect một cách rõ ràng, các ngưỡng sẽ được đặt lại! Và .NET lại phải dành thời gian để thích nghi với "thói quen" chương trình của bạn.

Đề xuất của tôi là luôn tin tưởng .NET GC. Bất kỳ vấn đề bộ nhớ nào xuất hiện, hãy kiểm tra bộ đếm hiệu suất "Bộ nhớ .NET" và chẩn đoán mã của riêng tôi.


6
Tôi nghĩ tốt hơn là bạn nên kết hợp câu trả lời này với câu trả lời trước của bạn.
Salamander2007

1

Không chắc liệu đó có phải là phương pháp hay nhất ...

Đề xuất: không thực hiện điều này hoặc bất cứ điều gì khi không chắc chắn. Đánh giá lại khi biết được sự thật, sau đó thực hiện các bài kiểm tra hiệu suất trước / sau để xác minh.


0

Tuy nhiên, nếu bạn có thể kiểm tra đáng tin cậy mã của mình để xác nhận rằng việc gọi Collect () sẽ không có tác động tiêu cực thì hãy tiếp tục ...

IMHO, điều này tương tự như câu nói "Nếu bạn có thể chứng minh rằng chương trình của bạn sẽ không bao giờ có bất kỳ lỗi nào trong tương lai, thì hãy tiếp tục ..."

Nhìn chung, việc ép buộc GC rất hữu ích cho mục đích gỡ lỗi / kiểm tra. Nếu bạn cảm thấy cần phải làm điều đó vào bất kỳ thời điểm nào khác, thì có thể là bạn đã nhầm hoặc chương trình của bạn đã được xây dựng sai. Dù bằng cách nào, giải pháp không buộc GC ...


"thì hoặc là bạn đã nhầm, hoặc chương trình của bạn đã được xây dựng sai. Dù bằng cách nào, giải pháp không buộc GC ..." Những điều tuyệt đối hầu như luôn không đúng. Có một số trường hợp ngoại lệ nó có ý nghĩa.
cuộn
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.