Hướng dẫn does c++ have garbage collection - c ++ có bộ sưu tập rác không

Câu trả lời thực sự là cách duy nhất để tạo ra một cơ chế thu gom rác an toàn, hiệu quả là có sự hỗ trợ ở cấp độ ngôn ngữ cho các tài liệu tham khảo mờ đục. (Hoặc, ngược lại, thiếu hỗ trợ ở cấp độ ngôn ngữ để thao tác bộ nhớ trực tiếp.)language-level support for opaque references. (Or, conversely, a lack of language-level support for direct memory manipulation.)

Java và C# có thể làm điều đó bởi vì họ có các loại tham chiếu đặc biệt không thể bị thao túng. Điều này mang lại cho thời gian chạy tự do để làm những việc như di chuyển các đối tượng được phân bổ trong bộ nhớ, điều này rất quan trọng đối với việc triển khai GC hiệu suất cao.move allocated objects in memory, which is crucial to a high-performance GC implementation.

Đối với hồ sơ, không có triển khai GC hiện đại nào sử dụng đếm tham chiếu, vì vậy đó hoàn toàn là cá trích đỏ. Các GC hiện đại sử dụng bộ sưu tập thế hệ, trong đó phân bổ mới được đối xử về cơ bản giống như cách phân bổ xếp chồng bằng ngôn ngữ như C ++, và sau đó định kỳ bất kỳ đối tượng mới được phân bổ mới vẫn còn của các đối tượng được giải quyết cùng một lúc.no modern GC implementation uses reference counting, so that is completely a red herring. Modern GCs use generational collection, where new allocations are treated essentially the same way that stack allocations are in a language like C++, and then periodically any newly allocated objects that are still alive are moved to a separate "survivor" space, and an entire generation of objects is deallocated at once.

Cách tiếp cận này có ưu và nhược điểm: Ưu điểm là phân bổ đống bằng ngôn ngữ hỗ trợ GC nhanh như phân bổ xếp chồng trong một ngôn ngữ không hỗ trợ GC và nhược điểm là các đối tượng cần thực hiện dọn dẹp trước khi bị phá hủy yêu cầu một cơ chế riêng biệt (ví dụ: từ khóa using của C#) hoặc nếu không mã dọn dẹp của chúng chạy không xác định.as fast as stack allocations in a language that doesn't support GC, and the downside is that objects that need to perform cleanup before being destroyed either require a separate mechanism (e.g. C#'s using keyword) or else their cleanup code runs non-deterministically.

Lưu ý rằng một chìa khóa cho GC hiệu suất cao là phải có hỗ trợ ngôn ngữ cho một lớp tài liệu tham khảo đặc biệt. C không có hỗ trợ ngôn ngữ này và sẽ không bao giờ; Vì C ++ có quá tải toán tử, nó có thể mô phỏng loại con trỏ GC'D, mặc dù nó sẽ phải được thực hiện cẩn thận. Trên thực tế, khi Microsoft phát minh ra phương ngữ C ++ của họ sẽ chạy theo CLR (thời gian chạy .NET), họ phải phát minh ra một cú pháp mới cho "tham chiếu kiểu C#" (ví dụ: Foo^) để phân biệt chúng với "C ++- "(ví dụ: Foo&).

Những gì C ++ có, và những gì được sử dụng thường xuyên bởi các lập trình viên C ++, là những con trỏ thông minh, thực sự chỉ là một cơ chế tính toán tham chiếu. Tôi sẽ không coi việc đếm tham chiếu là GC "thực sự", nhưng nó mang lại nhiều lợi ích tương tự, với chi phí hiệu suất chậm hơn so với quản lý bộ nhớ thủ công hoặc GC thực sự, nhưng với lợi thế phá hủy xác định.smart pointers, which are really just a reference-counting mechanism. I wouldn't consider reference counting to be "true" GC, but it does provide many of the same benefits, at the cost of slower performance than either manual memory management or true GC, but with the advantage of deterministic destruction.

Vào cuối ngày, câu trả lời thực sự sôi sục với tính năng thiết kế ngôn ngữ. C đã đưa ra một lựa chọn, C ++ đã đưa ra lựa chọn cho phép nó tương thích ngược với C trong khi vẫn cung cấp các lựa chọn thay thế đủ tốt cho hầu hết các mục đích và Java và C# đã đưa ra một lựa chọn khác không tương thích với C nhưng cũng đủ tốt cho hầu hết các mục đích. Thật không may, không có viên đạn bạc, nhưng làm quen với các lựa chọn khác nhau ngoài kia sẽ giúp bạn chọn đúng cho bất kỳ chương trình nào bạn hiện đang cố gắng xây dựng.

Từ đầu tiên xuất hiện trong đầu khi tôi nghe về việc giới thiệu các kỹ thuật thu gom rác vào chương trình C hoặc C ++ là những điều vô nghĩa. Như với bất kỳ lập trình viên C tốt nào khác yêu thích ngôn ngữ này, ý nghĩ để lại sự quản lý của trí nhớ của tôi cho những người khác dường như gần như gây khó chịu. Tôi đã có một cảm giác tương tự 15 năm trước, khi tôi lần đầu tiên nghe về các trình biên dịch sẽ tạo ra mã lắp ráp thay mặt tôi. Tôi đã quen với việc viết mã của mình trực tiếp trong 6510 opcodes, nhưng đó là Commodore 64, và một câu chuyện hoàn toàn khác.

Bộ sưu tập rác là gì?

Bộ sưu tập rác (GC) là một cơ chế cung cấp khai hoang bộ nhớ tự động cho các khối bộ nhớ không sử dụng. Các lập trình viên tự động phân bổ bộ nhớ, nhưng khi một khối không còn cần thiết, họ không phải trả lại cho hệ thống một cách rõ ràng với một cuộc gọi miễn phí (). Động cơ GC quan tâm đến việc nhận ra rằng một khối bộ nhớ được phân bổ (HEAP) cụ thể không được sử dụng nữa và đặt nó trở lại khu vực bộ nhớ miễn phí. GC được John McCarthy giới thiệu vào năm 1958, là cơ chế quản lý bộ nhớ của ngôn ngữ LISP. Kể từ đó, các thuật toán GC đã phát triển và bây giờ có thể cạnh tranh với quản lý bộ nhớ rõ ràng. Một số ngôn ngữ dựa trên GC. Java có lẽ là người phổ biến nhất, và những người khác bao gồm Lisp, Sơ đồ, Smalltalk, Perl và Python. C và C ++, theo truyền thống của một cách tiếp cận cấp thấp, đáng kính đối với quản lý tài nguyên hệ thống, là những ngoại lệ đáng chú ý nhất trong danh sách này.

Nhiều cách tiếp cận khác nhau để thu thập rác tồn tại, dẫn đến một số gia đình của các thuật toán bao gồm đếm tham chiếu, đánh dấu và quét và sao chép GC. Các thuật toán lai, cũng như các biến thể thế hệ và bảo thủ, hoàn thành hình ảnh. Chọn một thuật toán GC cụ thể thường không phải là nhiệm vụ của lập trình viên, vì hệ thống quản lý bộ nhớ được áp đặt bởi ngôn ngữ lập trình được thông qua. Một ngoại lệ cho quy tắc này là Thư viện GC Boehm-Demers-Weiser (BDW), một gói phổ biến cho phép các lập trình viên C và C ++ đưa quản lý bộ nhớ tự động vào các chương trình của họ. Câu hỏi là: Tại sao họ muốn làm một điều như thế này?

Thư viện GC Boehm-Demers-Weiser

Thư viện BDW là một thư viện có sẵn miễn phí cung cấp các chương trình C và C ++ với khả năng thu gom rác. Thuật toán mà nó sử dụng thuộc họ của các nhà sưu tập Mark và Score, nơi GC được chia thành hai giai đoạn. Đầu tiên, việc quét tất cả bộ nhớ trực tiếp được thực hiện để đánh dấu các khối không sử dụng. Sau đó, một giai đoạn quét chăm sóc việc đưa các khối được đánh dấu vào danh sách các khối miễn phí. Hai giai đoạn có thể, và thường được thực hiện riêng để tăng thời gian phản hồi chung của thư viện. Thuật toán BDW cũng là thế hệ; Nó tập trung tìm kiếm không gian trống trên các khối mới hơn. Điều này dựa trên ý tưởng rằng các khối cũ hơn thống kê sống lâu hơn. Nói cách khác, hầu hết các khối được phân bổ có thời gian tồn tại ngắn. Cuối cùng, thuật toán BDW là bảo thủ ở chỗ nó cần đưa ra các giả định về các biến thực sự là con trỏ đến dữ liệu động và các biến nào chỉ nhìn theo cách đó. Đây là hậu quả của các ngôn ngữ được đánh máy yếu.

Trình thu thập BDW đến dưới dạng thư viện tĩnh hoặc động và được cài đặt dễ dàng bằng cách tải xuống gói tương ứng (xem tài nguyên) và chạy cấu hình truyền thống, thực hiện và thực hiện các lệnh cài đặt. Một số phân phối Linux cũng đi kèm với một gói đã được chế tạo. Ví dụ: với Gentoo, bạn chỉ cần gõ Boehm-GC để cài đặt nó. Các tệp được cài đặt bao gồm cả đối tượng được chia sẻ (libgc.o) và thư viện tĩnh (libgc.a).

Sử dụng thư viện là một nhiệm vụ khá đơn giản; Đối với các chương trình mới được phát triển, bạn chỉ cần gọi gc_alloc () để lấy bộ nhớ và sau đó quên nó khi bạn không cần nó nữa. Hãy quên đi nó, có nghĩa là đặt tất cả các con trỏ tham chiếu nó vào null. Đối với các nguồn hiện có, thay thế tất cả các cuộc gọi phân bổ (malloc, calloc, realloc) với các cuộc gọi được kết hợp GC. Tất cả các cuộc gọi miễn phí () được thay thế không có gì cả, nhưng không đặt bất kỳ con trỏ nào có liên quan thành NULL.

GC_ALLOC () thực sự đặt bộ nhớ được phân bổ thành 0 để giảm thiểu rủi ro rằng các giá trị từ trước bị giải thích sai là con trỏ hợp lệ của động cơ GC. Do đó, gc_alloc () hoạt động giống như calloc () hơn malloc ().

Sử dụng GC trong các chương trình C hiện có

Nếu bạn muốn thử GC trong một ứng dụng hiện có, hãy chỉnh sửa thủ công mã nguồn để thay đổi mallocs và giải phóng là không cần thiết. Để chuyển hướng các cuộc gọi đó đến phiên bản GC, về cơ bản, bạn có ba tùy chọn: sử dụng macro, sửa đổi các móc malloc và ghi đè malloc () của glibc () với libgc's malloc (). Cách tiếp cận đầu tiên là cách dễ nhất; Bạn chỉ cần chèn một cái gì đó như:

#define malloc(x) GC_malloc(x)
#define calloc(n,x) GC_malloc((n)*(x))
#define realloc(p,x) GC_realloc((p),(x))
#define free(x) (x) = NULL

thích hợp bao gồm các tập tin. Mã này chỉ thay thế các cuộc gọi rõ ràng có trong mã của bạn, để lại phân bổ thư viện và khởi động cho các cuộc gọi malloc/miễn phí truyền thống.

Một cách tiếp cận khác là nối malloc và bạn bè với các chức năng của riêng bạn, điều này sẽ gọi các phiên bản GC. Liệt kê 1 cho thấy cách thực hiện và nó có thể được liên kết trực tiếp với một chương trình hiện có. Xem bài viết của tôi về phân bổ bộ nhớ nâng cao của tôi [LJ, tháng 5 năm 2003] để biết chi tiết về các móc này. Với phương pháp này, bất kỳ phân bổ đống nào cũng được đảm bảo đi qua LIBGC, ngay cả khi nó không được thực hiện trực tiếp bởi mã của bạn.

Liệt kê 1. Sử dụng malloc của glibc và móc miễn phí để bật bộ sưu tập rác

/*
 * Using the malloc hooks to substitute GC functions
 * to existing malloc/free.
 * Similar wrapper functions can be written
 * to redirect calloc() and realloc()
 */

#include 
#include 

static void gc_wrapper_init(void);
static void *gc_wrapper_malloc(size_t,const void *);
static void gc_wrapper_free(void*, const void *);

__malloc_ptr_t (*old_malloc_hook)
    __MALLOC_PMT((size_t __size,
                  __const __malloc_ptr_t));
void (*old_free_hook)
    __MALLOC_PMT ((__malloc_ptr_t __ptr,
                   __const __malloc_ptr_t));


/* Override initializing hook from the C library. */
void (*__malloc_initialize_hook)(void) =
                                    gc_wrapper_init;

static void gc_wrapper_init() {
    old_malloc_hook = __malloc_hook;
    old_free_hook = __free_hook;
    __malloc_hook = gc_wrapper_malloc;
    __free_hook = gc_wrapper_free;
}

void *
gc_wrapper_malloc(size_t size, const void *ptr) {
    void *result;
    /* Restore all old hooks */
    __malloc_hook = old_malloc_hook;
    __free_hook = old_free_hook;

    /* Call the Boehm malloc */
    result = GC_malloc(size);


    /* Save underlying hooks */
    old_malloc_hook = __malloc_hook;
    old_free_hook = __free_hook;

    /* Restore our own hooks */
    __malloc_hook = gc_wrapper_malloc;
    __free_hook = gc_wrapper_free;

    return result;
}

static void
gc_wrapper_free(void *ptr, const void *caller)
{
  /* Nothing done! */
}


Là một giải pháp thay thế thứ ba, bạn có thể vượt qua-redirect-malloc để định cấu hình trước khi biên dịch thư viện LIBGC. Làm như vậy cung cấp cho thư viện các chức năng trình bao bọc có cùng tên với gia đình Glibc Malloc tiêu chuẩn. Khi liên kết với mã của bạn, các chức năng trong LIBGC ghi đè lên các tiêu chuẩn, với hiệu ứng ròng tương tự như sử dụng móc malloc. Mặc dù vậy, trong trường hợp này, hiệu ứng là khôn ngoan về hệ thống, vì bất kỳ chương trình nào được liên kết với LIBGC đều bị ảnh hưởng bởi sự thay đổi.configure before compiling the libgc library. Doing so provides the library with wrapper functions that have the same names as the standard glibc malloc family. When linking with your code, the functions in libgc override the standard ones, with a net effect similar to using malloc hooks. In this case, though, the effect is system-wise, as any program linked with libgc is affected by the change.

Đừng mong đợi để ban cho các chương trình lớn với GC dễ dàng sử dụng bất kỳ phương pháp nào trong số này. Một số thủ thuật đơn giản là cần thiết để khai thác các hàm GC và giúp thuật toán thu thập hiệu quả. Ví dụ: tôi đã cố gắng biên dịch lại GAWK (phiên bản 3.1.1) bằng GC và thu được chậm hơn gấp mười lần so với bản gốc. Với một số điều chỉnh, chẳng hạn như đặt từng con trỏ thành NULL sau khi giải phóng nó, thời gian thực hiện được cải thiện đáng kể, ngay cả khi nó vẫn còn lớn hơn thời gian ban đầu.

Bộ sưu tập rác trong các chương trình mới

Nếu bạn đang phát triển một chương trình mới và muốn tận dụng quản lý bộ nhớ tự động, tất cả những gì bạn cần làm là sử dụng họ gc_malloc () thay cho một malloc () một và liên kết với libgc. Các khối bộ nhớ không còn cần thiết chỉ đơn giản là có thể được xử lý bằng cách đặt bất kỳ con trỏ tham chiếu nào cho NULL. Ngoài ra, bạn có thể gọi gc_free () để giải phóng khối ngay lập tức.

Luôn luôn nhớ rằng toàn bộ đống của bạn được người sưu tầm quét định kỳ để tìm kiếm các khối không sử dụng. Nếu đống lớn, hoạt động này có thể mất một thời gian, khiến hiệu suất của chương trình xuống cấp. Hành vi này là tối ưu, bởi vì các khối bộ nhớ lớn thường được đảm bảo không bao giờ chứa các con trỏ, bao gồm các bộ đệm được sử dụng cho tệp hoặc mạng I/O và các chuỗi lớn. Thông thường, con trỏ chỉ được chứa ở các vị trí cố định trong các cấu trúc dữ liệu nhỏ, chẳng hạn như danh sách và các nút cây. Là các ngôn ngữ được gõ mạnh C và C ++, bộ thu có thể quyết định có nên quét một khối bộ nhớ hay không, dựa trên loại con trỏ. Thật không may, điều này là không thể bởi vì nó hoàn toàn hợp pháp trong C để có một tham chiếu con trỏ char một nút danh sách.

Để có hiệu suất tối ưu, lập trình viên nên cố gắng cung cấp một số thông tin loại thời gian chạy cơ bản cho trình thu thập. Để kết thúc này, thư viện BDW có một tập hợp các chức năng thay thế có thể được sử dụng để phân bổ bộ nhớ. GC_MALLOC_ATOMIC () có thể được sử dụng thay cho gc_malloc () để có được các khối bộ nhớ sẽ không bao giờ chứa các con trỏ hợp lệ. Đó là, người thu thập bỏ qua các khối đó khi tìm kiếm các tài liệu tham khảo bộ nhớ trực tiếp. Hơn nữa, những khối đó không cần phải được xóa khi phân bổ. GC_MALLOC_UNCOLLECTABLE () và GC_MALLOC_STUBBORN () cũng có thể được sử dụng để phân bổ các khối cố định và hiếm khi thay đổi. Cuối cùng, có thể cung cấp một số thông tin loại thô bằng cách sử dụng gc_malloc_explicitly_typed () và xây dựng bản đồ khối với gc_make_descriptor (). Xem gc_typed.h trên trang FTP của Tạp chí Linux để biết thêm thông tin [có sẵn tại ftp.linuxjournal.com/pub/lj/listings/issue113/6679.tgz].

Hành vi của người thu thập cũng có thể được người dùng kiểm soát thông qua một số cuộc gọi và biến chức năng. Trong số những cái hữu ích nhất là gc_gcollect (), buộc một bộ sưu tập rác đầy đủ trên toàn bộ đống; Gc_enable_incremental (), cho phép thu thập chế độ gia tăng; và GC_FREE_SPACE_DIVISOR, điều chỉnh sự đánh đổi giữa các bộ sưu tập thường xuyên (giá trị cao, gây ra sự mở rộng đống thấp và chi phí CPU cao) và hiệu quả thời gian (giá trị thấp).

Trạng thái HEAP và thông tin gỡ lỗi có sẵn thông qua một số chức năng, bao gồm gc_get_heap_size (), gc_get_free_bytes (), gc_get_bytes_since_gc (), gc_get_total_bytes () và gc_dump (). Nhiều trong số các tham số và hàm này hoàn toàn không được ghi lại, ngay cả trong chính mã nguồn. Như mọi khi, một biên tập viên giỏi là bạn của bạn.

Hiệu suất và hành vi

Một cách tiếp cận tốt nhất để quản lý bộ nhớ có hiệu quả cho bất kỳ chương trình nào không tồn tại. Đưa ra một ứng dụng cụ thể, giải pháp tối ưu phải được tìm thấy bằng cách thỏa hiệp trên một số yếu tố khác nhau, bao gồm chi phí CPU, mở rộng HEAP, độ trễ phân bổ và cuối cùng nhưng không kém phần quan trọng, khả năng quản lý và mạnh mẽ của mã. Hồ sơ chương trình và thử nghiệm các chiến lược bộ nhớ khác nhau có lẽ là giải pháp tốt nhất để xử lý các vấn đề này.

Tối ưu hóa bộ sưu tập và bộ sưu tập rác

Một điểm tinh tế chống lại GC là nó đòi hỏi phải chăm sóc thêm nếu tối ưu hóa trình biên dịch được bật. Người thu thập có thể giả định sai một con trỏ nhất định đã biến mất đơn giản vì các tham chiếu đến nó đã được tối ưu hóa. Do đó, khối bộ nhớ tương ứng có thể được giải phóng ngay cả khi nó vẫn được sử dụng bởi chương trình, với những hậu quả rõ ràng. Do đó, có thể rất hấp dẫn khi tắt tối ưu hóa trình biên dịch để an toàn, mất một phần hiệu suất đạt được bằng cách sử dụng GC.

Để có ý tưởng về hành vi và hiệu suất của GC so với quản lý bộ nhớ truyền thống, chúng tôi đã thử nghiệm một chương trình thử nghiệm, GCTest, lặp lại việc tạo và phá hủy một danh sách đơn giản. Đơn giản như nó có vẻ, bài kiểm tra làm tăng một số điểm thú vị. Mã nguồn không thực sự mang tính hướng dẫn và quá dài để được in, vì vậy nó có sẵn để tải xuống trên trang web FTP (ftp.linuxjournal.com/pub/lj/listings/issue113/6679.tgz).

GCTest có thể được kiểm soát với một số tùy chọn cho phép bạn thử nghiệm các chiều dài và kích thước nút khác nhau, cũng như để thay đổi các tham số làm việc, cho phép và vô hiệu hóa các tính năng cụ thể của thư viện GC. Trước khi chúng tôi nhận xét về kết quả mà chúng tôi thu được với công cụ thử nghiệm này, điều quan trọng là phải chỉ ra rằng chúng đã thu được trong một môi trường nhân tạo và không quá thực tế. Vì vậy, một lần nữa, bạn được mời để tự mình kiểm tra thư viện GC và đánh giá nó cho mã của riêng bạn. Lấy các tham số được trình bày ở đây là chỉ số có thể của sự phù hợp của thư viện đối với một ứng dụng cụ thể. can be controlled with a number of options that allow you to experiment with different list lengths and node sizes, as well as to change working parameters—enabling and disabling specific features of the GC library. Before we comment on the results we obtained with this test tool, it is important to point out that they were obtained in an artificial and not excessively realistic environment. So, again, you are invited to test the GC library yourself and evaluate it for your own code. Take the parameters presented here as possible indicators of the library suitability to a particular application.

Các phép đo chúng tôi thu thập là thời gian thực hiện tổng thể, tải CPU; Mở rộng Heap, bao nhiêu bộ nhớ được hệ thống yêu cầu liên quan đến số lượng thực sự được phân bổ bởi chương trình; và phân bổ độ trễ trung bình và độ lệch chuẩn, mất bao lâu để phân bổ một khối bộ nhớ duy nhất và thời gian này thay đổi bao nhiêu giữa các phân bổ khác nhau. Ý nghĩa của tham số đầu tiên là khá rõ ràng và không cần giải thích thêm. Mở rộng Heap là thước đo số lượng bộ nhớ được phân bổ bị phân mảnh và số lượng bộ nhớ bổ sung được thư viện yêu cầu từ hệ điều hành. Như chúng ta sẽ thấy, thư viện có thể phân bổ khối 1MB mười lần, giải phóng nó sau mỗi lần phân bổ và yêu cầu tổng cộng 10 MB của hệ thống, như thể bộ nhớ tự do không được đưa trở lại danh sách miễn phí. Mặc dù điều này đôi khi là hành vi mong muốn, cần thiết để tối ưu hóa chiến lược phân bổ, nhưng nó có thể gây phiền nhiễu trên các hệ thống với sự hạn chế của RAM. Nó có thể trở thành một nguồn tiếp theo của chi phí CPU nếu không gian hoán đổi. Cuối cùng, độ trễ phân bổ rất quan trọng đối với các ứng dụng thời gian thực, cần thời gian phân bổ dài nhất để bị ràng buộc. Các trường hợp điển hình là các ứng dụng phát lại đa phương tiện và các hệ thống nhúng chuyên dụng cần phản ứng với các sự kiện bên ngoài trong một khoảng thời gian dự đoán.

Một số nhận xét về kết quả

Hộp thử nghiệm A của chúng tôi là hệ thống Pentium 4 2.53GHz, với 1GB RAM chạy Gentoo Linux (tất cả mã được tối ưu hóa cho kiến ​​trúc CPU) và Glibc 2.3, có thuật toán quản lý bộ nhớ được cải thiện so với GLIBC 2.2. Hộp thử B là máy tính xách tay K6-II 400MHz với RAM 128 MB, chạy Linux Slackware với Glibc 2.2.

Thử nghiệm đầu tiên của chúng tôi bao gồm phân bổ một danh sách 150.000 nút, 16 byte mỗi cái, 30 lần. Trên mỗi vòng lặp, danh sách được phân bổ đã bị phá hủy, nghĩa là miễn phí () ed trong trường hợp quản lý truyền thống, không liên kết trong trường hợp GC. Các lệnh kiểm tra là:

gctest -tu -s 4 -n 150000 -l 30

và:

gctest -gu -s 4 -n 150000 -l 30

Thời gian thực hiện tổng thể, trên hộp A, là 3,80 giây với quản lý truyền thống và 2,43 giây với GC, cải thiện khoảng 35%. Bài kiểm tra tương tự được thực hiện trên hộp B cho thấy sự cải thiện thậm chí còn lớn hơn, khoảng 40%. Thử nghiệm đầu tiên này cho thấy, trái với niềm tin phổ biến, GC thực sự có thể khá nhanh hơn malloc/miễn phí. Mở rộng Heap khá hạn chế và số tiền khoảng 2 trong cả hai trường hợp. Điều đáng ngạc nhiên hơn nữa là độ trễ phân bổ là giống nhau 6.7 micro giây với độ lệch lớn hơn một chút cho trường hợp GC. Cũng thú vị là bằng cách gọi gc_gcollect () ở mỗi vòng (tùy chọn -g), thời gian thực hiện tổng thể giảm 0,1 giây. Kết quả này là trái ngược, bởi vì chúng tôi có thêm một cuộc gọi chức năng trong vòng lặp.

Bây giờ, chúng ta hãy xem điều gì sẽ xảy ra nếu chúng ta quên phá hủy danh sách ở cuối mỗi vòng lặp. Trong trường hợp quản lý truyền thống, thử nghiệm thực hiện nhanh hơn, 2,58 so với 3,80 giây, nhưng mở rộng Heap Heap là 140MB, gấp đôi bộ nhớ được phân bổ tổng thể. Trong trường hợp GC, thử nghiệm hủy bỏ do cạn kiệt bộ nhớ trừ khi chúng tôi kêu gọi một bộ sưu tập rõ ràng (-g) ở cuối mỗi vòng lặp. Bằng cách làm như vậy, chúng tôi có được thời gian thực hiện thấp nhất, 2,32 giây. Điều này có lẽ khá xa so với những gì chúng ta có thể tưởng tượng ra một tiên nghiệm, đó là lý do tại sao thử nghiệm thực tế là quan trọng để tìm các giải pháp tối ưu.

Thử nghiệm tương tự cũng đã được thực hiện trên hộp B, nhưng với phân phối Slackware không được tối ưu hóa và GLIBC 2.2. Điều thú vị là mặc dù sự cải thiện của GC so với malloc/miễn phí vẫn còn khoảng 40%, nhưng bài kiểm tra đã chạy nhanh hơn 27% dưới Gentoo.

Thử nghiệm thứ hai chúng tôi thực hiện cho thấy một số hạn chế của thư viện GC. Các điều kiện thử nghiệm thực sự khá cực đoan: chúng tôi đã cố gắng phân bổ năm danh sách, mỗi danh sách có 1.500.000 nút, với mỗi nút dài 16 byte. Mặc dù phiên bản malloc/miễn phí chạy chính xác, phiên bản GC không hoàn thành bài kiểm tra vì kiệt sức bộ nhớ. Vấn đề có lẽ là do số lượng lớn các khối được phân bổ liên tiếp.

Thử nghiệm thứ ba đã sử dụng các nút lớn hơn, mỗi nút 140 byte và chiều dài danh sách ngắn hơn, 150.000 nút. Chúng tôi đã chạy:

gctest -tu -s 128 -n 150000 -l 5

và:

gctest -gu -s 128 -n 150000 -l 5

Thời gian thực hiện tổng thể, trên hộp A, là 3,80 giây với quản lý truyền thống và 2,43 giây với GC, cải thiện khoảng 35%. Bài kiểm tra tương tự được thực hiện trên hộp B cho thấy sự cải thiện thậm chí còn lớn hơn, khoảng 40%. Thử nghiệm đầu tiên này cho thấy, trái với niềm tin phổ biến, GC thực sự có thể khá nhanh hơn malloc/miễn phí. Mở rộng Heap khá hạn chế và số tiền khoảng 2 trong cả hai trường hợp. Điều đáng ngạc nhiên hơn nữa là độ trễ phân bổ là giống nhau 6.7 micro giây với độ lệch lớn hơn một chút cho trường hợp GC. Cũng thú vị là bằng cách gọi gc_gcollect () ở mỗi vòng (tùy chọn -g), thời gian thực hiện tổng thể giảm 0,1 giây. Kết quả này là trái ngược, bởi vì chúng tôi có thêm một cuộc gọi chức năng trong vòng lặp.

gctest -tuc -s 128 -n 150000 -l 5

Thử nghiệm này mang lại thời gian thực hiện 0,88 giây và mang lại sự cải thiện GC lên 32%. Mở rộng Heap lớn hơn trong trường hợp GC, với giá trị là 1,7 so với 1.0. Độ trễ phân bổ thực tế là giống nhau cho cả quản lý truyền thống và GC, mặc dù một biến thể độ trễ lớn hơn đã được trải nghiệm ở sau này. Kích hoạt bộ sưu tập gia tăng (tùy chọn -i) không làm giảm biến thể, mặc dù việc giới thiệu các cuộc gọi đến gc_free () (-f tùy chọn) để giải phóng rõ ràng các nút danh sách thực sự đã mang lại kết quả tốt hơn so với trường hợp malloc/miễn phí, vào cả thời gian thực hiện và độ trễ. Tuy nhiên, trong trường hợp này, chúng tôi không sử dụng nghiêm ngặt cách tiếp cận thu gom rác thực sự.

Kiểm tra các khối bộ nhớ lớn hơn làm cho sự khác biệt giữa quản lý bộ nhớ truyền thống và GC khá đáng chú ý. Trên các nút 4KB, GC hoạt động khá kém so với malloc/miễn phí vào thời gian thực hiện, 0,85 so với 2 giây; Mở rộng đống, 2,75 so với 1; và độ trễ, 0,7 mili giây so với 1,6 micro giây. Khi so sánh với hiệu suất calloc/miễn phí, thời gian thực hiện của GC vẫn khá cạnh tranh (nhanh hơn 40%). Nhưng, các vấn đề liên quan đến đống và độ trễ vẫn còn.

Sự kết luận

Các kỹ thuật GC thường được bao quanh bởi các huyền thoại và truyền thuyết. Trong bài viết này, chúng tôi đã chỉ ra rằng GC thực sự có thể hoạt động tốt hơn malloc/miễn phí. Tuy nhiên, những lợi thế này không đến miễn phí và việc sử dụng chính xác thư viện đòi hỏi kiến ​​thức tối thiểu về các cơ chế nội bộ của nó.

Không có phán quyết cuối cùng về sự phù hợp của GC cho các chương trình C. Hiện tại, thư viện BDW có thể là một công cụ nữa trong hộp của bạn, để được xem xét nghiêm túc vào lần tới khi bạn đối phó với một phần mềm phức tạp. Một số dự án nguồn mở, như Dự án Mono và thời gian chạy GNU GCJ Java, đã sử dụng nó trong một thời gian.

Ưu và nhược điểm

Trước khi quyết định rằng GC là dành cho WIMP và lập trình viên C khó khăn sẽ không bao giờ cần nó, có thể có lợi khi xem xét các lợi thế thực tế mà GC có thể cung cấp đối với các chương trình quản lý bộ nhớ C/C ++ truyền thống. Như Ellis và Stroustrup nói trong hướng dẫn tham khảo C ++ được chú thích, các lập trình viên của C C nghĩ rằng quản lý bộ nhớ là quá quan trọng để được để lại cho máy tính. Các lập trình viên LISP nghĩ rằng quản lý bộ nhớ là quá quan trọng để được để lại cho người dùng.The Annotated C++ Reference Manual, “C programmers think memory management is too important to be left to the computer. LISP programmers think memory management is too important to be left to the user.”

Các vấn đề liên quan đến bộ nhớ đó chứa một số lỗi ngấm ngầm và hoang dã nhất mà một lập trình viên C có thể gặp phải là một thực tế nổi tiếng. Ngay cả một lập trình viên có kinh nghiệm cũng có thể gặp khó khăn trong việc theo dõi các lỗi do truy cập không hợp lệ, ghi tràn, truy cập vào bộ nhớ chết, rò rỉ bộ nhớ và những thứ tương tự. Hơn nữa, từ quan điểm thiết kế phần mềm, nhu cầu ngăn chặn các tình huống như vậy thường dẫn các nhà thiết kế đến các giao diện ô uế giữa các mô -đun cần được tách rời. Nghĩ về các chức năng trả về một khối bộ nhớ được phân bổ động, sau đó phải được giải phóng bởi người gọi hoặc con trỏ tới bộ đệm tĩnh bên trong, làm hỏng các chủ đề an toàn trong số một số mô -đun. Mỗi mô -đun phải chỉ giải phóng các khu vực như vậy nếu không có mô -đun nào khác hoặc sẽ sử dụng nó, dẫn đến một khớp nối chặt chẽ hơn giữa các mô -đun. Vấn đề này thường được giải quyết bằng cách sử dụng đếm tham chiếu, trong đó một bộ đếm nội bộ được giữ cho mỗi người dùng hoạt động của khu vực; nhiều bản sao, trong đó một bản sao của khu vực được giữ cho mỗi mô -đun; Hoặc, đối với những người ưa thích C ++, con trỏ thông minh.

GC là một cách hiệu quả để xử lý các vấn đề liên quan đến bộ nhớ trong C, làm giảm bớt gánh nặng kế toán bộ nhớ. Mỗi khối bộ nhớ đều biết khi nào nó được sử dụng, thực sự, động cơ GC biết và khối tự động biến mất khi nó không được tham chiếu nữa. GC loại bỏ một tiên nghiệm tất cả các vấn đề giải phóng sớm và rò rỉ bộ nhớ.

GC có một số nhược điểm, tất nhiên, điều khó chịu nhất là cảm giác mất kiểm soát làm ảnh hưởng đến các lập trình viên C. Một cách cụ thể hơn, các nhược điểm xuất phát từ việc quản lý tài nguyên tự động, chuyển thành:

  • Không biết khi nào một khối bộ nhớ không sử dụng thực sự được giải phóng và nếu nó sẽ xảy ra. Điều này có thêm hậu quả khi khối bộ nhớ là đối tượng C ++, có chất phá hủy được gọi vào một thời điểm không xác định.

  • Không thể xác định trước mức độ phân bổ (độ trễ phân bổ và jitter), thường là một vấn đề cho các hệ thống thời gian thực. Tuy nhiên, phải nói rằng malloc truyền thống () cũng đưa ra vấn đề này đôi khi. GC gia tăng có thể giúp giảm bớt vấn đề và cung cấp các ràng buộc hạn chế đối với thời gian phân bổ.

  • Thời gian thực hiện lâu hơn do chi phí xử lý GC. Điều này thường không đúng, vì đôi khi chi phí liên quan đến truyền thống miễn phí () s bằng hoặc thậm chí lớn hơn GC. Theo nghĩa này, chỉ những người phân bổ lưu trữ được viết riêng cho một chương trình nhất định mới có thể nhanh hơn. Mặc dù vậy, các lập trình viên thường viết các hệ thống quản lý bộ nhớ của riêng họ mà không có hoạt động định hình sơ bộ, đôi khi dẫn đến các tác động tiêu cực đến hiệu suất.

  • Phân mảnh bộ nhớ cao hơn (mở rộng HEAP), gây ra bởi các đối tượng không sử dụng không được giải phóng bởi động cơ GC khi nhiều bộ nhớ được phân bổ bởi chương trình. Nó khá phổ biến để có một đống lớn hơn nhiều so với lượng bộ nhớ trong sử dụng thực tế tại một thời điểm nhất định. Thậm chí tệ hơn, kích thước heap đôi khi tỷ lệ thuận với tổng số lượng phân bổ được thực hiện kể từ khi chương trình bắt đầu. Đây có thể là một vấn đề nghiêm trọng trên các hệ thống có sẵn RAM khan hiếm, trong đó các bộ sưu tập thường xuyên và các cuộc gọi miễn phí rõ ràng () có thể là cần thiết để hạn chế phân mảnh.

  • Giảm địa phương của tài liệu tham khảo, do giai đoạn phát hiện rác phải đi qua toàn bộ không gian đống địa chỉ để tìm kiếm các khối không sử dụng. Điều này dẫn đến bộ nhớ cache và bộ nhớ ảo bỏ lỡ thường xuyên hơn là xảy ra với các phân bổ truyền thống. Thuật toán GC thế hệ giới hạn mức độ nghiêm trọng của vấn đề này.

Những người đam mê GC cho rằng những vấn đề này không liên quan đến nhau, xem xét thiết kế sạch hơn và sự mạnh mẽ hơn mà GC cung cấp. Ngay cả khi những lợi thế này rất khó để được đánh giá cụ thể, đôi khi chúng cung cấp sự đánh đổi thuận tiện giữa khả năng quản lý mã và mất tài nguyên.

Cuối cùng, thư viện BDW cũng có thể được sử dụng thành thạo như một máy dò rò rỉ; Nhóm Mozilla sử dụng nó theo cách này, ví dụ. Để biết thêm thông tin, hãy xem các tài liệu bao gồm.

Tài nguyên

Trang chủ Boehm-Demers-Weiser tại www.hpl.hp.com/personal/hans_boehm/gc cung cấp mã nguồn cho thư viện và rất nhiều tài liệu và liên kết để biết thêm thông tin về GC. Điểm khởi đầu tốt là Hans Boehm và David Chase, một đề xuất cho việc tổng hợp collector-collector-safe c, 1992, có sẵn tại www.hpl.hp.com/personal/hans_boehm/gc/papers/boecha.ps.gz; Paul Wilson, Kỹ thuật thu thập rác của Uniprocessor, Đại học Texas, 1992, có sẵn tại ftp.cs.utexas.edu/pub/garbage/gcsurvey.ps; và Benjamin Zorn, Hồi Chi phí đo lường của bộ sưu tập rác bảo thủ, Báo cáo công nghệ, Đại học Colorado tại Boulder, 1992.

Gianluca Insolvibile là một người đam mê Linux kể từ kernel 0,99PL4. Ông hiện giao dịch với mạng và nghiên cứu video và phát triển kỹ thuật số. Anh ta có thể đạt được tại [Email & NBSP; được bảo vệ].[email protected].

Bộ sưu tập rác được thực hiện như thế nào trong C?

Bộ sưu tập rác (GC) là một cơ chế cung cấp khai hoang bộ nhớ tự động cho các khối bộ nhớ không sử dụng. Các lập trình viên tự động phân bổ bộ nhớ, nhưng khi một khối không còn cần thiết, họ không phải trả lại cho hệ thống một cách rõ ràng với một cuộc gọi miễn phí ().provides automatic memory reclamation for unused memory blocks. Programmers dynamically allocate memory, but when a block is no longer needed, they do not have to return it to the system explicitly with a free() call.

C 11 có bộ sưu tập rác không?

Thu thập rác (tự động tái chế các vùng bộ nhớ không được giới thiệu) là tùy chọn trong C ++; Đó là, một người thu gom rác không phải là một phần bắt buộc của việc thực hiện. Tuy nhiên, C ++ 11 cung cấp một định nghĩa về những gì GC có thể làm nếu được sử dụng và ABI (Giao diện nhị phân ứng dụng) để giúp kiểm soát các hành động của nó. that is, a garbage collector is not a compulsory part of an implementation. However, C++11 provides a definition of what a GC can do if one is used and an ABI (Application Binary Interface) to help control its actions.

Những ngôn ngữ nào có bộ sưu tập rác?

Bộ sưu tập rác (GC) là một tính năng phục hồi bộ nhớ được tích hợp trong các ngôn ngữ lập trình như C# và Java.C# and Java.

C Plus Plus có bộ sưu tập rác không?

Một chương trình C ++ có thể chứa cả quản lý bộ nhớ thủ công và thu gom rác diễn ra trong cùng một chương trình.Theo nhu cầu, có thể sử dụng con trỏ bình thường hoặc con trỏ thu gom rác cụ thể.Do đó, để tổng hợp, bộ sưu tập rác là một phương pháp ngược lại với quản lý bộ nhớ thủ công.. According to the need, either the normal pointer or the specific garbage collector pointer can be used. Thus, to sum up, garbage collection is a method opposite to manual memory management.