Tại sao thu gom rác nếu con trỏ thông minh ở đó


67

Những ngày này, rất nhiều ngôn ngữ là rác được thu thập. Nó thậm chí còn có sẵn cho C ++ bởi các bên thứ ba. Nhưng C ++ có RAII và con trỏ thông minh. Vậy quan điểm của việc sử dụng bộ sưu tập rác là gì? Là nó làm một cái gì đó thêm?

Và trong các ngôn ngữ khác như C #, nếu tất cả các tham chiếu được coi là con trỏ thông minh (giữ RAII sang một bên), theo đặc điểm kỹ thuật và bằng cách thực hiện, liệu có còn cần người thu gom rác không? Nếu không, tại sao điều này không phải như vậy?


1
Một điều tôi hiểu sau khi đặt câu hỏi này - Con trỏ thông minh cần RAII để quản lý việc phân bổ tự động .
Gul Sơn

8
Con trỏ thông minh có nghĩa là sử dụng RAII cho GC;)
Dario

Heh, c # nên có một tùy chọn để xử lý tất cả "bộ sưu tập rác" với RAII. Tham chiếu tròn có thể được phát hiện khi tắt ứng dụng, tất cả những gì chúng ta cần là xem phân bổ nào vẫn còn trong bộ nhớ sau khi lớp Program.cs đã được giải quyết. Sau đó, tài liệu tham khảo tròn có thể được thay thế bằng một số loại tài liệu tham khảo trong tuần.
AareP

Câu trả lời:


67

Vì vậy, quan điểm của việc sử dụng bộ sưu tập rác là gì?

Tôi giả sử bạn có nghĩa là tham chiếu đếm con trỏ thông minh và tôi sẽ lưu ý rằng chúng là một hình thức thu gom rác (thô sơ) vì vậy tôi sẽ trả lời câu hỏi "lợi thế của các hình thức thu gom rác khác so với con trỏ thông minh được tham chiếu là gì" thay thế.

  • Độ chính xác . Tham chiếu đếm chu kỳ rò rỉ một mình vì vậy các con trỏ thông minh đếm tham chiếu sẽ nói chung rò rỉ bộ nhớ trừ khi các kỹ thuật khác được thêm vào chu kỳ bắt. Khi các kỹ thuật đó được thêm vào, lợi ích của tính đơn giản tham chiếu đã biến mất. Ngoài ra, lưu ý rằng việc đếm và theo dõi các tham chiếu dựa trên phạm vi thu thập các giá trị tại các thời điểm khác nhau, đôi khi việc đếm tham chiếu thu thập sớm hơn và đôi khi theo dõi các GC thu thập trước đó.

  • Thông lượng . Con trỏ thông minh là một trong những hình thức thu gom rác kém hiệu quả nhất, đặc biệt là trong bối cảnh các ứng dụng đa luồng khi số lượng tham chiếu bị va chạm nguyên tử. Có các kỹ thuật đếm tham chiếu nâng cao được thiết kế để giảm bớt điều này nhưng theo dõi các GC vẫn là thuật toán được lựa chọn trong môi trường sản xuất.

  • Độ trễ . Việc triển khai con trỏ thông minh điển hình cho phép các tàu khu trục xuất hiện, dẫn đến thời gian tạm dừng không giới hạn. Các hình thức thu gom rác khác gia tăng hơn nhiều và thậm chí có thể là thời gian thực, ví dụ như máy chạy bộ của Baker.


23
Không thể tin câu trả lời này đã kết thúc câu trả lời hàng đầu. Nó cho thấy sự thiếu hiểu biết hoàn toàn về con trỏ thông minh C ++ và đưa ra những tuyên bố không đồng bộ với thực tế đến mức đơn giản là vô lý. Đầu tiên, con trỏ thông minh trong một đoạn mã C ++ được thiết kế tốt sẽ chiếm ưu thế nhất là con trỏ duy nhất, không phải con trỏ chia sẻ. en.cppreference.com/w/cpp/memory/unique_ptr Và thứ hai, tôi không thể tin rằng bạn thực sự khẳng định lợi thế 'hiệu suất' và lợi thế thời gian thực của việc thu gom rác không xác định so với con trỏ thông minh.
dùng1703394

4
@ user1703394 Dường như người trả lời đã chia sẻ các gợi ý về con trỏ (đúng hay sai, tôi không chắc chắn những gì OP đề xuất), ít hiệu quả hơn so với việc thu gom rác không xác định.
Nathan Cooper

8
Đây là tất cả các đối số người rơm và chỉ có giá trị nếu bạn hoàn toàn bỏ qua câu hỏi thực tế hoặc bỏ qua các mô hình sử dụng thực tế của các loại con trỏ thông minh khác nhau. Câu hỏi là về con trỏ thông minh. Có shared_ptr là một con trỏ thông minh và có shared_ptr là con trỏ thông minh đắt nhất, nhưng không, không có đối số thực tế nào cho việc sử dụng phổ biến của chúng ở bất cứ đâu gần với bất kỳ đối số hiệu suất nào gần với liên quan. Nghiêm túc, câu trả lời này nên được chuyển sang một câu hỏi về đếm tham chiếu. Tôi là một câu trả lời tham khảo kém cho một câu hỏi con trỏ thông minh tốt.
user1703394 9/2/2016

4
"Con trỏ thông minh không phải là một khái niệm rộng hơn", Nghiêm túc chứ? Bạn không biết bao nhiêu tuyên bố đó làm suy yếu tất cả các đối số có thể hợp lệ bạn đã thực hiện cho đến nay. Có thể có một cái nhìn về quyền sở hữu và di chuyển ngữ nghĩa của Rust: koerbitz.me/posts/, Thật dễ dàng cho những người có kinh nghiệm lịch sử với C ++ để bỏ lỡ thực tế rằng C ++ 11 / C ++ 14 với mô hình bộ nhớ của nó, được cải thiện thông minh con trỏ và di chuyển ngữ nghĩa là một con thú hoàn toàn khác với người tiền nhiệm của họ. Hãy xem Rust làm điều đó như thế nào, nó sạch hơn C ++ và cung cấp phối cảnh mới mẻ.
user1703394

6
@ user1703394: "Tạm dừng không giới hạn do các hàm hủy là một thuộc tính không may của RAII đang được sử dụng cho các tài nguyên không có bộ nhớ". Không, điều này không có gì để làm với các tài nguyên không có bộ nhớ.
Jon Harrop

63

Vì không ai nhìn nó từ góc độ này, tôi sẽ viết lại câu hỏi của bạn: tại sao lại đưa thứ gì đó vào ngôn ngữ nếu bạn có thể làm điều đó trong thư viện? Bỏ qua việc thực hiện cụ thể và chi tiết cú pháp, GC / con trỏ thông minh về cơ bản là một trường hợp đặc biệt của câu hỏi đó. Tại sao định nghĩa trình thu gom rác bằng ngôn ngữ nếu bạn có thể triển khai nó trong thư viện?

Có một vài câu trả lời cho câu hỏi đó. Điều quan trọng nhất đầu tiên:

  1. Bạn đảm bảo rằng tất cả các mã có thể sử dụng nó để tương tác. Tôi nghĩ, đây lý do lớn khiến việc sử dụng lại mã và chia sẻ mã không thực sự phát huy cho đến khi Java / C # / Python / Ruby. Các thư viện cần giao tiếp và ngôn ngữ chia sẻ đáng tin cậy duy nhất họ có là những gì trong chính ngôn ngữ đó (và, ở một mức độ nào đó, thư viện chuẩn của nó). Nếu bạn đã từng cố gắng sử dụng lại các thư viện trong C ++, thì có khả năng bạn đã trải qua nỗi đau khủng khiếp mà không có ngữ nghĩa bộ nhớ tiêu chuẩn nào gây ra. Tôi muốn truyền một cấu trúc cho một số lib. Tôi có vượt qua một tài liệu tham khảo? Con trỏ? scoped_ptr?smart_ptr? Tôi có thông qua quyền sở hữu hay không? Có cách nào để chỉ ra điều đó? Nếu lib cần phân bổ thì sao? Tôi có phải cung cấp cho nó một phân bổ? Bằng cách không biến quản lý bộ nhớ thành ngôn ngữ, C ++ buộc mỗi cặp thư viện phải đàm phán chiến lược cụ thể của riêng họ ở đây và thật khó để khiến tất cả đồng ý. GC làm cho nó không thành vấn đề.

  2. Bạn có thể thiết kế cú pháp xung quanh nó. Vì C ++ không tự đóng gói quản lý bộ nhớ, nên nó phải cung cấp một loạt các cú pháp cú pháp để cho phép mã cấp độ người dùng thể hiện tất cả các chi tiết. Bạn có con trỏ, tài liệu tham khảo, consttoán tử hội thảo, toán tử gián tiếp, địa chỉ, v.v. Nếu bạn cuộn quản lý bộ nhớ vào ngôn ngữ, cú pháp có thể được thiết kế xung quanh đó. Tất cả các toán tử đó biến mất và ngôn ngữ trở nên sạch sẽ và đơn giản hơn.

  3. Bạn nhận được lợi tức đầu tư cao. Giá trị mà bất kỳ đoạn mã nào tạo ra được nhân với số người sử dụng nó. Điều này có nghĩa là bạn càng có nhiều người dùng, bạn càng có đủ khả năng chi tiêu cho một phần mềm. Khi bạn chuyển một tính năng sang ngôn ngữ, tất cả người dùng ngôn ngữ sẽ sử dụng nó. Điều này có nghĩa là bạn có thể phân bổ nhiều nỗ lực hơn nó cho một thư viện chỉ được sử dụng bởi một tập hợp con của những người dùng đó. Đây là lý do tại sao các ngôn ngữ như Java và C # hoàn toàn có máy ảo hạng nhất và bộ thu gom rác chất lượng cao tuyệt vời: chi phí phát triển chúng được khấu hao trên hàng triệu người dùng.


Câu trả lời tuyệt vời! Giá như tôi có thể nâng cao hơn một lần ...
Dean Harding

10
Điều đáng chú ý là bộ sưu tập rác không thực sự được triển khai bằng chính ngôn ngữ C #, mà trong .NET Framework , cụ thể là Thời gian chạy ngôn ngữ chung (CLR).
Robert Harvey

6
@RobertHarvey: Nó không được thực hiện bởi ngôn ngữ, nhưng nó sẽ không hoạt động nếu không có sự hợp tác của ngôn ngữ. Ví dụ, trình biên dịch phải bao gồm thông tin chỉ định, tại mọi điểm trong mã, vị trí của mỗi thanh ghi hoặc phần bù khung ngăn xếp giữ tham chiếu đến một đối tượng không được đánh dấu. Đó là một trường hợp không có ngoại lệ tuyệt đối - bất kể điều gì bất biến , không thể được duy trì nếu không có sự hỗ trợ về ngôn ngữ.
supercat

Một lợi thế lớn của việc có GC hỗ trợ ngôn ngữ và khung yêu cầu là nó đảm bảo rằng không có tài liệu tham khảo nào tồn tại cho bộ nhớ có thể được phân bổ cho một số mục đích khác. Nếu một cuộc gọi Disposevào một đối tượng đóng gói một bitmap, thì bất kỳ tham chiếu nào đến đối tượng đó sẽ là một tham chiếu đến một đối tượng bitmap bị loại bỏ. Nếu đối tượng bị xóa sớm trong khi mã khác vẫn mong đợi sử dụng nó, lớp bitmap có thể đảm bảo rằng mã khác sẽ thất bại theo kiểu có thể dự đoán được . Ngược lại, sử dụng tham chiếu đến bộ nhớ đã giải phóng là Hành vi không xác định.
supercat

34

Về cơ bản, bộ sưu tập rác chỉ có nghĩa là các đối tượng được phân bổ của bạn sẽ tự động được giải phóng tại một số điểm sau khi chúng không thể truy cập được nữa.

Chính xác hơn, chúng được phát hành khi chúng không thể truy cập được vào chương trình, vì các đối tượng được tham chiếu theo vòng tròn sẽ không bao giờ được phát hành theo cách khác.

Con trỏ thông minh chỉ đề cập đến bất kỳ cấu trúc nào hoạt động như một con trỏ bình thường nhưng có một số chức năng bổ sung kèm theo. Chúng bao gồm nhưng không giới hạn ở việc phân bổ, mà còn sao chép khi viết, kiểm tra ràng buộc, ...

Bây giờ, như bạn đã nói, con trỏ thông minh có thể được sử dụng để thực hiện một hình thức thu gom rác.

Nhưng đoàn tàu tư tưởng đi theo con đường sau:

  1. Bộ sưu tập rác là một thứ tuyệt vời để có, vì nó tiện lợi và tôi phải chăm sóc ít thứ hơn
  2. Do đó: Tôi muốn thu gom rác bằng ngôn ngữ của mình
  3. Bây giờ, làm thế nào có thể đưa GC vào ngôn ngữ của tôi?

Tất nhiên, bạn có thể thiết kế nó như thế này từ đầu. C # được thiết kế để trở thành rác được thu thập, vì vậy chỉ cần newđối tượng của bạn và nó sẽ được phát hành khi các tham chiếu nằm ngoài phạm vi. Làm thế nào điều này được thực hiện là tùy thuộc vào trình biên dịch.

Nhưng trong C ++, không có ý định thu gom rác. Nếu chúng ta phân bổ một số con trỏ int* p = new int;và nó nằm ngoài phạm vi, pnó sẽ bị xóa khỏi ngăn xếp, nhưng không ai quan tâm đến bộ nhớ được phân bổ.

Bây giờ điều duy nhất bạn có từ đầu là các hàm hủy xác định . Khi một đối tượng rời khỏi phạm vi mà nó đã được tạo, hàm hủy của nó được gọi. Kết hợp với các mẫu và nạp chồng toán tử, bạn có thể thiết kế một đối tượng trình bao bọc hoạt động giống như một con trỏ, nhưng sử dụng chức năng hủy để dọn sạch các tài nguyên được gắn vào nó (RAII). Bạn gọi đây là một con trỏ thông minh .

Đây là tất cả đặc thù của C ++: Quá tải toán tử, mẫu, hàm hủy, ... Trong tình huống ngôn ngữ cụ thể này, bạn đã phát triển các con trỏ thông minh để cung cấp cho bạn GC mà bạn muốn.

Nhưng nếu bạn thiết kế một ngôn ngữ với GC từ đầu, đây chỉ là một chi tiết triển khai. Bạn chỉ cần nói đối tượng sẽ được dọn sạch và trình biên dịch sẽ làm điều này cho bạn.

Các con trỏ thông minh như trong C ++ có lẽ sẽ không thể có trong các ngôn ngữ như C #, vốn không có sự phá hủy xác định nào cả (C # hoạt động xung quanh điều này bằng cách cung cấp đường cú pháp để gọi một .Dispose()số đối tượng nhất định). Các tài nguyên không được ước tính cuối cùng sẽ được thu hồi bởi GC, nhưng nó không được xác định khi nào chính xác điều này sẽ xảy ra.

Và điều này, đến lượt nó, có thể cho phép GC thực hiện công việc của mình hiệu quả hơn. Được tích hợp sâu vào ngôn ngữ hơn các con trỏ thông minh, được đặt lên trên ngôn ngữ, .NET GC có thể ví dụ trì hoãn các hoạt động của bộ nhớ và thực hiện chúng trong các khối để làm cho chúng rẻ hơn hoặc thậm chí di chuyển bộ nhớ xung quanh để tăng hiệu quả dựa trên tần suất các đối tượng được truy cập.


C # có một hình thức phá hủy xác định thông qua IDisposableusing. Nhưng nó đòi hỏi một chút nỗ lực của lập trình viên, đó là lý do tại sao nó thường chỉ được sử dụng cho các tài nguyên rất khan hiếm như xử lý kết nối cơ sở dữ liệu.
JSB

7
@JSBangs: Chính xác. Giống như C ++ xây dựng các con trỏ thông minh xung quanh RAII để có được GC, C # đi theo một cách khác và xây dựng "các thiết bị thông minh" xung quanh GC để có RAII;) Trên thực tế, thật đáng tiếc khi RAII rất khó trong C # vì nó là ngoại lệ tuyệt vời- xử lý tài nguyên an toàn. Ví dụ, F # thử một IDisposablecú pháp đơn giản hơn bằng cách thay thế thông thường let ident = valuebằng use ident = value...
Dario

@Dario: "C # đi theo một cách khác và xây dựng 'các thiết bị xử lý thông minh' xung quanh GC để có RAII". RAII trong C # usingkhông liên quan gì đến bộ sưu tập rác, nó chỉ gọi một hàm khi một biến nằm ngoài phạm vi giống như các hàm hủy trong C ++.
Jon Harrop

1
@Jon Harrop: Làm ơn cái gì? Tuyên bố bạn trích dẫn là về các hàm hủy C ++ đơn giản, không có bất kỳ tính năng tham chiếu / con trỏ thông minh / bộ sưu tập rác nào có liên quan.
Dario

1
"Về cơ bản, bộ sưu tập rác chỉ có nghĩa là các đối tượng được phân bổ của bạn sẽ tự động được giải phóng khi chúng không được tham chiếu nữa. Chính xác hơn, chúng được phát hành khi chúng không thể truy cập được vào chương trình, vì các đối tượng được tham chiếu theo vòng tròn sẽ không bao giờ được phát hành nữa." ... Chính xác hơn sẽ nói rằng chúng được tự động phát hành tại một số điểm sau đó , không phải khi nào . Lưu ý rằng khi ngụ ý rằng việc khai hoang xảy ra ngay lập tức, khi thực tế việc khai hoang thường xảy ra sau đó.
Todd Lehman

4

Theo tôi, có hai sự khác biệt lớn, giữa bộ sưu tập rác và con trỏ thông minh được sử dụng để quản lý bộ nhớ:

  1. Con trỏ thông minh không thể thu gom rác theo chu kỳ; thu gom rác
  2. Con trỏ thông minh thực hiện tất cả các công việc tại các thời điểm tham chiếu, hội nghị và giao dịch, trên chuỗi ứng dụng; thu gom rác không cần

Điều này có nghĩa là GC sẽ thu gom rác mà con trỏ thông minh sẽ không; nếu bạn đang sử dụng con trỏ thông minh, bạn phải tránh tạo loại rác này hoặc sẵn sàng xử lý thủ công.

Điều thứ hai có nghĩa là cho dù con trỏ thông minh thông minh như thế nào, hoạt động của chúng sẽ làm chậm các luồng làm việc trong chương trình của bạn. Bộ sưu tập rác có thể trì hoãn công việc, và di chuyển nó đến các chủ đề khác; cho phép nó hoạt động tổng thể hiệu quả hơn (thực tế, chi phí thời gian chạy của một GC hiện đại ít hơn một hệ thống malloc / miễn phí bình thường, ngay cả khi không có thêm chi phí cho các con trỏ thông minh), và làm những gì nó vẫn cần làm mà không cần vào cách của các chủ đề ứng dụng.

Bây giờ, lưu ý rằng các con trỏ thông minh, là các cấu trúc lập trình, có thể được sử dụng để thực hiện tất cả các loại điều thú vị khác - xem câu trả lời của Dario - hoàn toàn nằm ngoài phạm vi thu gom rác. Nếu bạn muốn làm những điều đó, bạn sẽ cần con trỏ thông minh.

Tuy nhiên, với mục đích quản lý bộ nhớ, tôi không thấy bất kỳ triển vọng nào về con trỏ thông minh thay thế bộ sưu tập rác. Họ chỉ đơn giản là không giỏi về nó.


6
@Tom: hãy xem câu trả lời của Dario để biết chi tiết về con trỏ thông minh. Đối với lợi thế của con trỏ thông minh - sự phân chia xác định có thể là một lợi thế rất lớn khi được sử dụng để kiểm soát tài nguyên (không chỉ bộ nhớ). Trên thực tế, điều này đã được chứng minh là rất quan trọng đến nỗi Microsoft đã giới thiệu usingkhối này trong các phiên bản tiếp theo của C #. Hơn nữa, hành vi không xác định của các GC có thể bị cấm trong các hệ thống thời gian thực (đó là lý do tại sao các GC không được sử dụng ở đó). Ngoài ra, đừng quên rằng các GC rất phức tạp để có được quyền mà hầu hết thực sự bị rò rỉ bộ nhớ và khá kém hiệu quả (ví dụ như Boehm mật).
Konrad Rudolph

6
Tính không phổ biến của các GC là, tôi nghĩ, một chút cá trích đỏ - có những hệ thống GC phù hợp để sử dụng thời gian thực (như Recycler của IBM), mặc dù những cái bạn thấy trong máy tính để bàn và máy chủ VM không có. Thêm vào đó, sử dụng con trỏ thông minh có nghĩa là sử dụng malloc / miễn phí, và việc triển khai malloc thông thường là không đặc biệt do nhu cầu tìm kiếm danh sách miễn phí. Di chuyển hệ thống GC có nhiều thời gian phân bổ xác định hơn malloc / hệ thống miễn phí, mặc dù tất nhiên ít xác định lần deallocation.
Tom Anderson

3
Về độ phức tạp: có, các GC rất phức tạp, nhưng tôi không biết rằng "hầu hết thực sự bị rò rỉ bộ nhớ và khá kém hiệu quả", và sẽ rất thích nhìn thấy một số bằng chứng khác. Boehm không phải là bằng chứng, bởi vì đó là một triển khai rất nguyên thủy và nó được xây dựng để phục vụ một ngôn ngữ, trong đó, chính xác về cơ bản là không thể do thiếu an toàn kiểu. Đó là một nỗ lực dũng cảm, và nó hoạt động rất ấn tượng, nhưng bạn không thể coi đó là một ví dụ điển hình của GC.
Tom Anderson

8
@Jon: quyết định không nhảm nhí. bugzilla.novell.com/show_orms.cgi?id=621899 hoặc, nói chung hơn: Flyingfrogblog.blogspot.com/2009/01/ Khăn Đây là một tài sản nổi tiếng và là tài sản của tất cả các GC bảo thủ.
Konrad Rudolph

3
"Chi phí thời gian chạy của một GC hiện đại ít hơn một hệ thống malloc / miễn phí bình thường." Cá trích đỏ đây. Điều này chỉ bởi vì malloc truyền thống là một thuật toán kém hiệu quả khủng khiếp. Các bộ phân bổ hiện đại sử dụng nhiều nhóm cho các kích thước khối khác nhau để phân bổ nhanh hơn nhiều, ít bị phân mảnh hơn và vẫn cung cấp cho bạn khả năng phân bổ nhanh.
Mason Wheeler

3

Thuật ngữ thu gom rác ngụ ý có bất kỳ rác để thu thập. Trong C ++, con trỏ thông minh có nhiều hương vị, quan trọng nhất là unique_ptr. Unique_ptr về cơ bản là một cấu trúc sở hữu và phạm vi duy nhất. Trong một đoạn mã được thiết kế tốt, hầu hết các công cụ được phân bổ heap thường nằm sau các con trỏ thông minh unique_ptr và quyền sở hữu các tài nguyên đó sẽ luôn được xác định rõ. Hầu như không có bất kỳ chi phí nào trong unique_ptr và unique_ptr loại bỏ hầu hết các vấn đề quản lý bộ nhớ thủ công mà theo truyền thống đã đưa mọi người đến các ngôn ngữ được quản lý. Giờ đây, khi nhiều lõi chạy đồng thời đang trở nên phổ biến hơn, các nguyên tắc thiết kế hướng mã sử dụng quyền sở hữu duy nhất và được xác định rõ ràng tại bất kỳ thời điểm nào trở nên quan trọng hơn đối với hiệu suất.

Ngay cả trong một chương trình được thiết kế tốt, đặc biệt là trong môi trường đa luồng, không phải mọi thứ đều có thể được thể hiện mà không có cấu trúc dữ liệu được chia sẻ và đối với những cấu trúc dữ liệu thực sự yêu cầu, các luồng cần phải giao tiếp. RAII trong c ++ hoạt động khá tốt đối với các mối quan tâm trọn đời trong một thiết lập luồng đơn, trong một thiết lập đa luồng, thời gian tồn tại của các đối tượng có thể không được xác định hoàn toàn theo thứ bậc. Đối với những tình huống này, việc sử dụng shared_ptr cung cấp một phần lớn của giải pháp. Bạn tạo quyền sở hữu chung cho một tài nguyên và điều này trong C ++ là nơi duy nhất chúng ta thấy rác, nhưng với số lượng nhỏ như vậy, chương trình c ++ được thiết kế phù hợp nên được xem xét nhiều hơn để thực hiện bộ sưu tập 'rác' với chia sẻ ptr so với thu gom rác đầy đủ như thực hiện bằng các ngôn ngữ khác. C ++ đơn giản là không có nhiều 'rác'

Như đã nói bởi những người khác, con trỏ thông minh được tính tham chiếu là một dạng thu gom rác và đối với đó có một vấn đề chính. Ví dụ được sử dụng chủ yếu như nhược điểm của các hình thức thu gom rác tham chiếu là vấn đề với việc tạo ra các cấu trúc dữ liệu mồ côi được kết nối với các con trỏ thông minh với nhau để tạo ra các cụm đối tượng không bị thu thập lẫn nhau. Mặc dù trong một chương trình được thiết kế theo mô hình tính toán của diễn viên, các cấu trúc dữ liệu thường không cho phép các cụm không thể thu được như vậy xuất hiện trong C ++, khi bạn sử dụng phương pháp chia sẻ dữ liệu rộng rãi để lập trình đa luồng, như được sử dụng chủ yếu trong một phần lớn của ngành công nghiệp, những cụm mồ côi này có thể nhanh chóng trở thành hiện thực.

Vì vậy, để tổng hợp tất cả, nếu sử dụng con trỏ dùng chung, bạn có nghĩa là việc sử dụng unique_ptr rộng rãi kết hợp với mô hình tiếp cận tính toán của lập trình cho lập trình đa luồng và sử dụng shared_ptr hạn chế, hơn các hình thức thu gom rác khác không mua cho bạn thêm lợi ích. Tuy nhiên, nếu cách tiếp cận mọi thứ được chia sẻ sẽ khiến bạn kết thúc với shared_ptr ở mọi nơi, thì bạn nên xem xét chuyển đổi mô hình đồng thời hoặc chuyển sang ngôn ngữ được quản lý theo hướng chia sẻ quyền sở hữu rộng hơn và truy cập đồng thời vào cấu trúc dữ liệu.


1
Có nghĩa là Rustkhông cần thu gom rác?
Gul Sơn

1
@Gulshan Rust là một trong số rất ít ngôn ngữ hỗ trợ con trỏ độc đáo an toàn.
CodeInChaos

2

Hầu hết các con trỏ thông minh được thực hiện bằng cách sử dụng đếm tham chiếu. Nghĩa là, mỗi con trỏ thông minh tham chiếu đến một đối tượng sẽ tăng số tham chiếu đối tượng. Khi số đó về 0, đối tượng được giải phóng.

Vấn đề là nếu bạn có tài liệu tham khảo tròn. Nghĩa là, A có tham chiếu đến B, B có tham chiếu đến C và C có tham chiếu đến A. Nếu bạn đang sử dụng con trỏ thông minh, thì để giải phóng bộ nhớ liên quan đến A, B & C, bạn cần phải thủ công có được một "phá vỡ" tham chiếu vòng tròn (ví dụ: sử dụng weak_ptrtrong C ++).

Bộ sưu tập rác (thường) hoạt động khá khác nhau. Hầu hết những người thu gom rác ngày nay đều sử dụng bài kiểm tra khả năng tiếp cận . Đó là, nó xem xét tất cả các tham chiếu trên ngăn xếp và các tham chiếu có thể truy cập toàn cầu và sau đó theo dõi mọi đối tượng mà các tham chiếu đó tham chiếu và các đối tượng mà chúng tham chiếu, v.v. Mọi thứ khác đều là rác.

Theo cách đó, các tham chiếu vòng tròn không còn quan trọng nữa - miễn là không thể truy cập A, B và C , bộ nhớ có thể được lấy lại.

Có những lợi thế khác để thu gom rác "thực sự". Ví dụ, phân bổ bộ nhớ là cực kỳ rẻ: chỉ cần tăng con trỏ đến "kết thúc" của khối bộ nhớ. Giao dịch có chi phí khấu hao không đổi là tốt. Nhưng tất nhiên các ngôn ngữ như C ++ cho phép bạn thực hiện quản lý bộ nhớ theo bất kỳ cách nào bạn muốn, do đó bạn có thể đưa ra một chiến lược phân bổ thậm chí còn nhanh hơn.

Tất nhiên, trong C ++, lượng bộ nhớ được phân bổ heap thường ít hơn một ngôn ngữ nặng tham chiếu như C # /. NET. Nhưng đó không thực sự là vấn đề thu gom rác so với con trỏ thông minh.

Trong mọi trường hợp, vấn đề không phải là cắt và khô là tốt hơn so với vấn đề khác. Họ đều có ưu điểm và nhược điểm.


2

Đó là về hiệu suất . Bộ nhớ unallocating đòi hỏi rất nhiều quản trị. Nếu unallocation chạy trong nền, hiệu suất của quá trình tiền cảnh tăng lên. Thật không may, phân bổ bộ nhớ không thể lười biếng (các đối tượng được phân bổ sẽ được sử dụng tại thời điểm tiếp theo thánh), nhưng việc giải phóng các đối tượng có thể.

Hãy thử trong C ++ (w / o bất kỳ GC) để phân bổ một nhóm lớn các đối tượng, in "xin chào", sau đó xóa chúng. Bạn sẽ ngạc nhiên khi mất bao lâu để các đối tượng miễn phí.

Ngoài ra, GNU libc cung cấp các công cụ hiệu quả hơn để không phân bổ bộ nhớ, xem các chướng ngại vật . Phải chú ý, tôi không có kinh nghiệm với chướng ngại vật, tôi không bao giờ sử dụng chúng.


Về nguyên tắc bạn có một điểm nhưng cần lưu ý rằng đây là một vấn đề có một giải pháp rất đơn giản: sử dụng bộ cấp phát nhóm hoặc bộ cấp phát đối tượng nhỏ để bó các giao dịch. Nhưng điều này phải thừa nhận (hơi) nỗ lực nhiều hơn so với việc chạy GC ở chế độ nền.
Konrad Rudolph

Đúng, chắc chắn, GC thoải mái hơn nhiều. (Đặc biệt đối với người mới bắt đầu: không có vấn đề về quyền sở hữu, thậm chí không có toán tử xóa.)
ern0

3
@ ern0: không. Điểm chung của (tính tham chiếu) con trỏ thông minh là không có vấn đề sở hữu và không có toán tử xóa.
Konrad Rudolph

3
@Jon: trong đó, thành thật mà nói, hầu hết thời gian. Nếu bạn chia sẻ trạng thái đối tượng giữa các luồng khác nhau, bạn sẽ có các vấn đề hoàn toàn khác nhau. Tôi sẽ thừa nhận rằng nhiều người lập trình theo cách đó nhưng đây là hậu quả của sự trừu tượng luồng xấu đã tồn tại cho đến gần đây và đó không phải là cách hay để thực hiện đa luồng.
Konrad Rudolph

1
Giao dịch thường không được thực hiện "trong nền", nhưng tạm dừng tất cả các chủ đề nền trước. Bộ sưu tập rác chế độ hàng loạt nói chung là một chiến thắng hiệu suất, mặc dù tạm dừng các luồng tiền cảnh, bởi vì nó cho phép không gian không sử dụng được hợp nhất. Người ta có thể tách các quy trình thu gom rác và nén đống, nhưng - đặc biệt là trong các khung sử dụng tham chiếu trực tiếp thay vì xử lý - cả hai đều có xu hướng là các quy trình "dừng lại trên thế giới" và thường thực tế nhất để thực hiện chúng cùng với nhau.
supercat

2

Việc thu gom rác có thể hiệu quả hơn - về cơ bản là 'xử lý' chi phí quản lý bộ nhớ và thực hiện tất cả cùng một lúc. Nói chung, điều này sẽ dẫn đến việc CPU ít được sử dụng cho việc phân bổ bộ nhớ, nhưng điều đó có nghĩa là bạn sẽ có một loạt hoạt động phân bổ lớn vào một lúc nào đó. Nếu GC không được thiết kế đúng, điều này có thể hiển thị cho người dùng dưới dạng 'tạm dừng' trong khi GC cố gắng phân bổ lại bộ nhớ. Hầu hết các GC hiện đại rất giỏi trong việc giữ điều này vô hình cho người dùng ngoại trừ trong các điều kiện bất lợi nhất.

Con trỏ thông minh (hoặc bất kỳ sơ đồ đếm tham chiếu nào) có lợi thế là chúng xảy ra chính xác khi bạn mong đợi khi xem mã (con trỏ thông minh nằm ngoài phạm vi, điều sẽ bị xóa). Bạn nhận được một chút bùng nổ của phân bổ ở đây và đó. Nhìn chung, bạn có thể sử dụng nhiều thời gian CPU hơn cho việc phân bổ lại, nhưng vì nó trải rộng trên tất cả những điều xảy ra trong chương trình của bạn, nên ít có khả năng (không phân bổ cấu trúc dữ liệu quái vật) để hiển thị cho người dùng của bạn.

Nếu bạn đang làm điều gì đó mà khả năng phản hồi quan trọng, tôi sẽ đề xuất rằng con trỏ / số đếm thông minh cho bạn biết chính xác khi nào mọi thứ đang xảy ra, vì vậy bạn có thể biết trong khi mã hóa những gì có khả năng hiển thị cho người dùng của bạn. Trong cài đặt GC, bạn chỉ có quyền kiểm soát nhanh nhất đối với trình thu gom rác và chỉ cần cố gắng làm việc xung quanh sự việc.

Mặt khác, nếu thông lượng tổng thể là mục tiêu của bạn, một hệ thống dựa trên GC có thể là lựa chọn tốt hơn nhiều, vì nó giảm thiểu các tài nguyên cần thiết để thực hiện quản lý bộ nhớ.

Chu kỳ: Tôi không coi vấn đề của chu kỳ là một vấn đề quan trọng. Trong một hệ thống mà bạn có con trỏ thông minh, bạn có xu hướng hướng đến các cấu trúc dữ liệu không có chu kỳ hoặc đơn giản là bạn cẩn thận về cách bạn từ bỏ những thứ đó. Nếu cần thiết, các đối tượng thủ môn biết cách phá vỡ các chu kỳ trong các đối tượng sở hữu có thể được sử dụng để tự động bảo đảm sự phá hủy thích hợp. Trong một số lĩnh vực lập trình, điều này có thể quan trọng, nhưng đối với hầu hết các công việc hàng ngày, điều đó không liên quan.


1
"Bạn sẽ có một sự bùng nổ lớn của hoạt động phân bổ lại tại một số điểm". Máy chạy bộ của Baker là một ví dụ về công cụ thu gom rác gia tăng đẹp mắt. memorymanloyment.org/glossary/t.html#treadmill
Jon Harrop

1

Hạn chế số một của con trỏ thông minh là chúng không luôn giúp chống lại các tham chiếu vòng tròn. Ví dụ: bạn có đối tượng A lưu trữ một con trỏ thông minh vào đối tượng B và đối tượng B đang lưu trữ một con trỏ thông minh vào đối tượng A. Nếu chúng được đặt lại với nhau mà không đặt lại một trong hai con trỏ mà chúng sẽ không bị hủy.

Điều này xảy ra bởi vì một con trỏ thông minh phải thực hiện một hành động cụ thể sẽ không được xem xét trong kịch bản trên vì cả hai đối tượng đều không thể truy cập được vào chương trình. Bộ sưu tập rác sẽ đối phó - nó sẽ xác định chính xác rằng các đối tượng không thể truy cập được vào chương trình và chúng sẽ được thu thập.


1

Đó là một quang phổ .

Nếu bạn không kiểm soát chặt chẽ về hiệu suất và chuẩn bị đưa vào hoạt động, bạn sẽ kết thúc hội nghị hoặc c, với tất cả trách nhiệm để bạn đưa ra quyết định đúng đắn và tất cả tự do để làm điều đó, nhưng với nó , tất cả tự do để làm hỏng nó:

"Tôi sẽ cho bạn biết phải làm gì, bạn làm điều đó. Hãy tin tôi".

Thu gom rác là đầu kia của quang phổ. Bạn có rất ít quyền kiểm soát, nhưng nó được chăm sóc cho bạn:

"Tôi sẽ nói với bạn những gì tôi muốn, bạn thực hiện nó".

Điều này có rất nhiều lợi thế, chủ yếu là bạn không cần phải đáng tin khi biết chính xác khi nào tài nguyên không còn cần thiết nữa, nhưng (mặc dù một số câu trả lời trôi nổi ở đây) không tốt cho hiệu suất, và khả năng dự đoán của hiệu suất. (Giống như tất cả mọi thứ, nếu bạn được kiểm soát và làm điều gì đó ngu ngốc, bạn có thể có kết quả tồi tệ hơn. Tuy nhiên, để gợi ý rằng biết tại thời điểm biên dịch các điều kiện để có thể giải phóng bộ nhớ, không thể sử dụng làm chiến thắng hiệu suất là ngoài ngây thơ).

RAII, phạm vi, đếm ref, v.v ... đều là những người trợ giúp cho bạn di chuyển xa hơn dọc theo quang phổ đó nhưng nó không phải là tất cả. Tất cả những điều này vẫn yêu cầu sử dụng tích cực. Họ vẫn cho phép và yêu cầu bạn tương tác với quản lý bộ nhớ theo cách mà bộ sưu tập rác không có.


0

Xin nhớ rằng cuối cùng, mọi thứ đều sôi sục theo hướng dẫn thực thi CPU. Theo hiểu biết của tôi, tất cả các CPU cấp tiêu dùng đều có các tập lệnh yêu cầu bạn phải lưu trữ dữ liệu ở một vị trí nhất định trong bộ nhớ và bạn có con trỏ tới dữ liệu đã nói. Đó là tất cả những gì bạn có ở cấp độ cơ bản.

Tất cả mọi thứ trên hết với bộ sưu tập rác, tham chiếu đến dữ liệu có thể đã được di chuyển, nén heap, v.v. đang thực hiện công việc trong các giới hạn được đưa ra bởi mô hình "bộ nhớ với con trỏ địa chỉ" ở trên. Điều tương tự với con trỏ thông minh - bạn VẪN phải làm cho mã chạy trên phần cứng thực tế.

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.