Làm thế nào để người thu gom rác tránh chồng chất tràn?


23

Vì vậy, tôi đã suy nghĩ về cách thức thu gom rác hoạt động và tôi nghĩ về một vấn đề thú vị. Có lẽ người thu gom rác phải đi qua tất cả các cấu trúc theo cùng một cách. Họ không thể biết thời tiết họ đang đi qua một danh sách được liên kết hoặc một cây cân bằng hoặc bất cứ điều gì. Họ cũng không thể sử dụng quá nhiều bộ nhớ trong tìm kiếm của họ. Một cách có thể, và cách duy nhất tôi có thể nghĩ để vượt qua TẤT CẢ các cấu trúc, có thể chỉ là vượt qua tất cả chúng theo cách đệ quy theo cách bạn sẽ làm cây nhị phân. Tuy nhiên, điều này sẽ cung cấp một ngăn xếp tràn trong danh sách được liên kết hoặc thậm chí chỉ là một cây nhị phân kém cân bằng. Nhưng tất cả các ngôn ngữ thu thập rác mà tôi từng sử dụng dường như không có vấn đề gì trong việc xử lý các trường hợp như vậy.

Trong cuốn sách rồng, nó sử dụng một hàng đợi "Không có kế hoạch". Về cơ bản thay vì duyệt qua cấu trúc một cách đệ quy, nó chỉ thêm những thứ cần được đánh dấu quá hàng đợi và sau đó cho mỗi thứ không được đánh dấu ở cuối, nó sẽ bị xóa. Nhưng hàng đợi này sẽ rất lớn phải không?

Vì vậy, làm thế nào để người thu gom rác đi qua các cấu trúc tùy ý? Làm thế nào để kỹ thuật truyền tải này tránh tràn?


1
GC đi qua tất cả các cấu trúc theo cách ít nhiều giống nhau, nhưng chỉ trong một ý nghĩa rất trừu tượng (xem câu trả lời). Cách họ theo dõi sự việc một cách cụ thể phức tạp hơn rất nhiều so với những bài thuyết trình cơ bản bạn có thể tìm thấy trong sách giáo khoa. Và họ không sử dụng đệ quy. Hơn nữa, với bộ nhớ ảo, các triển khai xấu hiển thị khi làm chậm GC, hiếm khi tràn bộ nhớ.
babou

Bạn lo lắng về không gian cần thiết để truy tìm. Nhưng những gì về không gian hoặc cấu trúc cần thiết để phân biệt bộ nhớ đã được theo dõi và được biết là đang sử dụng, từ bộ nhớ có khả năng thu hồi được. Điều này có thể có một chi phí bộ nhớ đáng kể, có thể tỷ lệ thuận với kích thước heap.
babou

Tôi đã hình dung nó sẽ được thực hiện với một bitvector trên kích thước đối tượng lớn hơn 16 byte, do đó, phần đầu sẽ tối thiểu 1000 lần.
Jake

Có nhiều cách để làm điều đó (xem câu trả lời), và chúng cũng có thể được sử dụng để theo dõi, sau đó sẽ trả lời câu hỏi của bạn (bitvector hoặc bitmap có thể được sử dụng để theo dõi, thay vì ngăn xếp hoặc xếp hàng mà bạn đề xuất). Bạn không thể cho rằng tất cả các đối tượng đều lớn, trừ khi bạn có ý định lãng phí không gian trên các đối tượng nhỏ, trong đó có thể có nhiều đối tượng, và sau đó bạn không nên lo lắng về không gian. Nếu bạn đang ở trong bộ nhớ ảo, không gian thường là một vấn đề ít hơn và các vấn đề rất khác nhau.
babou

Câu trả lời:


13

Lưu ý rằng tôi không phải là một chuyên gia thu gom rác. Câu trả lời này chỉ đưa ra ví dụ về kỹ thuật. Tôi không khẳng định rằng đó là một tổng quan đại diện về kỹ thuật thu gom rác.

Một hàng đợi không có kế hoạch là một lựa chọn phổ biến. Hàng đợi có thể lớn - có khả năng lớn bằng cấu trúc dữ liệu sâu nhất. Hàng đợi thường được lưu trữ rõ ràng, không phải trên ngăn xếp của chuỗi thu gom rác.

Khi tất cả ngoại trừ một trong những phần tử con của một nút đã được quét, nút đó có thể được xóa khỏi hàng đợi không được quét. Đây về cơ bản là một tối ưu hóa cuộc gọi đuôi. Người thu gom rác có thể bao gồm các phương pháp phỏng đoán để cố gắng quét phần con sâu nhất của một nút cuối cùng; ví dụ, một GC cho Lisp nên quét cara constrước cdr.

Một cách để tránh giữ hàng đợi không có kế hoạch là sửa đổi con trỏ tại chỗ, làm cho đứa trẻ tạm thời chỉ vào cha mẹ. Đây là một kỹ thuật truyền tải cây bộ nhớ không đổi được sử dụng trong các bối cảnh khác với các công cụ thu gom rác. Nhược điểm của kỹ thuật này là trong khi GC đi ngang qua cấu trúc dữ liệu, cấu trúc dữ liệu không hợp lệ, do đó, GC phải dừng thế giới. Đây không phải là một công cụ giải quyết: nhiều người thu gom rác bao gồm một giai đoạn ngăn chặn thế giới, ngoài ra còn có các giai đoạn không thể bỏ lỡ rác.


2
Kỹ thuật được mô tả trong đoạn cuối thường được gọi là " đảo ngược con trỏ ".
Logic lang thang

@WanderingLogic Vâng, đảo ngược con trỏ là cách tôi gọi nó trong câu trả lời của riêng tôi. Đó là do tiếng Đức, Schorr và Waite (1967). Tuy nhiên, không đúng khi tuyên bố rằng nó hoạt động trong bộ nhớ không đổi: nó yêu cầu thêm bit cho mỗi ô bằng con trỏ , mặc dù điều này có thể được giảm bằng cách sử dụng ngăn xếp bit. Câu trả lời được chấp nhận mà bạn tham khảo không hoàn toàn chính xác hoặc hoàn thành vì lý do tương tự. plog2pp
babou

Tôi đã sử dụng đảo ngược con trỏ trong một GC tùy chỉnh mà không cần các bit bổ sung này; mẹo là sử dụng một đại diện đặc biệt trong bộ nhớ của các đối tượng trong bộ nhớ. Cụ thể, đối tượng "tiêu đề" ở giữa, với các trường con trỏ trước tiêu đề và các trường không con trỏ sau; hơn nữa, tất cả các con trỏ được căn chỉnh và tiêu đề bao gồm một trường có bit quan trọng nhất luôn được đặt. Do đó, trong quá trình quay ngược con trỏ, việc tiếp cận con trỏ tiếp theo và nhận thấy chúng ta đã hoàn thành với một đối tượng có thể được thực hiện rõ ràng mà không cần thêm bit. Bố cục này cũng hỗ trợ kế thừa OOP.
Thomas Pornin

@Thomasuckingin Tôi nghĩ rằng thông tin bit phải ở đâu đó. Câu hỏi là ở đâu? Chúng ta có thể thảo luận điều này trong trò chuyện? Tôi phải rời đi ngay bây giờ, nhưng tôi muốn đi đến tận cùng của điều này. Hoặc có một mô tả có thể truy cập trên web?
babou


11

Tóm lại : Người thu gom rác không sử dụng đệ quy. Họ chỉ kiểm soát theo dõi bằng cách theo dõi về cơ bản hai bộ (có thể kết hợp). Thứ tự truy tìm và xử lý ô là không liên quan, điều này cho phép tự do thực hiện đáng kể để đại diện cho các bộ. Do đó, có nhiều giải pháp thực sự rất rẻ trong việc sử dụng bộ nhớ. Điều này rất cần thiết vì GC được gọi chính xác khi heap hết bộ nhớ. Mọi thứ có một chút khác biệt với những ký ức ảo lớn, vì các trang mới có thể được phân bổ dễ dàng và kẻ thù không thiếu không gian, mà thiếu địa phương dữ liệu .

Tôi giả sử bạn đang xem xét truy tìm người thu gom rác, không tính tham chiếu mà câu hỏi của bạn dường như không áp dụng.

Câu hỏi tập trung vào chi phí bộ nhớ theo dõi để theo dõi một tập hợp: tập hợp (không được bảo vệ) của các ô nhớ có thể truy cập vẫn chứa con trỏ chưa được theo dõi. Đây chỉ là một nửa vấn đề bộ nhớ cho bộ sưu tập rác. GC cũng phải theo dõi một tập hợp khác: tập (đã truy cập) của tất cả các ô đã được tìm thấy có thể truy cập được, để lấy lại tất cả các ô khác ở cuối quy trình. Thảo luận về cái này và cái kia không có ý nghĩa hạn chế, vì chúng có thể có chi phí tương tự, sử dụng các giải pháp tương tự và thậm chí được kết hợp.VUV

Điều đầu tiên cần lưu ý là tất cả các dấu vết theo cùng một mô hình trừu tượng, dựa trên sự thăm dò có hệ thống của đồ thị có hướng của các ô trong bộ nhớ có thể truy cập được từ chương trình, trong đó các ô nhớ là các đỉnh và các con trỏ là các cạnh được định hướng. Nó sử dụng cho các bộ sau:

  • tập (truy cập) của các tế bào đã được tìm thấy để có thể truy cập bởi các mutator , tức là chương trình hay thuật toán mà GC được thực hiện. Tập được phân chia thành hai tập con khác nhau: ;V V = U TVVV=UT

  • tập hợp (chưa được bảo vệ) của các ô được truy cập với các con trỏ chưa được theo dõi;U

  • tập (theo dõi) của các ô được truy cập có tất cả các con trỏ của chúng được theo dõi.T

  • chúng tôi cũng lưu ý tập hợp tất cả các ô trong heap, có sử dụng hay không.H

Chỉ và , hoặc và , cần được biểu diễn bằng cách nào đó để thuật toán hoạt động.U U TVUUT

Thuật toán bắt đầu từ một số con trỏ gốc được biết đến với hệ thống thời gian chạy (thường là các con trỏ trong bộ nhớ được cấp phát ngăn xếp) và đặt tất cả các ô mà chúng trỏ vào tập không được bảo vệ (do đó cũng ở ).VUV

Sau đó, bộ sưu tập lấy các ô trong từng cái một và kiểm tra từng ô tất cả các con trỏ của nó. Đối với mỗi con trỏ, nếu ô nhọn nằm trong , thì không có gì được thực hiện, nếu không thì ô nhọn được thêm vào , vì các con trỏ của nó vẫn chưa được kiểm tra. Khi tất cả các con trỏ của nó đã được xử lý, ô được chuyển từ tập chưa được xử lý sang tập theo dõi .c V U c U TUcVUcUT

Dấu vết chấm dứt khi trống. Điều này chắc chắn sẽ xảy ra, vì không có tế bào nào đi qua hơn một lần. Tại thời điểm đó, và tất cả các ô trong được biết là có thể truy cập được vào chương trình, do đó không thể thu hồi được. bổ sung của trong heap xác định những ô nào không thể truy cập được bằng chương trình trình biến đổi và có thể được người thu thập lấy lại để phân bổ trong tương lai cho trình biến đổi.U V = T V H - V VUUV=TVHVV

Của couse, các chi tiết khác nhau tùy thuộc vào cách các bộ được thực hiện và tùy thuộc vào đó là và , hay và , được thể hiện một cách hiệu quả.U U TVUUT

Tôi cũng bỏ qua chi tiết về một tế bào là gì, dù chúng có một kích cỡ hay nhiều, cách chúng ta tìm thấy con trỏ trong chúng, cách chúng có thể được nén và một loạt các vấn đề kỹ thuật khác mà bạn có thể tìm thấy trong sách và khảo sát về thu gom rác .

Bạn có thể nhận thấy rằng đây là một thuật toán cực kỳ đơn giản. Không có đệ quy, mà chỉ có một vòng lặp trên các phần tử của tập có thể phát triển khi nó đang được xử lýU , cho đến khi cuối cùng nó trống rỗng. Không có một giả định tiên nghiệm về bộ nhớ thêm. Bất cứ điều gì cho phép xác định các bộ và thực hiện đủ rẻ các hoạt động cần thiết sẽ làm. Lưu ý rằng thứ tự các ô được xử lý là không liên quan (không cần cụ thể cho ngăn xếp đẩy xuống), điều này mang lại rất nhiều sự tự do cho việc chọn phương tiện để biểu diễn các bộ một cách hiệu quả.

Trường hợp thực hiện được biết khác nhau là trong cách các bộ này được thực sự đại diện. Nhiều kỹ thuật đã thực sự được sử dụng:

  • bit map: Một số không gian bộ nhớ được giữ lại cho một bản đồ có một bit cho mỗi ô nhớ, có thể được tìm thấy bằng cách sử dụng địa chỉ của ô. Bit được bật khi ô tương ứng nằm trong tập xác định bởi bản đồ. Nếu chỉ sử dụng bản đồ bit, bạn chỉ cần 2 bit cho mỗi ô.

  • cách khác, bạn có thể có không gian cho một bit thẻ đặc biệt (hoặc 2) trong mỗi ô để đánh dấu nó.

  • list: bạn tạo một danh sách các ô trong tập hợp. Bạn không cần một ngăn xếp, hoặc một cấu trúc dữ liệu cụ thể. Trong một số hệ thống, kỹ thuật đảo ngược con trỏ sắc sảo cho phép xây dựng danh sách với rất ít bộ nhớ, chính xác là bit trong đó là số con trỏ trên mỗi ô, điều này được giảm thêm bằng cách sắp xếp các bit.plog2pp

  • bạn có thể kiểm tra một vị ngữ về nội dung của ô và các con trỏ của nó.

  • bạn có thể định vị lại ô trong một phần bộ nhớ trống dành cho tất cả chỉ các ô thuộc về tập hợp được biểu diễn.

  • một trường hợp đặc biệt thú vị là có một ô được truy cập được di chuyển trong một vùng bộ nhớ liền kề khác (tìm tập ) và biểu diễn tập của các ô được theo dõi bởi một địa chỉ ranh giới duy nhất, nhưng thay đổi, lớn hơn địa chỉ của các ô trong , và ít hơn so với .T T UVTTU

  • bạn thực sự có thể kết hợp các kỹ thuật này, ngay cả đối với một bộ duy nhất.

Như đã nói, tất cả những điều trên đã được sử dụng bởi một số người thu gom rác đã triển khai, kỳ lạ như một số có vẻ như. Tất cả phụ thuộc vào các ràng buộc khác nhau của việc thực hiện. Và chúng có thể khá rẻ trong việc sử dụng bộ nhớ, có thể được giúp đỡ bằng cách xử lý các chính sách đơn hàng có thể được tự do lựa chọn cho mục đích đó, vì chúng không quan trọng đối với kết quả cuối cùng.

Những gì có vẻ kỳ lạ nhất, chuyển các tế bào trong một khu vực mới, thực sự rất phổ biến: nó được gọi là bộ sưu tập sao chép. Nó chủ yếu được sử dụng với bộ nhớ ảo.

Rõ ràng không có đệ quy và ngăn xếp thuật toán trình biến đổi không phải sử dụng.

Một điểm quan trọng khác là nhiều GC hiện đại được triển khai cho các bộ nhớ ảo lớn . Sau đó, có được không gian để thực hiện và danh sách bổ sung hoặc ngăn xếp không phải là một vấn đề vì các trang mới có thể được phân bổ dễ dàng. Tuy nhiên, trong những ký ức ảo lớn, kẻ thù không phải là thiếu không gian mà là thiếu địa phương . Sau đó, cấu trúc đại diện cho các bộ và việc sử dụng chúng, phải được hướng tới việc bảo tồn địa phương của cấu trúc dữ liệu và thực hiện GC. Vấn đề không phải là không gian mà là thời gian. Việc triển khai không đầy đủ có nhiều khả năng cho thấy sự chậm chạp không thể chấp nhận hơn là tràn bộ nhớ.

Tôi đã không đưa ra các tài liệu tham khảo cho nhiều thuật toán cụ thể, kết quả từ các kết hợp khác nhau của các kỹ thuật này, vì điều này dường như đủ dài.


4

Cách tiêu chuẩn để tránh tràn ngăn xếp là sử dụng ngăn xếp rõ ràng (được lưu trữ dưới dạng cấu trúc dữ liệu trong heap). Điều đó cũng làm việc cho những mục đích này. Người thu gom rác thường có một danh sách công việc về các hạng mục cần được kiểm tra / duyệt qua, phục vụ cho vai trò này. Chẳng hạn, hàng đợi "Không có kế hoạch" của bạn là một ví dụ về chính xác loại mẫu này. Hàng đợi có thể có khả năng lớn, nhưng nó không gây ra tràn ngăn xếp, vì nó không được lưu trữ trong phân đoạn ngăn xếp. Trong mọi trường hợp, nó sẽ không bao giờ lớn hơn số lượng vật thể sống trong đống.


Khi GC được gọi, heap thường đầy. Một điểm khác là nó xảy ra rằng ngăn xếp và đống phát triển từ cả hai đầu của cùng một không gian bộ nhớ ..
babou

4

Trong các mô tả "cổ điển" về bộ sưu tập rác (ví dụ: Mark Wilson, " Kỹ thuật thu gom rác không xử lý ", Hội thảo quốc tế về quản lý bộ nhớ , 1992, ( liên kết thay thế ) hoặc mô tả trong Triển khai trình biên dịch hiện đại của Andrew Appel (Nhà xuất bản Đại học Cambridge, 1998)), người sưu tầm được phân loại là "Đánh dấu và quét" hoặc "Sao chép".

Bộ sưu tập Mark và Sweep tránh cần thêm không gian bằng cách sử dụng đảo ngược con trỏ, như được mô tả trong câu trả lời của @ Gilles. Appel nói rằng Knuth gán thuật toán đảo ngược con trỏ cho Peter Deutsch và Herbert Schorr và WM Waite.

Sao chép bộ thu gom rác sử dụng thuật toán thường được gọi là thuật toán của Cheyney để thực hiện duyệt qua hàng đợi mà không cần thêm dung lượng. Thuật toán này đã được giới thiệu trong CJ Cheyney, "Thuật toán nén danh sách không lợi nhuận", Comm. ACM , 13 (11): 677-678, 1970.

Trong trình thu gom rác sao chép, bạn có một đoạn bộ nhớ mà bạn đang cố thu thập, được gọi là từ không gian và một đoạn bộ nhớ mà bạn đang sử dụng cho các bản sao được gọi là không gian . Không gian được tổ chức dưới dạng một hàng đợi với một scancon trỏ trỏ đến bản ghi cũ nhất được sao chép nhưng không được quét và một freecon trỏ trỏ đến vị trí tự do tiếp theo trong không gian. Bức tranh về điều này từ bài báo của Wilson là:

Ví dụ thuật toán của Cheyney

Khi bạn quét từng mục vào không gian, bạn sao chép con của nó từ không gian sang freecon trỏ vào không gian, sau đó thay đổi con trỏ sang con từ không gian sang bản sao mới của trẻ vào không gian. Có một mẹo bổ sung mà bạn cần sử dụng khi cấu trúc dữ liệu của bạn không có cây (khi một đứa trẻ có thể có nhiều hơn một cha mẹ). Trong trường hợp đó, khi bạn sao chép một đứa trẻ từ không gian sang không gian, bạn cần ghi đè lên phiên bản cũ của đứa trẻ bằng một con trỏ chuyển tiếp sang bản sao mới của đứa trẻ. Sau đó, nếu bạn từng quét một con trỏ khác đến phiên bản cũ của đứa trẻ, bạn sẽ nhận ra rằng nó đã được sao chép và không sao chép lại.


Trên thực tế, như đã giải thích trong câu trả lời của tôi, cả bộ sưu tập Mark + Sweep và Copy đều là cùng một thuật toán đồ thị trừu tượng. Bộ sưu tập MS và Copy chỉ khác nhau về cách các tập hợp được sử dụng bởi thuật toán trừu tượng và cả hai họ được bao gồm, với nhiều biến thể, trong một số kết hợp các kỹ thuật triển khai tập hợp mà tôi mô tả trong câu trả lời của mình. Một số biến thể GC thực sự trộn MS và Copy trong cùng một GC. Tách MS và Copy được một số người coi là một cách thuận tiện để cấu trúc sách, nhưng nó là một cách tùy tiện và tôi tin rằng tầm nhìn đã lỗi thời.
babou

@babou: Nếu một người đang sử dụng thuật toán sao chép, trong đó mọi thứ được truy cập sẽ được sao chép (chậm, nhưng có thể hữu ích trên các nền tảng nhỏ nơi bộ công việc không bao giờ lớn như vậy), một số thuật toán có thể được đơn giản hóa một chút bộ nhớ trước đây bị chiếm bởi một đối tượng di dời như một bàn di chuột. Người ta cũng có thể có được khả năng hạn chế để các luồng khác thực hiện truy cập chỉ đọc vào các đối tượng trong khi thu thập với điều kiện một kiểm tra tính hợp lệ của đối tượng trước và sau mỗi lần đọc và theo con trỏ chuyển tiếp nếu một đối tượng đã di chuyển.
supercat

@supercat Tôi không chắc bạn đang cố nói gì, ý định của bạn là gì. Một số tuyên bố của bạn có vẻ đúng. Nhưng tôi không hiểu làm thế nào bạn có thể sử dụng từ không gian trước khi chu trình GC kết thúc (nó chứa các con trỏ chuyển tiếp). Và nó sẽ là một Scratchpad để làm gì? Đơn giản hóa algoritm như thế nào? Liên quan đến nhiều luồng đột biến được thực thi trong khi GC đang diễn ra, đây phần lớn là một vấn đề trực giao, mặc dù nó có thể ảnh hưởng đến việc thực hiện nghiêm trọng. Tôi sẽ không cố gắng để giải quyết điều đó trong các ý kiến. Nó sẽ tăng ít vấn đề hơn trong việc truy cập chỉ đọc, nhưng ma quỷ nằm trong các chi tiết.
babou
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.