Đã hơn **8 năm nay**, tôi liên tục nghe về cái chết sắp xảy đến của frontend – hay ít nhất là của JavaScript. Một trong những công cụ được cho là sẽ quét sạch nó khỏi bản đồ chính là **WebAssembly** (WASM). Từ lâu, tôi đã được nghe rằng JavaScript về cơ bản đã trở thành “di sản” và rằng chúng ta sẽ sớm viết các ứng dụng frontend bằng Rust hoặc Go.
Trong khi đó, giữa vô vàn bài viết trực tuyến vừa gây hoảng sợ về sự thống trị của WASM, vừa là những phân tích kỹ thuật quá sâu khó tiếp cận với người mới bắt đầu, tôi muốn **làm rõ hơn về WASM** và trình bày một góc nhìn trung thực về:
- Tại sao nó **sẽ không thể thay thế JavaScript sớm**?
- Và **khi nào nó thực sự vượt trội JavaScript**?
Mục lục
WASM và JS: Cái nhìn tổng quan về hiệu năng (TL;DR và Demo)
Hãy bắt đầu với một minh chứng trực quan nhất. Dưới đây là một demo trực tiếp so sánh hiệu năng giữa WASM và JS, kèm theo kho mã nguồn GitHub để bạn tự kiểm chứng.
Qua demo này, bạn sẽ nhận thấy:
- JavaScript tự thân đã **rất nhanh** và hoàn toàn phù hợp cho hầu hết các tác vụ phát triển web hàng ngày.
- Nhưng khi đối mặt với **các phép tính toán nặng nề**, WASM cho thấy **lợi thế vượt trội**, ngay cả khi chưa áp dụng các tối ưu hóa nâng cao như SIMD.
Bạn có nhận được kết quả tương tự trên máy của mình không? Đừng ngần ngại chia sẻ trong phần bình luận nhé!
⚠️ Lưu ý quan trọng: Tôi cố ý không xác thực các giá trị đầu vào trong demo này. Hãy cẩn thận khi thử nghiệm, rất dễ làm treo tab trình duyệt của bạn.
Demo trực tiếp: https://sylwia-lask.github.io/wasm-js-bench/
Kho mã nguồn GitHub: https://github.com/sylwia-lask/wasm-js-bench
WebAssembly được viết bằng ngôn ngữ nào?
Để hiểu sâu hơn, chúng ta cần nắm rõ những kiến thức cơ bản. Mã nguồn được biên dịch thành WASM thường được viết bằng các **ngôn ngữ cấp thấp** như:
- Rust
- Go
- C / C++
Tại sao không phải Python hay JavaScript?
Một điểm cần làm rõ: WebAssembly **không phải là mã máy CPU gốc**. Nó là một **định dạng bytecode di động** mà các trình duyệt sau đó biên dịch JIT (Just-In-Time) thành mã máy – tương tự như cách chúng làm với JavaScript.
Vậy tại sao lại là những ngôn ngữ cấp thấp kia? Vấn đề chính với Python hay JavaScript không phải là “trình biên dịch nặng”, mà là:
- Kiểu dữ liệu động (dynamic typing)
- Các runtime lớn
- Thu gom rác tự động (garbage collection)
- Mô hình bộ nhớ không thể đoán trước
WASM được thiết kế dựa trên một **mô hình thực thi và bộ nhớ đơn giản, có thể dự đoán được**, điều này phù hợp hơn nhiều với các ngôn ngữ như Rust hoặc C++. (Đúng vậy, WASM GC đang tồn tại và phát triển, nhưng vẫn còn xa mới trở nên phổ biến trong môi trường sản xuất).
Lý do chọn Rust cho demo
Trong demo của mình, tôi đã sử dụng **Rust**, chủ yếu vì nó có một **runtime gần như bằng không** và cung cấp khả năng kiểm soát bộ nhớ rất rõ ràng.
Thành thật mà nói, ở cấu hình cơ bản, việc thiết lập Rust không hề khó khăn. Tôi thậm chí đã cài đặt được nó trên **Windows**! Tất cả những gì tôi phải làm là cài đặt toàn bộ công cụ Visual Studio – chính xác là lý do tại sao chúng ta yêu thích Linux!
Bản thân Rust thường được mô tả là _”TypeScript phiên bản nâng cấp”_. Rõ ràng chúng không giống nhau, nhưng:
- Nó rất nghiêm ngặt về kiểu dữ liệu.
- Không có kiểu dữ liệu `any` như trong TypeScript.
- Điều này có thể gây khó chịu lúc đầu cho các nhà phát triển TypeScript…
…nhưng bạn sẽ nhanh chóng đánh giá cao việc nó ngăn chặn được bao nhiêu lỗi tiềm ẩn. Rust cũng giới thiệu các khái niệm như **ownership** (quyền sở hữu) và **borrowing** (mượn), nhưng tôi sẽ không đi sâu vào chi tiết ở đây.
Biên dịch code sang WebAssembly
Quá trình biên dịch mã nguồn sang WASM khá đơn giản và có thể dễ dàng tích hợp vào quy trình CI/CD của bạn.
Nếu bạn tò mò về kết quả sau khi biên dịch trông như thế nào và cách nó được sử dụng từ JavaScript, hãy kiểm tra kho mã nguồn GitHub. Tôi đã cố ý đưa các tệp đã biên dịch vào đó để bạn có thể xem xét.
Khi nào nên dùng WASM, khi nào JS tối ưu hơn? ⚔️
Bạn có thể tự mình kiểm tra mọi thứ trong demo. Mã nguồn benchmark được công khai trên GitHub, vì vậy bạn có thể xác minh rằng tôi không gian lận chống lại WASM hay JS.
Một lần nữa: các giá trị đầu vào hoàn toàn do bạn chịu trách nhiệm – rất dễ làm treo tab trình duyệt.
JavaScript vẫn cực kỳ nhanh cho tác vụ thông thường 🏎️
WASM *thực sự* nhanh một cách đáng kinh ngạc – nhưng các nhà phát triển công cụ trình duyệt cũng không hề ngủ quên. Biên dịch JIT trong các công cụ như **V8** (Chrome) hoặc **SpiderMonkey** (Firefox) hoạt động cực kỳ hiệu quả.
Vì vậy, nếu bạn:
- Đang viết một ứng dụng frontend thông thường.
- Một ứng dụng CRUD đơn giản.
- Có nhiều tương tác với DOM.
… thì JavaScript thuần túy hoàn toàn có thể **nhanh hơn tổng thể**. Ngay cả với các phép tính khá nặng nhưng đơn giản.
Ví dụ: **Phép nhân ma trận n × n và trả về một số duy nhất (mod 1 000 000 007)**
JavaScript xử lý loại toán học này rất tốt và thường xuất hiện “nhanh hơn” WASM. Tại sao lại “xuất hiện”? Bởi vì chúng ta cũng phải nhớ về **chi phí khi vượt qua ranh giới JS ↔ WASM**.
Ranh giới giữa JS và WASM là tốn kém – bạn càng vượt qua nó thường xuyên và sao chép nhiều dữ liệu, bạn càng nhanh chóng loại bỏ mọi lợi ích về hiệu suất.
Dưới đây là một ví dụ minh họa từ demo, cho thấy JavaScript có thể vượt trội trong các tác vụ tính toán không quá phức tạp, đặc biệt khi có nhiều tương tác qua lại giữa hai môi trường:
// Ví dụ giả định về hàm tính toán trong JS và WASM
// Khi gọi WASM quá nhiều lần với dữ liệu nhỏ, overhead sẽ tăng lên
function calculateMatrixInJS(matrixA, matrixB) { /* ... */ }
function calculateMatrixInWASM(matrixA, matrixB) { /* ... */ } // Có overhead khi gọi từ JS
Điểm yếu của JavaScript: Tính toán siêu nặng 💥
Mọi thứ thay đổi khi chúng ta đi vào **các phép tính toán thực sự nặng**.
Lấy ví dụ: **n! mod 1 000 000 007** (giai thừa của n).
Ở đây, JavaScript trở nên chậm hơn đáng kể so với WASM. Trên ví dụ nhỏ này, thời gian tuyệt đối vẫn thấp – nhưng hãy tưởng tượng những thứ như:
- Mô phỏng vật lý
- Bộ giải số
- Các mô hình thống kê lớn
Vấn đề cốt lõi nằm ở các con số. JavaScript sử dụng **số dấu phẩy động độ chính xác kép IEEE-754**, và đối với các giá trị rất lớn, nó phải chuyển sang `BigInt`, vốn _rất chậm_. Rust, mặt khác, thoải mái hoạt động trên các kiểu dữ liệu như `u64`. Riêng điều đó đã tạo ra một khoảng cách hiệu suất khổng lồ.
Điều thú vị là, kết quả có thể khác nhau tùy thuộc vào:
- Trình duyệt
- Công cụ JS (engine)
- Phần cứng
Ví dụ, trên máy của tôi, khoảng cách này nhỏ hơn nhiều trong Firefox so với Chrome.
Xử lý ảnh và video: Khi WASM tỏa sáng 🖼️
WASM thường được nhắc đến trong bối cảnh:
- Xử lý hình ảnh
- Video
- Âm thanh
Và đúng vậy – nó *có thể* tuyệt vời ở những lĩnh vực đó.
Nhưng đây là một bất ngờ thú vị: Khi tôi thử một **benchmark rất đơn giản** – chỉ áp dụng hiệu ứng Gaussian blur lên một hình ảnh – **JS hoàn toàn đánh bại WASM**. Tại sao? Bởi vì tôi phải sao chép toàn bộ `Uint8Array` vào bộ nhớ WASM.
WASM chỉ bắt đầu thắng thế khi tôi:
- Xây dựng một **pipeline xử lý hình ảnh hoàn chỉnh**.
- Áp dụng nhiều bộ lọc liên tiếp.
- Thực hiện càng nhiều công việc càng tốt _bên trong_ WASM.
Bài học rút ra:
“WASM rất tuyệt cho hình ảnh” là đúng – **nhưng chỉ khi các thao tác đủ nặng**. (Thực tế, một số tối ưu hóa (như bộ nhớ chia sẻ) có thể giảm chi phí này, nhưng đổi lại là sự phức tạp).
Ngay cả Gaussian blur, một tác vụ xử lý hàng triệu pixel, cũng là thứ mà JS xử lý đáng ngạc nhiên tốt.
// Minh họa cấu trúc pipeline xử lý ảnh trong WASM
// Thay vì gọi WASM cho từng pixel hoặc từng bộ lọc nhỏ
// Toàn bộ logic xử lý ảnh được đóng gói và chạy bên trong WASM
function processImageWithWASM(imageData) {
// 1. Copy imageData (Uint8Array) to WASM memory ONCE
// 2. Call WASM function to apply multiple filters (blur, sharpen, resize, etc.)
// 3. Get processed imageData back from WASM memory ONCE
return wasmModule.applyFullImagePipeline(imageData);
}
Các lựa chọn thay thế cho WebAssembly 🧰
Cũng cần nhớ rằng:
- Web Workers
- OffscreenCanvas
- Các giải pháp dựa trên GPU (như WebGPU)
… đôi khi có thể là những lựa chọn thay thế hoàn hảo. Không phải mọi tác vụ nặng đều tự động cần WASM – đôi khi **JS + Web Workers** đã là “đủ tốt”.
Vậy, JavaScript có “chết” không? ☠️
Không hề! 😄
Như bạn đã thấy:
- JavaScript **vẫn rất sống động**.
- Các framework hiện đại thừa sức đáp ứng công việc frontend hàng ngày.
Nhưng khi bạn *thực sự* có các phép tính toán nặng:
- Đừng ngần ngại sử dụng WASM.
- Nó thực sự là một công cụ **dành cho các nhà phát triển bình thường**, không chỉ riêng các lập trình viên hệ thống “hardcore”.
WASM không phải là một sự thay thế cho JavaScript – nó là một **bổ sung mạnh mẽ**, đặc biệt là như một công cụ tính toán hiệu năng cao.
Đến lượt bạn! 👋
Bạn đã nhận được kết quả như thế nào trong các benchmark?
Hãy chia sẻ chúng trong phần bình luận – tôi thực sự tò mò đấy! 😊



