Nhược điểm của quản lý bộ nhớ dựa trên phạm vi


38

Tôi thực sự thích quản lý bộ nhớ dựa trên phạm vi (SBMM), hoặc RAII , vì nó phổ biến hơn (gây nhầm lẫn?) Được đề cập bởi cộng đồng C ++. Theo như tôi biết, ngoại trừ C ++ (và C), ngày nay không có ngôn ngữ chính nào được sử dụng để biến SBMM / RAII thành cơ chế quản lý bộ nhớ chính của họ, và thay vào đó họ thích sử dụng bộ sưu tập rác (GC).

Tôi thấy điều này khá khó hiểu, vì

  1. SBMM làm cho các chương trình mang tính quyết định hơn (bạn có thể biết chính xác khi nào một đối tượng bị phá hủy);
  2. trong các ngôn ngữ sử dụng GC, bạn thường phải thực hiện quản lý tài nguyên thủ công (ví dụ, xem các tệp đóng trong Java), phần nào đánh bại mục đích của GC và cũng dễ bị lỗi;
  3. bộ nhớ heap cũng có thể (rất thanh lịch, imo) bị giới hạn phạm vi (xem std::shared_ptrtrong C ++).

Tại sao SBMM không được sử dụng rộng rãi hơn? Nhược điểm của nó là gì?


1
Một số nhược điểm (đặc biệt liên quan đến tốc độ) được thảo luận trong Wikipedia: en.wikipedia.org/wiki/ Kẻ
Philipp

2
Vấn đề quản lý tài nguyên thủ công của Java là một tác dụng phụ của việc không đảm bảo rằng finalize()phương thức của đối tượng sẽ được gọi trước khi thu gom rác. Trong thực tế, điều này tạo ra cùng một loại vấn đề mà bộ sưu tập rác được cho là phải giải quyết.
Blrfl

7
@Blrfl Vô nghĩa. Quản lý tài nguyên "thủ công" (đối với các tài nguyên không phải là bộ nhớ, rõ ràng) sẽ tốt hơn ngay cả khi "vấn đề" đó không tồn tại, bởi vì GC có thể chạy rất lâu sau khi tài nguyên không được sử dụng hoặc thậm chí không chạy được. Đó không phải là vấn đề đối với bộ nhớ và quản lý bộ nhớ là tất cả những gì bộ sưu tập rác được cho là phải giải quyết.

4
btw. Tôi muốn gọi nó là SBRM vì bạn có thể sử dụng cùng một cơ chế để quản lý tài nguyên nói chung, không chỉ bộ nhớ.
PlasmaHH

Câu trả lời:


27

Hãy bắt đầu bằng cách quy định rằng bộ nhớ là phổ biến (hàng chục, hàng trăm hoặc thậm chí hàng nghìn lần) phổ biến hơn tất cả các tài nguyên khác cộng lại. Mỗi biến, đối tượng, thành viên đối tượng cần một số bộ nhớ được cấp phát cho nó và giải phóng sau này. Đối với mỗi tệp bạn mở, bạn tạo hàng chục đến hàng triệu đối tượng để lưu trữ dữ liệu được rút ra khỏi tệp. Mỗi luồng TCP đi cùng với một số chuỗi byte tạm thời không giới hạn được tạo để ghi vào luồng. Có phải chúng ta trên cùng một trang ở đây? Tuyệt quá.

Để RAII hoạt động (ngay cả khi bạn có con trỏ thông minh đã sẵn sàng cho mọi trường hợp sử dụng dưới ánh mặt trời), bạn cần có quyền sở hữu . Bạn cần phân tích ai nên sở hữu đối tượng này hoặc đối tượng đó, ai không nên và khi nào nên chuyển quyền sở hữu từ A sang B. Chắc chắn, bạn có thể sử dụng quyền sở hữu chung cho mọi thứ , nhưng sau đó bạn sẽ mô phỏng một GC thông qua con trỏ thông minh. Tại thời điểm đó , việc xây dựng GC thành ngôn ngữ trở nên dễ dàng nhanh chóng hơn nhiều .

Bộ sưu tập rác giải phóng bạn khỏi mối quan tâm này đối với tài nguyên, bộ nhớ được sử dụng phổ biến nhất. Chắc chắn, bạn vẫn cần đưa ra quyết định tương tự cho các tài nguyên khác, nhưng những tài nguyên đó ít phổ biến hơn (xem ở trên) và quyền sở hữu phức tạp (ví dụ như được chia sẻ) cũng ít phổ biến hơn. Gánh nặng tinh thần giảm đáng kể.

Bây giờ, bạn đặt tên cho một số nhược điểm để làm cho tất cả các giá trị rác được thu thập. Tuy nhiên, việc tích hợp cả hai loại giá trị an toàn bộ nhớ với RAII vào một ngôn ngữ là vô cùng khó khăn, vì vậy có lẽ tốt hơn là nên số hóa các giao dịch này thông qua các phương tiện khác?

Sự mất đi tính quyết định hóa ra không phải là xấu trong thực tế, bởi vì nó chỉ ảnh hưởng đến tuổi thọ của đối tượng xác định . Như được mô tả trong đoạn tiếp theo, hầu hết các tài nguyên (ngoài bộ nhớ, rất dồi dào và có thể được tái chế khá lười biếng) không bị ràng buộc với tuổi thọ của đối tượng trong các ngôn ngữ này. Có một vài trường hợp sử dụng khác, nhưng chúng rất hiếm trong kinh nghiệm của tôi.

Điểm thứ hai của bạn, quản lý tài nguyên thủ công, hiện được xử lý thông qua một câu lệnh thực hiện dọn dẹp dựa trên phạm vi, nhưng không kết hợp việc này với thời gian sống của đối tượng (do đó không tương tác với an toàn bộ nhớ và bộ nhớ). Đây là usingtrong C #, withtrong Python, try-với tài nguyên trong các phiên bản Java gần đây.


1
Điều này không giải thích tại sao các mô hình không xác định như GC nên vượt trội hơn các mô hình xác định. usingbáo cáo chỉ có thể tại địa phương. Không thể dọn sạch các tài nguyên được giữ trong các biến thành viên theo cách đó.
Philipp

8
@Philipp Quyền sở hữu được chia sẻ cho mọi thứ một GC, chỉ là một người rất nghèo. Nếu bạn lấy "quyền sở hữu chung" để ám chỉ việc đếm ref, vui lòng nói như vậy, nhưng hãy tiếp tục thảo luận về các chu kỳ trong các nhận xét về câu trả lời của amon. Tôi cũng không chắc liệu việc đếm ref có mang tính quyết định theo nghĩa OP quan tâm hay không (các đối tượng được giải phóng càng sớm càng tốt, các chu kỳ giảm giá, nhưng bạn thường không thể biết khi đó là bằng cách xem chương trình). Hơn nữa, việc đếm ref cho mọi thứ đều chậm, chậm hơn nhiều so với một dấu vết hiện đại.

16
usinglà một trò đùa so với RAII, chỉ để bạn biết.
DeadMG

3
@Philipp vui lòng mô tả số liệu của bạn cho "cấp trên". Đúng là quản lý bộ nhớ thủ công nhanh hơn trong thời gian chạy để xử lý việc quản lý bộ nhớ. Tuy nhiên, chi phí phần mềm không thể được đánh giá hoàn toàn dựa trên thời gian CPU dành cho việc quản lý bộ nhớ.
ArTs

2
@ArTs: Tôi thậm chí không nhất thiết phải đồng ý với điều này. RAII đi kèm với yêu cầu một đối tượng phải bị phá hủy khi nó rời khỏi phạm vi. Sau đó, một vòng lặp được yêu cầu để thực hiện n phá hủy đối tượng. Theo một thế hệ GC hiện đại, những sự phá hủy đó có thể được hoãn lại cho đến hết vòng lặp, hoặc thậm chí sau đó, và thực hiện chỉ một thao tác để phá hủy hàng trăm lần lặp bộ nhớ. Một GC thể rất rất nhanh trong trường hợp tốt của nó.
Phoshi

14

RAII cũng xuất phát từ quản lý bộ nhớ đếm tham chiếu tự động, ví dụ như được sử dụng bởi Perl. Mặc dù việc đếm tham chiếu rất dễ thực hiện, có tính xác định và khá hiệu quả, nhưng nó không thể xử lý các tham chiếu vòng tròn (chúng gây rò rỉ), đó là lý do tại sao nó không được sử dụng phổ biến.

Các ngôn ngữ được thu gom rác không thể sử dụng RAII trực tiếp, nhưng thường cung cấp cú pháp với hiệu quả tương đương. Trong Java, chúng tôi có câu lệnh try-with-ressource

try (BufferedReader br = new BufferedReader(new FileReader(path))) { ... }

tự động gọi .close()tài nguyên trên khối thoát. C # có IDisposablegiao diện, cho phép .Dispose()được gọi khi để lại một using (...) { ... }câu lệnh. Python có withtuyên bố:

with open(filename) as f:
    ...

hoạt động theo cách tương tự. Trong một vòng quay thú vị về điều này, phương pháp mở tệp của Ruby nhận được một cuộc gọi lại. Sau khi gọi lại đã được thực hiện, tập tin được đóng lại.

File.open(name, mode) do |f|
    ...
end

Tôi nghĩ Node.js sử dụng cùng một chiến lược.


4
Sử dụng các hàm bậc cao hơn để quản lý tài nguyên có từ rất lâu trước Ruby. Trong Lisps, điều khá phổ biến là có các with-open-filehandlehàm mở tệp, đưa nó vào một hàm và khi trả về hàm, hãy đóng lại tệp.
Jörg W Mittag

4
Đối số tham chiếu theo chu kỳ là khá phổ biến, nhưng nó thực sự quan trọng như thế nào? Tham chiếu theo chu kỳ có thể được giảm thiểu bằng cách sử dụng các con trỏ yếu nếu quyền sở hữu rõ ràng.
Philipp

2
@Philipp Khi bạn sử dụng tính năng giới thiệu, quyền sở hữu thường không rõ ràng. Ngoài ra, câu trả lời này nói về các ngôn ngữ sử dụng ref đếm riêng và tự động, vì vậy các tài liệu tham khảo yếu không tồn tại hoặc khó sử dụng hơn nhiều so với tài liệu tham khảo mạnh.

3
Cấu trúc dữ liệu tuần hoàn @Philipp rất hiếm, trừ khi bạn vẫn làm việc với các biểu đồ phức tạp. Con trỏ yếu không giúp ích trong biểu đồ đối tượng tuần hoàn chung, mặc dù chúng giúp ích trong các trường hợp phổ biến hơn như con trỏ cha trong cây. Một cách giải quyết tốt là giữ một đối tượng bối cảnh đại diện cho tham chiếu đến toàn bộ biểu đồ và quản lý sự phá hủy. Việc đếm tiền không phải là một thỏa thuận, nhưng nó đòi hỏi người lập trình phải rất ý thức về những hạn chế của nó. Tức là nó có chi phí nhận thức cao hơn một chút so với GC.
amon

1
Một lý do quan trọng tại sao việc đếm ref hiếm khi được sử dụng là nó thường chậm hơn so với GC, mặc dù nó đơn giản.
Rufflewind

14

Theo tôi, ưu điểm thuyết phục nhất của bộ sưu tập rác là nó cho phép khả năng kết hợp . Tính đúng đắn của quản lý bộ nhớ là một thuộc tính cục bộ trong môi trường thu gom rác. Bạn có thể xem xét từng phần một cách riêng biệt và xác định xem nó có thể rò rỉ bộ nhớ hay không. Kết hợp bất kỳ số lượng các bộ phận chính xác bộ nhớ và chúng vẫn đúng.

Khi bạn dựa vào số tham chiếu, bạn sẽ mất tài sản đó. Liệu ứng dụng của bạn có thể rò rỉ bộ nhớ trở thành một tài sản toàn cầu của toàn bộ ứng dụng hay không bằng cách tham chiếu. Mỗi tương tác mới giữa các bộ phận có khả năng sử dụng quyền sở hữu sai và phá vỡ quản lý bộ nhớ.

Nó có tác dụng rất rõ ràng đối với việc thiết kế các chương trình bằng các ngôn ngữ khác nhau. Các chương trình trong các ngôn ngữ GC có xu hướng nhiều hơn một chút các đối tượng có nhiều tương tác, trong khi trong các ngôn ngữ không có GC, người ta có xu hướng thích các phần có cấu trúc với các tương tác được kiểm soát chặt chẽ và hạn chế giữa chúng.


1
Tính chính xác chỉ có thể kết hợp được nếu các đối tượng chỉ giữ các tham chiếu thay mặt họ và không nhân danh các mục tiêu của các tham chiếu đó. Khi mọi thứ như thông báo được đưa vào hỗn hợp (Bob giữ tham chiếu đến Joe vì Joe yêu cầu Bob thông báo cho anh ta khi có chuyện xảy ra và Bob hứa sẽ làm như vậy, nhưng Bob thì không quan tâm đến Joe), tính chính xác của GC thường yêu cầu quản lý tài nguyên trong phạm vi [được thực hiện thủ công trong nhiều trường hợp, vì các hệ thống GC thiếu tự động hóa C ++].
supercat

@supercat: "Tính chính xác của GC thường yêu cầu quản lý tài nguyên có phạm vi". Ơ Phạm vi chỉ tồn tại trong mã nguồn và GC chỉ tồn tại trong thời gian chạy (và do đó, hoàn toàn không biết đến sự tồn tại của phạm vi).
Jon Harrop

@JonHarrop: Tôi đã sử dụng thuật ngữ "phạm vi" theo nghĩa tương tự như "con trỏ có phạm vi" C ++ [thời gian tồn tại của đối tượng nên là vật chứa nó], vì đó là cách sử dụng ngụ ý của câu hỏi ban đầu. Quan điểm của tôi là các đối tượng tạo ra các tài liệu tham khảo có khả năng tồn tại lâu dài cho chính họ cho các mục đích như nhận các sự kiện có thể không thể kết hợp được trong một hệ thống thuần túy. Để cho chính xác, các tham chiếu nhất định cần phải mạnh mẽ và các tham chiếu nhất định cần phải yếu, và các tham chiếu nào cần phải phụ thuộc vào cách sử dụng một đối tượng. Ví dụ ...
supercat

... giả sử các đối tượng Fred và Barney đăng ký thông báo bất cứ khi nào bất cứ điều gì trong một thư mục nhất định được sửa đổi. Trình xử lý của Fred không làm gì khác ngoài việc tăng một bộ đếm có giá trị mà nó có thể báo cáo theo yêu cầu, nhưng nó không có công dụng nào khác. Trình xử lý của Barney sẽ bật lên một cửa sổ mới nếu một tệp nhất định được sửa đổi. Để cho chính xác, Fred nên được đăng ký với một sự kiện yếu, nhưng Barney phải mạnh, nhưng đối tượng hẹn giờ sẽ không có cách nào để biết điều đó.
supercat

@supercat: Đúng vậy. Tôi sẽ không nói rằng điều đó xảy ra "thường". Tôi chỉ bắt gặp nó một lần trong 30 năm lập trình.
Jon Harrop

7

Đóng cửa là một tính năng thiết yếu của khá nhiều ngôn ngữ hiện đại. Chúng rất dễ thực hiện với GC và rất khó (mặc dù không phải là không thể) để có được quyền với RAII, vì một trong những tính năng chính của chúng là chúng cho phép bạn trừu tượng trong suốt vòng đời của các biến!

C ++ chỉ có họ 40 năm sau khi mọi người khác làm, và phải mất rất nhiều công sức của rất nhiều người thông minh để làm cho họ đúng. Ngược lại, nhiều ngôn ngữ kịch bản được thiết kế và triển khai bởi những người không có kiến ​​thức về thiết kế và triển khai ngôn ngữ lập trình có chúng.


9
Tôi không nghĩ rằng đóng cửa trong C ++ là một ví dụ tốt. Các lambdas trong C ++ 11 chỉ là đường cú pháp cho các lớp functor (trước C ++ 11 đáng kể) và không an toàn cho bộ nhớ: Nếu bạn nắm bắt được điều gì đó bằng cách tham chiếu và gọi đóng cửa sau khi điều đó bị chết, bạn chỉ cần nhận được UB, giống như giữ một tham chiếu lâu hơn hợp lệ. Việc họ xuất hiện muộn 40 năm là do sự thừa nhận muộn màng của FP, chứ không phải do tìm ra cách làm cho chúng an toàn. Và trong khi thiết kế chúng chắc chắn là một nhiệm vụ to lớn, tôi nghi ngờ rằng hầu hết các nỗ lực đã đi vào cân nhắc thời gian sống.

Tôi đồng ý với delnan: C ++ không được đóng đúng: bạn phải lập trình chúng rất cẩn thận nếu bạn không muốn nhận được kết xuất cốt lõi khi bạn gọi chúng.
Giorgio

2
@delnan: lambda bắt giữ theo tham chiếu rất có chủ ý có [&]cú pháp đó . Bất kỳ lập trình viên C ++ nào cũng đã liên kết &dấu hiệu với các tham chiếu và biết về các tham chiếu cũ.
MSalters

2
@MSalters Quan điểm của bạn là gì? Tôi đã tự rút ra kết nối tham chiếu. Tôi không nói C ++ lambdas đặc biệt không an toàn, tôi nói chúng chính xác là không an toàn như tài liệu tham khảo. Tôi không cho rằng C ++ lambdas là xấu, tôi đã lập luận chống lại yêu cầu của câu trả lời này (rằng C ++ đã đóng cửa rất muộn vì họ phải tìm ra cách làm đúng).

5
  1. SBMM làm cho các chương trình mang tính quyết định hơn (bạn có thể biết chính xác khi nào một đối tượng bị phá hủy);

Đối với hầu hết các lập trình viên, HĐH là không xác định, cấp phát bộ nhớ của họ là không xác định và hầu hết các chương trình họ viết là đồng thời và do đó, vốn không mang tính quyết định. Thêm các ràng buộc rằng một hàm hủy được gọi chính xác ở cuối phạm vi thay vì hơi trước hoặc sau một chút không phải là một lợi ích thực tế đáng kể cho đại đa số các lập trình viên.

  1. trong các ngôn ngữ sử dụng GC, bạn thường phải thực hiện quản lý tài nguyên thủ công (ví dụ, xem các tệp đóng trong Java), phần nào đánh bại mục đích của GC và cũng dễ bị lỗi;

Xem usingtrong C # và usetrong F #.

  1. bộ nhớ heap cũng có thể (rất thanh lịch, imo) bị giới hạn phạm vi (xem std :: shared_ptr trong C ++).

Nói cách khác, bạn có thể lấy một đống là một giải pháp cho mục đích chung và thay đổi nó thành chỉ hoạt động trong một trường hợp cụ thể bị hạn chế nghiêm trọng. Đó là sự thật, tất nhiên, nhưng vô dụng.

Tại sao SBMM không được sử dụng rộng rãi hơn? Nhược điểm của nó là gì?

SBMM giới hạn những gì bạn có thể làm:

  1. SBMM tạo ra vấn đề thú vị đi lên với các lần đóng từ vựng hạng nhất, đó là lý do tại sao các lần đóng là phổ biến và dễ sử dụng trong các ngôn ngữ như C # nhưng hiếm và khó trong C ++. Lưu ý rằng có một xu hướng chung đối với việc sử dụng các cấu trúc chức năng trong lập trình.

  2. SBMM yêu cầu các hàm hủy và chúng cản trở các cuộc gọi đuôi bằng cách thêm nhiều việc phải làm trước khi một chức năng có thể trở lại. Các cuộc gọi đuôi rất hữu ích cho các máy trạng thái mở rộng và được cung cấp bởi những thứ như .NET.

  3. Một số cấu trúc dữ liệu và thuật toán nổi tiếng là khó thực hiện khi sử dụng SBMM. Về cơ bản bất cứ nơi nào mà chu kỳ là tự nhiên. Đáng chú ý nhất là các thuật toán đồ thị. Bạn thực sự kết thúc việc viết GC của riêng bạn.

  4. Lập trình đồng thời khó hơn vì dòng điều khiển và do đó, tuổi thọ đối tượng vốn không mang tính quyết định ở đây. Các giải pháp thực tế trong các hệ thống chuyển tin nhắn có xu hướng sao chép sâu các thông điệp và sử dụng tuổi thọ quá dài.

  5. SBMM giữ cho các đối tượng tồn tại cho đến khi kết thúc phạm vi của chúng trong mã nguồn thường dài hơn mức cần thiết và có thể dài hơn mức cần thiết. Điều này làm tăng lượng rác trôi nổi (các vật thể không thể tiếp cận đang chờ tái chế). Ngược lại, truy tìm bộ sưu tập rác có xu hướng giải phóng các đối tượng ngay sau khi tham chiếu cuối cùng về chúng biến mất có thể sớm hơn nhiều. Xem huyền thoại quản lý bộ nhớ: nhanh chóng .

SBMM hạn chế đến mức các lập trình viên cần một lối thoát cho các tình huống mà thời gian sống không thể được thực hiện để làm tổ. Trong C ++, shared_ptrcung cấp một lối thoát nhưng nó có thể chậm hơn ~ 10 lần so với truy tìm bộ sưu tập rác . Vì vậy, sử dụng SBMM thay vì GC sẽ khiến hầu hết mọi người sai lầm trong hầu hết thời gian. Tuy nhiên, điều đó không có nghĩa là nó vô dụng. SBMM vẫn có giá trị trong bối cảnh các hệ thống và lập trình nhúng nơi tài nguyên bị hạn chế.

FWIW bạn có thể muốn xem Forth và Ada, và đọc về tác phẩm của Nicolas Wirth.


1
Nếu bạn nói những bit nào tôi có thể xây dựng hoặc trích dẫn bài viết.
Jon Harrop

2
Làm thế nào có liên quan là chậm hơn 10 lần trong một vài trường hợp sử dụng hiếm hoi thay vì có mặt khắp nơi trong tất cả các trường hợp sử dụng? C ++ có unique_ptr và cho hầu hết các mục đích là đủ. Bên cạnh đó, thay vì tấn công RAII máng C ++ (một ngôn ngữ mà nhiều người ghét ghét vì là ngôn ngữ cổ), nếu bạn định tấn công máng RAII để tấn công một ngôn ngữ, hãy thử một người anh em trẻ của gia đình RAII, Rust chẳng hạn. Về cơ bản, Rust có mọi thứ đúng mà C ++ đã sai trong khi hầu hết mọi thứ đều đúng mà C ++ cũng đúng. Hơn nữa, "sử dụng" giúp bạn có một bộ trường hợp sử dụng rất hạn chế và bỏ qua thành phần.
dùng1703394

2
"Làm thế nào có liên quan là chậm hơn 10 lần trong một vài trường hợp sử dụng hiếm hoi thay vì có mặt khắp nơi trong tất cả các trường hợp sử dụng?". Thứ nhất, đó là một đối số vòng tròn: shared_ptrchỉ hiếm trong C ++ vì nó quá chậm. Thứ hai, đó là một so sánh táo và cam (như bài báo tôi đã trích dẫn đã chỉ ra) vì shared_ptrchậm hơn nhiều lần so với một sản phẩm GC. Thứ ba, các GC không có mặt khắp nơi và được tránh trong các phần mềm như công cụ FIX của LMax và Rapid Addition.
Jon Harrop

1
@Jon Harrop, nếu bạn không làm ơn hãy soi sáng cho tôi. Công thức ma thuật nào bạn đã sử dụng máng trong hơn 30 năm qua để giảm thiểu tác động bắc cầu của việc sử dụng tài nguyên sâu? Nếu không có một công thức kỳ diệu như vậy sau hơn 30 năm, tôi chỉ có thể kết luận rằng bạn phải phân bổ bị cắn bởi nó cho các nguyên nhân khác.
dùng1703394

1
@Jon Harrop, shared_ptr không phải là hiếm vì nó chậm, hiếm vì lý do là trong một hệ thống được thiết kế khéo léo, nhu cầu 'sở hữu chung' là rất hiếm.
dùng1703394

4

Nhìn vào một số chỉ số phổ biến như TIOBE (tất nhiên là có thể tranh cãi, nhưng tôi đoán đối với loại câu hỏi của bạn có thể sử dụng câu hỏi này), trước tiên bạn thấy rằng ~ 50% trong số 20 ngôn ngữ hàng đầu là "ngôn ngữ kịch bản" hoặc "phương ngữ SQL ", Trong đó" dễ sử dụng "và phương tiện trừu tượng có tầm quan trọng hơn nhiều so với hành vi xác định. Từ các ngôn ngữ "được biên dịch" còn lại, có khoảng 50% ngôn ngữ có SBMM và ~ 50% không có ngôn ngữ. Vì vậy, khi lấy các ngôn ngữ kịch bản ra khỏi tính toán của bạn, tôi sẽ nói rằng giả định của bạn là sai, trong số các ngôn ngữ được biên dịch, các ngôn ngữ có SBMM phổ biến như các ngôn ngữ không có.


1
"Dễ sử dụng" khác với chủ nghĩa quyết định như thế nào? Không nên coi một ngôn ngữ xác định dễ sử dụng hơn ngôn ngữ không xác định?
Philipp

2
@Philipp Chỉ có điều quyết định hay không thực sự quan trọng. Thời gian sống của đối tượng không phải là vấn đề của riêng nó (mặc dù C ++ và bạn bè ràng buộc nhiều thứ quan trọng với đối tượng thời gian sống, bởi vì họ có thể). Khi một đối tượng không thể truy cập được giải phóng không thành vấn đề bởi vì theo định nghĩa, bạn không sử dụng nó nữa.

Một điều nữa, nhiều "ngôn ngữ kịch bản" khác nhau như Perl và Python cũng sử dụng tính tham chiếu làm phương tiện chính để quản lý bộ nhớ.
Philipp

1
@Philipp Ít nhất là trong thế giới Python, đây được coi là một chi tiết triển khai của CPython, không phải là một thuộc tính của ngôn ngữ (và hầu như mọi cách thực hiện khác của eschews). Hơn nữa, tôi sẽ lập luận rằng việc đếm tất cả các tham chiếu không từ chối với chu kỳ sao lưu GC không đủ điều kiện là SBMM hoặc RAII. Trên thực tế, bạn khó có thể tìm thấy những người đề xướng RAII đang xem xét phong cách quản lý bộ nhớ này có thể so sánh với RAII (chủ yếu là vì không phải vậy, chu kỳ ở bất cứ đâu có thể ngăn chặn sự phân bổ nhanh chóng ở bất kỳ nơi nào khác trong chương trình).

3

Một lợi thế lớn của hệ thống GC mà chưa ai đề cập đến là tham chiếu trong hệ thống GC được đảm bảo giữ nguyên danh tính của nó miễn là nó tồn tại . Nếu một cuộc gọi IDisposable.Dispose(.NET) hoặc AutoCloseable.Close(Java) trên một đối tượng trong khi các bản sao của tham chiếu tồn tại, các bản sao đó sẽ tiếp tục tham chiếu đến cùng một đối tượng. Đối tượng sẽ không còn hữu ích cho bất cứ điều gì nữa, nhưng cố gắng sử dụng nó sẽ có hành vi dự đoán được kiểm soát bởi chính đối tượng. Ngược lại, trong C ++, nếu mã gọi deletevào một đối tượng và sau đó cố gắng sử dụng nó, toàn bộ trạng thái của hệ thống sẽ hoàn toàn không được xác định.

Một điều quan trọng khác cần lưu ý là quản lý bộ nhớ dựa trên phạm vi hoạt động rất tốt cho các đối tượng có quyền sở hữu được xác định rõ ràng. Nó hoạt động kém hơn nhiều, và đôi khi hết sức tệ hại, với các đối tượng không có quyền sở hữu được xác định. Nói chung, các đối tượng có thể thay đổi nên có chủ sở hữu, trong khi các đối tượng không thay đổi thì không cần, nhưng có một nếp nhăn: việc sử dụng mã của một loại biến đổi để giữ dữ liệu bất biến là rất phổ biến, bằng cách đảm bảo rằng không có tham chiếu nào được hiển thị mã có thể làm thay đổi thể hiện. Trong kịch bản như vậy, các thể hiện của lớp có thể thay đổi có thể được chia sẻ giữa nhiều đối tượng bất biến và do đó không có quyền sở hữu rõ ràng.


4
Tài sản bạn đề cập đến trong nửa đầu là an toàn bộ nhớ. Mặc dù GC là một cách rất dễ dàng để đạt được an toàn bộ nhớ, nhưng không cần thiết phải có GC: Hãy xem Rust để biết ví dụ được thực hiện tốt.

@delnan: Khi tôi xem Rust-lang.org, trình duyệt của tôi dường như không thể điều hướng hữu ích từ đó; Tôi nên tìm thêm thông tin ở đâu? Ấn tượng của tôi là an toàn bộ nhớ mà không có GC áp đặt một số hạn chế nhất định đối với cấu trúc dữ liệu có thể không phù hợp với tất cả những điều mà ứng dụng có thể cần phải làm, nhưng tôi rất vui khi được chứng minh là sai.
supercat

1
Tôi không biết về một tài liệu tham khảo tốt (hoặc thậm chí nhỏ) cho việc này; Kiến thức Rust của tôi đã tích lũy được hơn một hoặc hai năm để đọc danh sách gửi thư (và tất cả nội dung được liên kết trong thư, bao gồm các hướng dẫn khác nhau, bài đăng trên blog về thiết kế ngôn ngữ, các vấn đề về github, ThisWeekInRust, v.v.). Để giải quyết ngắn gọn ấn tượng của bạn: Có, mỗi cấu trúc an toàn (nhất thiết) áp đặt các hạn chế, nhưng đối với hầu như bất kỳ đoạn mã an toàn bộ nhớ nào, một cấu trúc an toàn thích hợp đều tồn tại hoặc có thể được viết. Những cái phổ biến nhất đã tồn tại trong ngôn ngữ và stdlib, tất cả những cái khác có thể được viết bằng mã người dùng.

@delnan: Rust có yêu cầu cập nhật lồng vào các bộ đếm tham chiếu không, hay nó có một số phương tiện khác để xử lý các đối tượng bất biến (hoặc các đối tượng có thể thay đổi được bọc không thay đổi) không có quyền sở hữu nhất định? Liệu Rust có một khái niệm về cả hai con trỏ "sở hữu đối tượng" và "không sở hữu" không? Tôi nhớ lại một bài viết về con trỏ "Xonor" trong đó thảo luận về ý tưởng của các đối tượng có một tham chiếu duy nhất "sở hữu" chúng và các tài liệu tham khảo khác không có; khi tham chiếu "sở hữu" vượt quá phạm vi, tất cả các tham chiếu không sở hữu sẽ trở thành tham chiếu đến các đối tượng đã chết và sẽ được nhận dạng như vậy ...
supercat

1
Tôi không nghĩ các bình luận của Stack Exchange là phương tiện phù hợp để thực hiện chuyến tham quan qua ngôn ngữ. Nếu bạn vẫn quan tâm, bạn có thể truy cập thẳng vào nguồn (#rust IRC, danh sách gửi thư rỉ sét, v.v.) và / hoặc đánh tôi với một phòng trò chuyện (bạn sẽ có thể tạo một).

-2

Trước hết, điều rất quan trọng để nhận ra rằng việc đánh đồng RAII với SBMM. hoặc thậm chí đến SBRM. Một trong những phẩm chất cần thiết nhất (và ít được biết đến nhất hoặc được đánh giá cao nhất) của RAII là thực tế rằng nó làm cho 'trở thành một tài nguyên' trở thành một tài sản KHÔNG chuyển tiếp thành phần.

Bài đăng trên blog sau đây thảo luận về khía cạnh quan trọng này của RAII và đối chiếu nó với sự thay đổi tài nguyên trong các ngôn ngữ của GC sử dụng GC không xác định.

http://minorfs.wordpress.com/2011/04/29/why-garbage-collection-is-anti-productive/

Điều quan trọng cần lưu ý là mặc dù RAII chủ yếu được sử dụng trong C ++, Python (cuối cùng là phiên bản không dựa trên VM) có các hàm hủy và GC xác định cho phép RAII được sử dụng cùng với GC. Tốt nhất của cả hai thế giới nếu nó là.


1
-1 Đó là một trong những bài viết tồi tệ nhất tôi từng đọc.
Jon Harrop

1
Vấn đề trong các ngôn ngữ không phải là họ hỗ trợ GC, mà là họ từ bỏ RAII. Không có lý do gì một ngôn ngữ / khung không thể hỗ trợ cả hai.
supercat

1
@Jon Harrop, bạn có thể giải thích. Trong số các khiếu nại được đưa ra trong bài viết, thậm chí có một trong 3 khiếu nại đầu tiên không giữ? Tôi nghĩ rằng bạn có thể không đồng ý với yêu cầu năng suất, nhưng 3 yêu cầu khác là hoàn toàn hợp lệ. Quan trọng nhất là cái đầu tiên về tính siêu việt của việc trở thành một tài nguyên.
dùng1703394

2
@ user1703394: Đầu tiên, toàn bộ bài viết dựa trên "ngôn ngữ được phân loại" của người rơm khi trên thực tế, nó không liên quan gì đến việc thu gom rác. Thứ hai, ông đổ lỗi cho bộ sưu tập rác khi trên thực tế, lỗi nằm ở lập trình hướng đối tượng. Cuối cùng, cuộc cãi vã của anh là 10 năm quá muộn. Phần lớn các lập trình viên đã hiện đại hóa thành rác ngôn ngữ được thu thập chính xác bởi vì họ cung cấp năng suất cao hơn nhiều.
Jon Harrop

1
Các ví dụ cụ thể của anh ấy (RAM, xử lý tệp mở, khóa, luồng) khá hay. Tôi rất khó để nhớ lại lần cuối cùng tôi phải viết mã xử lý trực tiếp với bất kỳ ai trong số đó. Với RAM, GC tự động hóa mọi thứ. Với tay cầm tập tin, tôi viết mã giống như File.ReadLines file |> Seq.lengthnơi trừu tượng xử lý đóng cho tôi. Khóa và chủ đề tôi đã thay thế bằng .NET Taskvà F # MailboxProcessor. Toàn bộ "Chúng tôi đã bùng nổ số lượng quản lý tài nguyên thủ công" chỉ là hoàn toàn vô nghĩa.
Jon Harrop
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.