So Sánh Giữa Đếm Tham Chiếu Và Thu Gom Rác - nói dối e blog

So Sánh Giữa Đếm Tham Chiếu Và Thu Gom Rác

Về bản chất, cả chiến lược đếm tham chiếu (reference counting - RC) và chiến lược thu gom rác (garbage collection - GC) đều thuộc phạm trù quản lý tài nguyên tự động. Khái niệm “tự động” ở đây hàm ý rằng ở cấp độ logic, chúng ta không thể biết chính xác thời điểm tài nguyên bị giải phóng, mà phải dựa vào thư viện底层 để duy trì vòng đời của tài nguyên đó.

Ngược lại với quản lý tự động là quản lý thủ công - phương pháp cho phép xác định rõ ràng vòng đời tài nguyên và thu hồi nó tại vị trí cụ thể. Trong C++, điều này thể hiện qua việc viết lệnh delete trong hàm hủy, đồng thời trình biên dịch tự động sinh mã hủy các thành phần cơ sở và biến thành viên.

Việc xây dựng một hệ thống thu gom rác cho C++ không hề mâu thuẫn với quản lý thủ công. Trên thực tế, quản lý tự động đã được áp dụng rộng rãi trong hầu hết các dự án C++ quy mô, chỉ khác là họ chọn đếm tham chiếu thay vì GC. Điều này cho thấy chúng ta đã kết hợp linh hoạt cả hai phương pháp trong suốt quá trình phát triển hệ thống. Dù sử dụng RC hay GC, những phần cần quản lý thủ công vẫn giữ nguyên bản chất.

Tại sao lại cần quản lý vòng đời tài nguyên tự động?

Hãy xét từ góc độ lập trình hướng đối tượng. Nếu mọi thứ đều là đối tượng, thì vòng đời của mỗi đối tượng nên do chính nó quản lý. Tuy nhiên thực tế không đơn giản như vậy. Vấn đề nằm ở các “chứa đựng” (containers) - những thực thể đặc biệt không chỉ sở hữu thuộc tính riêng mà còn duy trì các tham chiếu đến tập hợp các đối tượng khác.

Một đối tượng có thể bị nhiều container tham chiếu cùng lúc, điều này khiến container khác biệt với các thực thể vật lý như “mèo” hay “chó”. Việc một container tham chiếu đến đối tượng không đồng nghĩa với việc đối tượng đó thuộc về container. Khi phân chia thế giới thành các đối tượng, chúng ta sẽ gặp nhiều khái niệm không thể mô hình hóa thành đối tượng thuần túy - đó chính là các mối quan hệ tham chiếu.

Bản chất của lập trình hướng đối tượng nằm ở việc tổng quát hóa các đặc điểm chung giữa nhiều đối tượng để xử lý đồng loạt. Điều này khiến việc sử dụng các loại container trở nên tất yếu. Cũng chính vì vậy, một đối tượng không thể tự biết liệu mình đã có thể bị hủy hay chưa, bởi mối quan hệ giữa các đối tượng không phải là thuộc tính của đối tượng.

So sánh hai phương pháp

Đếm tham chiếu là giải pháp dễ triển khai nhất: chỉ cần ghi nhận số lần tham chiếu mà không cần biết ai đang tham chiếu. Điều này giúp giảm chi phí thiết lập và hủy bỏ tham chiếu. Tuy nhiên, việc không biết “ai đang tham chiếu” khiến việc xử lý các tham chiếu gián tiếp trở nên phức tạp và tốn kém.

Việc xác định một đối tượng có còn tồn tại hay không phụ thuộc vào việc nó còn liên kết trực tiếp hay gián tiếp với “thế giới” hay không. Ngay cả khi còn đối tượng khác tham chiếu, nếu mối liên kết với thế giới bị cắt đứt, đối tượng đó cũng nên bị hủy. Hệ thống RC buộc phải thông báo cho các đối tượng liên quan khi mối quan hệ bị thay đổi, điều này làm tăng chi phí hủy đối tượng.

Về mặt thuật toán, chi phí thiết lập mối quan hệ giữa các đối tượng trong GC và RC có cùng bậc độ phức tạp O(1), nhưng thực tế GC thường tốn kém hơn về mặt thời gian và không gian. Tuy nhiên, GC lại vượt trội ở khâu hủy đối tượng - không cần thông báo cho các đối tượng liên quan.

Hạn chế của GC và RC

GC gặp phải chi phí bổ sung từ quá trình đánh dấu (marking), phải duyệt qua tất cả các đối tượng còn sống. Chi phí này tỷ lệ thuận với số lượng đối tượng O(N), khiến hiệu suất giảm khi số lượng đối tượng tăng. Điều này giải thích tại sao trong các hệ thống lớn như Java hay C#, đôi khi xảy ra hiện tượng trao đổi trang (paging) khi không gian bộ nhớ ảo vượt quá bộ nhớ vật lý.

RC lại gặp khó khăn với các đối tượng có vòng đời ngắn, khi chi phí hủy liên tục các đối tượng này trở nên đáng kể. GC giải quyết vấn đề này bằng cách dồn toàn bộ chi phí vào quá trình thu gom định kỳ, tách biệt logic xử lý và quản lý tài nguyên. Cách tiếp cận này tận dụng tốt hơn sự phát triển phần cứng như đa nhân, nhưng lại không hiệu quả với các hệ thống nhỏ.

Lựa chọn phương pháp phù hợp

Trong các hệ thống không yêu cầu hướng đối tượng, cả RC và GC đều không cần thiết. Ngay cả với C/C++, việc lựa chọn phương pháp phụ thuộc vào ngữ cảnh cụ thể. Khi một đối tượng rõ ràng là thành phần của đối tượng khác, quản lý thủ công vẫn là lựa chọn tối ưu. Ngược lại, với các mối quan hệ tham chiếu phức tạp, cả hai phương pháp đều có thể áp dụng tùy theo đặc thù hệ thống.

Công cụ thu gom rác đơn giản tôi xây dựng chỉ nhằm mở rộng thêm lựa chọn cho lập trình viên C/C++. Việc kết hợp linh hoạt giữa quản lý thủ công, đếm tham chiếu và thu gom rác mới là chìa khóa để xây dựng hệ thống hiệu quả và bền vững.

0%