- Trình phân tích WASM viết bằng Rust có cấu trúc vốn nhanh, nhưng chi phí sao chép dữ liệu và tuần tự hóa ở ranh giới JS-WASM đã bộc lộ là nút thắt hiệu năng
- Trả về object trực tiếp qua
serde-wasm-bindgenchậm hơn 9~29% so với tuần tự hóa JSON, do chi phí chuyển đổi tinh vi giữa các runtime - Khi port toàn bộ pipeline sang TypeScript, hệ thống đạt được hiệu năng cho mỗi lần gọi nhanh hơn 2,2~4,6 lần trên cùng một kiến trúc
- Trong xử lý streaming, việc cải thiện O(N²) → O(N) bằng bộ nhớ đệm theo câu giúp đạt tốc độ xử lý tổng thể nhanh hơn 2,6~3,3 lần
- Kết quả cho thấy, WASM phù hợp với tác vụ nặng về tính toán và ít gọi, nhưng không phù hợp với việc phân tích object JS hoặc các hàm bị gọi thường xuyên
Cấu trúc và giới hạn của trình phân tích Rust WASM
- Trình phân tích
openui-langlà một pipeline 6 bước dùng để chuyển DSL do LLM tạo ra thành cây component React- Các bước:
autocloser → lexer → splitter → parser → resolver → mapper → ParseResult - Mỗi bước đảm nhận token hóa, phân tích cú pháp, diễn giải biến, chuyển đổi AST, v.v.
- Các bước:
- Bản thân mã Rust chạy nhanh, nhưng quá trình sao chép chuỗi, tuần tự hóa JSON và giải tuần tự giữa JS↔WASM diễn ra ở mỗi lần gọi
- Sao chép chuỗi đầu vào (JS→WASM), phân tích trong Rust, tuần tự hóa kết quả sang JSON, sao chép JSON (WASM→JS), rồi giải tuần tự ở JS
- Chi phí tại ranh giới này chi phối toàn bộ hiệu năng, còn tốc độ tính toán của Rust không phải là nút thắt
Thử nghiệm với serde-wasm-bindgen và thất bại
- Để tránh tuần tự hóa JSON, tác giả áp dụng
serde-wasm-bindgenđể trả trực tiếp struct Rust thành object JS - Tuy nhiên, kết quả quan sát được là chậm hơn 30%
- JS không thể đọc trực tiếp bộ nhớ của struct Rust, và layout bộ nhớ giữa các runtime khác nhau nên cần chuyển đổi theo từng field
- Trong khi đó, tuần tự hóa JSON chỉ cần tạo chuỗi một lần trong Rust, rồi được xử lý bằng
JSON.parseđã được tối ưu ở phía JS
- Kết quả benchmark
Fixture JSON round-trip serde-wasm-bindgen Thay đổi simple-table 20.5µs 22.5µs -9% contact-form 61.4µs 79.4µs -29% dashboard 57.9µs 74.0µs -28%
Chuyển sang TypeScript và cải thiện hiệu năng
- Toàn bộ cấu trúc 6 bước giống hệt được port hoàn toàn sang TypeScript, loại bỏ ranh giới WASM và chạy trực tiếp trong heap của V8
- Kết quả benchmark theo từng lần gọi
Fixture TypeScript WASM Mức tăng tốc simple-table 9.3µs 20.5µs 2.2 lần contact-form 13.4µs 61.4µs 4.6 lần dashboard 19.4µs 57.9µs 3.0 lần - Chỉ riêng việc loại bỏ WASM đã giúp giảm mạnh chi phí cho mỗi lần gọi, nhưng sự kém hiệu quả trong cấu trúc streaming vẫn còn tồn tại
Vấn đề O(N²) trong phân tích streaming và cách cải thiện
- Khi đầu ra của LLM được truyền theo nhiều chunk, việc phải phân tích lại toàn bộ chuỗi tích lũy mỗi lần gây ra sự kém hiệu quả O(N²)
- Ví dụ: tài liệu 1000 ký tự được phân tích 50 lần, mỗi lần 20 ký tự → tổng cộng xử lý 25.000 ký tự
- Giải pháp là đưa vào bộ nhớ đệm tăng dần theo câu (incremental caching)
- Các câu đã hoàn thành được lưu vào cache, chỉ câu đang dở mới bị phân tích lại
- AST đã cache được hợp nhất với AST mới để trả kết quả
- Benchmark theo toàn bộ luồng stream
Fixture TS ngây thơ TS tăng dần Mức tăng tốc simple-table 69µs 77µs Không có contact-form 316µs 122µs 2.6 lần dashboard 840µs 255µs 3.3 lần - Càng nhiều câu, hiệu quả của cache càng lớn và tổng thông lượng được cải thiện theo tuyến tính
Bài học về việc sử dụng WASM
- Trường hợp phù hợp
- Tác vụ nặng về tính toán, ít tương tác: xử lý ảnh/video, mã hóa, mô phỏng vật lý, codec âm thanh, v.v.
- Port thư viện native hiện có: SQLite, OpenCV, libpng, v.v.
- Trường hợp không phù hợp
- Phân tích văn bản có cấu trúc thành object JS: chi phí tuần tự hóa chiếm ưu thế
- Các hàm nhận đầu vào ngắn nhưng bị gọi thường xuyên: chi phí ranh giới lớn hơn cả tính toán
- Bài học cốt lõi
- Cần profiling điểm nghẽn trước khi chọn ngôn ngữ
- Truyền object trực tiếp bằng
serde-wasm-bindgentốn kém hơn - Cải thiện độ phức tạp thuật toán hiệu quả hơn việc đổi ngôn ngữ
- WASM và JS không chia sẻ heap, nên chi phí chuyển đổi luôn tồn tại
Kết quả cuối cùng: Nhờ chuyển sang TypeScript và áp dụng incremental caching, hệ thống đạt mức tăng hiệu năng 2,2~4,6 lần cho mỗi lần gọi và 2,6~3,3 lần cho toàn bộ stream
2 bình luận
Chẳng phải có lẽ đây là một bài viết mỉa mai kiểu vòng vo về việc tối ưu hiệu năng Rust ở mức độ cao sao..
Ý kiến trên Hacker News
Điểm cốt lõi thật sự không phải là TypeScript so với Rust, mà là việc sửa thuật toán streaming từ O(N²) xuống O(N)
Chỉ riêng thay đổi này, được thực hiện bằng cách cache theo đơn vị câu lệnh (
statement), đã mang lại mức cải thiện 3,3 lầnTách khỏi chuyện chọn ngôn ngữ nào, đây mới là nguyên nhân chính khiến độ trễ (latency) mà người dùng cảm nhận được được cải thiện
Có cảm giác tiêu đề đã đánh giá thấp điểm kỹ thuật thú vị này
Bản thân bài viết thì thú vị, nhưng dạo này tôi đã quá mệt với những tiêu đề câu click quá đà
Họ đo thời gian của từng lần gọi rồi dùng trung vị (median), nhưng trong môi trường trình duyệt có logic phòng thủ timing attack trong JS engine, nên tôi nghi ngờ độ chính xác
Câu chuyện “viết lại mã từ ngôn ngữ L sang M rồi nhanh hơn” là điều quá đỗi bình thường
Vì đó là cơ hội để gỡ rối các quyết định chồng chéo và sai lầm, đồng thời áp dụng cách tiếp cận tốt hơn mới xuất hiện
Thực ra ngay cả khi L=M cũng vậy: tăng tốc không đến từ ngôn ngữ, mà đến từ quá trình viết lại và thiết kế lại
Tôi từng đào sâu hơn để cải thiện hiệu năng tuần tự hóa đối tượng ở ranh giới giữa Rust và JS
Cách tiếp cận của serde có vẻ không tốt về mặt hiệu năng, nên tôi đã tổng hợp một nỗ lực cải thiện nó trong bài blog của mình
Tôi từng thắc mắc vì sao Open UI lại không làm việc gì liên quan đến WASM
Nhưng rồi công ty mới này lại dùng tên Open UI, nên khá dễ gây nhầm lẫn
Vốn dĩ Open UI W3C Community Group là nhóm đã hơn 5 năm nay xây dựng các tiêu chuẩn như popover của HTML, select có thể tùy biến, invoker command, accordion, v.v.
Họ thực sự đang làm công việc rất tuyệt
Họ nói đã tích hợp serde-wasm-bindgen trong nỗ lực “bỏ qua việc JSON đi một vòng”, nhưng rốt cuộc nó trông giống như phát minh lại JSON ở dạng nhị phân
JSON trên V8 hiện nay đã được tối ưu hóa rất mạnh, và các triển khai như simdjson có thể xử lý ở mức hàng gigabyte mỗi giây
Tôi không nghĩ JSON có nhiều khả năng là nút thắt cổ chai
Tôi thực sự rất thích thiết kế của blog đó
Đặc biệt là thanh bên ‘scrollspy’ làm nổi bật heading theo vị trí cuộn
Theo Claude nói thì có vẻ nó được dựng bằng fumadocs.dev
Tôi không thực sự hiểu mục đích của bộ phân tích Rust WASM
Bài viết không làm rõ phần đó, nên cần giải thích thêm
Điều này dường như nhằm ngăn rò rỉ thông tin do prompt injection
Bộ phân tích sẽ biên dịch các chunk được stream từ LLM để dựng UI theo thời gian thực
Trước đây họ khởi động lại parser từ đầu cho mỗi chunk, nhưng sau đó đổi sang cách xử lý tăng dần, và trong quá trình port từ Rust sang TypeScript, hiệu năng đã cải thiện đáng kể
Tôi từng thắc mắc liệu TypeScript dạo này có đang chạy trên nền Golang hay không
Nói đùa thôi, nhưng biết đâu nếu lại viết lại bằng Rust thì sẽ có thêm mức tăng hiệu năng gấp 3 lần nữa /s