Vài ghi chép về V8 và Garbage Collection
Bài viết này chỉ đề cập đến V8 (là JavaScript engine đứng sau Google Chrome và NodeJS), sau khi đọc bài này, nên tìm đọc thêm về SpiderMonkey (Firefox), Chakra (Edge) và Carakan (Opera), các yếu tố về kĩ thuật trong các engine này có thể sẽ khác nhiều so với V8.
Lý do chọn viết về V8 thì rất là đơn giản, vì engine này có nguồn tài liệu cực kì phong phú và gần như là, hễ tìm với từ khóa JavaScript engine thì nó cứ ra V8 =))
Thực ra, nếu nhìn nhận một cách khách quan về V8 cũng như những kĩ thuật mà team này bỏ ra cho công việc optimization một ngôn ngữ như JavaScript, và đào sâu vào những kĩ thuật đó, thì đó là một kho tàng kiến thức đồ sộ mà chỉ có dại lắm mới dám bỏ qua không ngó ngàng tới.
Viết bài này, mình không có tham vọng gom hết đống kiến thức đồ sộ đó vào một bài viết nhỏ, nên tất nhiên sẽ còn nhiều điểm thiếu sót, hy vọng các bạn đọc xong sẽ nhiệt tình góp ý, cũng như thu về được một ít thông tin vụn vặt, để từ đó mà đem đào sâu hơn vào engine lý thú này.
Hidden Class
Mọi thứ trong JavaScript đều là object
, và mọi thuộc tính của một object thì đều có thể được thêm vào hoặc bỏ đi (thay đổi layout), hoặc thay đổi kiểu dữ liệu (type) bất cứ lúc nào (on the fly). Điều này khiến cho việc optimize một ngôn ngữ "động" như JavaScript (dynamically typed language) trở nên rất khó khăn.
Ví dụ luôn, giả sử ta có đoạn code như thế này:
class Car { door_open() { // ... }
} class Girl { // girls has no door
} const open_the_door = (object) => { object.door_open();
};
Trong ví dụ trên, hàm open_the_door()
nhận vào một object và gọi hàm door_open()
của object đó, tuy nhiên, vì không có cách nào quy định một cách cụ thể kiểu dữ liệu nhận vào của hàm open_the_door()
, compiler sẽ không thể nào biết trước được liệu object truyền vào có tồn tại hàm door_open()
hay không. Mà nếu không biết thì phải kiểm tra, bằng cách tra cứu (lookup - duyệt hết toàn bộ hàm/thuộc tính có trong object đó và tìm). Rõ ràng, là không hề tối ưu, và nguyên nhân thì lại do chính thiết kế của JavaScript.
Team V8 giới thiệu một khái niệm gọi là hidden class
, gán vào cho mỗi object để giúp cho việc tracking kiểu và các thuộc tính của chúng một cách thuận tiện hơn. Và mỗi lần object thay đổi, thì hidden class
của nó cũng sẽ thay đổi tương ứng.
Một ví dụ thực tế, có lẽ là rất thường gặp:
let product = {};
product.title = api.getBookTitle(book_id) || "";
product.pages = api.getBookPages(book_id) || "";
Với cách viết như trên, ta có tổng cộng 3 lần
thay đổi cấu trúc của object product
. Đầu tiên, là ở câu lệnh let product = {}
, lúc này V8 sẽ tạo ra hidden class C0
để biểu diễn cấu trúc của product
(là một object rỗng). Tiếp theo, khi gặp câu lệnh gán product.title
, thì cấu trúc thay đổi, V8 thay thế hidden class C0
thành C1
(có thêm thuộc tính .title
), và cuối cùng là thành C2
ở câu lệnh gán product.pages
, quá trình thay đổi diễn ra như hình bên dưới:
Đọc thêm:
- [1]: Garbage Collection, http://javascript.info/garbage-collection
- [2]: A tour of V8: Garbage Collection, http://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection
- [3]: Concurrent marking in V8, https://v8project.blogspot.com/2018/06/concurrent-marking.html
- [4]: How To Write Fast, Memory-Efficient JavaScript, https://www.smashingmagazine.com/2012/11/writing-fast-memory-efficient-javascript/