Bất cứ khi nào chạy code, bạn dùng bộ nhớ. Khi viết ngôn ngữ như Ruby, bộ nhớ dường như vô hạn. Bạn cứ tiếp tục mà không nghĩ đến lượng bộ nhớ cố định của hệ thống chạy code. Trong tập Ruby Magic này, chúng ta giải thích cách nó hoạt động!
Một chút lịch sử
Ngày xưa, ngôn ngữ script như Ruby chưa tồn tại. Mọi người chỉ viết code bằng C, ngôn ngữ lập trình thấp. Một điều làm cho các ngôn ngữ này thấp là bạn phải tự dọn dẹp. Ví dụ, khi cấp phát bộ nhớ lưu String, bạn cũng phải quyết định khi nào dọn dẹp.
Dọn dẹp thủ công
Trông giống như code Ruby giả sau. Khai báo biến và dùng phương thức free – phương thức này không thực sự tồn tại trong Ruby – để dọn bộ nhớ sau khi xong biến.
1_000_000.times do |i| variable = "Variable #{i}" puts variable free(variable) endCách lập trình nhàm chán
Bạn có thể nhận ra rủi ro: nếu quên free biến? Nội dung biến sẽ ở lại trong bộ nhớ cho đến khi process thoát. Làm đủ nhiều lần, hết bộ nhớ và process crash.
Ví dụ sau chứng minh vấn đề phổ biến khác:
1_000_000.times do |i| variable = "Variable #{i}" free(variable) puts variable endKhai báo biến và free nó. Nhưng rồi thử dùng lại, không thể vì nó không còn tồn tại. Nếu là C, chương trình crash với segfault. Oops!
Con người là máy sản xuất lỗi
Con người nổi tiếng kém ở việc không mắc lỗi kiểu này luôn. Do đó cần cách tự động dọn bộ nhớ. Cách phổ biến nhất – cũng dùng trong Ruby – là Garbage Collection (GC).
Garbage Collection (GC) hoạt động như thế nào
Trong ngôn ngữ dùng GC, bạn tạo đối tượng mà không dọn thủ công. Khi tạo đối tượng, nó đăng ký với Garbage Collector. GC cố gắng theo dõi tất cả tham chiếu bạn tạo đến đối tượng. Khi xác định bạn không dùng đối tượng nữa, nó mark để dọn. Mỗi lúc, GC tạm dừng chương trình và dọn tất cả đối tượng đã mark.
Xem vài ví dụ
Vòng lặp đơn giản trước đây, việc GC khá dễ. Mỗi vòng lặp, biến không dùng đâu nữa. Biến có thể mark dọn ngay.
1_000_000.times do |i| variable = "Variable #{i}" puts variable endVí dụ tiếp theo, truyền biến vào phương thức puts_later chờ 30 giây rồi puts biến.
def puts_later(variable) Thread.new do sleep 30 puts variable end end 1_000_000.times do |i| variable = "Variable #{i}" puts_later variable endViệc của Garbage Collector đã phức tạp trong ví dụ đơn giản này. Nó phải hiểu rằng chúng ta tham chiếu biến trong phương thức puts_later. Vì phương thức khởi động thread, GC phải theo dõi thread và chờ nó xong. Sau đó biến mới có thể mark dọn.
Khi nó phức tạp
Không đi vào ví dụ phức tạp, tin tôi đi, việc của Garbage Collector thực sự khó. Điều này cũng giải thích tại sao GC gây overhead và vấn đề trong production. Nó cần hiểu rất chi tiết chuyện gì xảy ra trong chương trình để dọn bộ nhớ đúng, tốn khá nhiều chu kỳ CPU. Nhưng mà, nó vẫn tốt hơn tự dọn!
Garbage Collection còn nhiều hơn thế
Đây chỉ là giới thiệu về Garbage Collection. Bài sau chúng ta sẽ xem cách chính xác nó hoạt động trong Ruby, và cách đo và tune GC để cải thiện hiệu suất ứng dụng.
Cập nhật: Tập tiếp theo có sẵn tại đây.