Thêm Loại Chuỗi Vào Tập Lệnh - nói dối e blog

Thêm Loại Chuỗi Vào Tập Lệnh

Gần đây, công việc chính của tôi là bổ sung kiểu chuỗi (string) cho máy ảo, đồng thời cho phép trình biên dịch tạo ra bytecode tương ứng. Ý tưởng khá đơn giản: làm theo cách của Lua, gộp tất cả các chuỗi giống nhau trong mã nguồn thành một bản duy nhất trong bytecode. Những vị trí tham chiếu chuỗi đó sẽ trực tiếp trỏ đến bản duy nhất này. Khi bytecode chạy, mọi chuỗi mới được tạo ra trong quá trình thực thi sẽ được cấp phát riêng biệt và sẽ được thu gom rác (garbage collection) dọn dẹp khi cần.

Nghe tưởng dễ, nhưng khi triển khai mới thấy phức tạp ra sao. Vấn đề đầu tiên là: khi bytecode nằm trong bộ nhớ, mọi tham chiếu đến chuỗi nên là địa chỉ vật lý. Tuy nhiên, trước khi bytecode được nạp vào máy ảo, không thể sử dụng địa chỉ tuyệt đối được. Do đó, quá trình nạp bytecode không còn là việc sao chép đơn thuần vào bộ nhớ nữa. Khi thao tác với chuỗi, tôi muốn mọi thứ diễn ra với chi phí bằng 0, không có bất kỳ quá trình khởi tạo trễ (lazy initialization) nào.

Giải pháp hiện tại như sau: Trong quá trình quét mã nguồn (trình biên dịch của tôi chỉ quét một lần), mỗi chuỗi gặp phải sẽ được đánh số thứ tự. Trong định nghĩa hàm, chuỗi sẽ được đẩy vào stack thông qua số thứ tự này. Trong các đoạn mã ngoài hàm, chuỗi sẽ được lấy từ một bảng (table) cục bộ đặc biệt thông qua số thứ tự. Việc thao tác chuỗi ở ngoài hàm có tốn chi phí một lần để truy cập bảng, nhưng vì thao tác đọc bảng cục bộ chỉ mất một lệnh bytecode nên không làm tăng không gian lưu trữ. Khi đăng ký hàm, trình biên dịch sẽ quét bytecode của thân hàm và chuyển đổi toàn bộ số thứ tự thành địa chỉ tuyệt đối. Đến lúc gọi hàm, mọi thao tác với chuỗi sẽ không còn chi phí phát sinh.

Sau khi biên dịch toàn bộ mã nguồn, hệ thống sẽ tự động sinh ra một hàm đăng ký để nạp toàn bộ chuỗi vào máy ảo. Dòng lệnh đầu tiên trong bytecode sẽ là lời gọi hàm tự động sinh này.

Tóm tắt xong giải pháp của tôi. Về phần xử lý bên trong máy ảo, dù phức tạp hơn nhưng cũng chỉ là công việc lập trình thông thường. Nhân đây, tôi muốn chia sẻ đôi điều về Java.

Trong Java, cách xử lý chuỗi của máy ảo cũng tương tự như phương pháp của Lua. Tuy nhiên, nhiều lập trình viên Java không hiểu rõ điều này. Một sự việc đáng nhớ là từ anh Lý Duy kể lại: phiên bản Tomcat 4 (tôi không nhớ rõ chính xác phiên bản) từng gặp vấn đề hiệu năng do dùng phép nối chuỗi (string +=) trong một vòng lặp. Khi vòng lặp chạy hàng nghìn lần, hệ thống tạo ra lượng lớn rác, khiến garbage collector phải làm việc liên tục.

Một ví dụ khác là từ đồng nghiệp của tôi: bộ phận phát triển dự án Java trong công ty từng áp dụng thao tác lưu trữ bền (persistent) trong một dự án. Khi code review, đồng nghiệp tôi nghi ngờ hiệu suất của thao tác này. Sau đó, nhóm phát triển đã tiến hành kiểm thử và kết luận rằng Java xử lý chuỗi khi “lưu trữ bền” rất hiệu quả, gần như không có vấn đề gì. Nghe đến đây tôi bật cười, dù không phải lập trình viên Java nhưng trực giác mách bảo rằng chắc chắn họ đã dùng chuỗi giống nhau trong một vòng lặp để lưu trữ. Không lạ khi kết luận cuối cùng là Java không chỉ hiệu quả mà còn tích hợp cả tính năng nén dữ liệu.

Bổ sung ngày 8/12: Có bạn đọc góp ý rằng từ “lưu trữ bền” nên thay bằng “chuyển đổi thành chuỗi (serialization)”. Tôi đồng tình. Về bản chất, nhóm lập trình Java muốn lưu một nhóm chuỗi ra ổ cứng, đây là thao tác lưu trữ bền. Tuy nhiên, như đặc điểm chung của lập trình viên Java, họ thường không quan tâm đến chi phí của thao tác I/O hay hiệu suất chuyển đổi dữ liệu. Trong khi đó, cả tôi và đồng nghiệp - những lập trình viên C++ - đều phản ứng đầu tiên là lo ngại về hiệu năng (đây là ứng dụng yêu cầu hiệu suất cao). Về phép nối chuỗi +=, những lập trình viên hiểu rõ cơ chế quản lý bộ nhớ của máy ảo sẽ rất nhạy cảm với việc tiêu tốn tài nguyên. Anh Lý Duy dùng ví dụ này để chỉ trích một số lập trình viên Java thiếu quan tâm đến chi tiết ngôn ngữ và hiệu suất. Dù không rành Java, tôi - một lập trình viên C++ - cũng đoán được cách xử lý đúng đắn. Việc sai sót xảy ra trong phiên bản phát hành chính thức của Tomcat là điều không nên có.

0%