Trước khi được công nhận bởi W3C là một trong tứ trụ của công nghệ web, WebAssembly vẫn chỉ được chú ý bởi một nhóm nhỏ các chuyên gia, các nhà phát triển. Đã có rất nhiều tài liệu, hội thảo giảng giải về công nghệ tương lai này và loạt bài viết A cartoon intro to WebAssembly của Lin Clark đã gây nhiều ấn tượng. Vì tầm quan trọng của WebAssembly, bài viết này sẽ trình bày lại một số khái niệm quan trọng dựa trên loạt bài viết của Lin Clark một cách cô động nhất.
Trình biên dịch JIT (Just – In – Time compiler)
Vì ngôn ngữ máy tính (machine language) quá khó để có thể lập trình nên các chuyên gia đã phát minh ra các ngôn ngữ lập trình như C, C++, JavaScript, v.v. Các ngôn ngữ này được gọi là các ngôn ngữ cấp cao vì chúng gần với ngôn ngữ con người. Do đó, mục đích cuối cùng của việc thực thi một chương trình máy tính là chuyển những đoạn mã được viết bởi các ngôn ngữ cấp cao thành các đoạn mã ngôn ngữ máy (các bit 0 và 1).
Có hai dạng chương trình phụ trách việc chuyển đổi này là trình biên dịch (compiler) và trình thông dịch (interpreter). Bảng dưới đây thực hiện một so sánh giữa hai chương trình này:
Trình biên dịch (compiler) |
Trình thông dịch (interpreter) |
Chuyển toàn bộ các lệnh chương trình sang ngôn ngôn ngữ máy trong một lần duy nhất. | Chuyển từng lệnh một (line by line) của chương trình sang ngôn ngữ máy |
Khởi đầu chậm do phải duyệt qua tất cả các lệnh của chương trình trước khi chuyển sang ngôn ngữ máy | Khởi đầu nhanh vì từng lệnh được chuyển sang ngôn ngữ máy ngay lập tức |
Thực thi nhanh khi gặp các lệnh điều khiển như lặp hay điều kiện | Chậm khi gặp các lệnh điều khiển như lặp hay so sánh |
Phát hiện lỗi chương trình sau khi toàn bộ chương trình được đọc bởi trình biên dịch | Phát hiện lỗi chương trình sau mỗi lệnh được đọc bời trình thông dịch |
Thường tạo ra các đối tượng trung gian (.obj) | Không tạo ra các đối tượng trung gian (obj) |
Do có quá trình duyệt qua các lệnh chương trình nên trình biên dịch không những phát hiện các lỗi mà còn có thể chỉnh sửa các lỗi này để tạo ra các phiên bản đoạn mã tốt hơn. Quá trình này gọi là tối ưu chương trình (optimization). | Không có thời gian cho optimization. |
JavaScript được tạo ra như là một ngôn ngữ kiểu thông dịch và điều này rất phù hợp với các trình duyệt web trong những ngày đầu tiên của công nghệ web. Nguyên tắc hoạt động của JavaScritp lúc này như sau:
- Parse: quá trình xử lý mã JavaScript thành các định dạng để trình thông dịch có thể thực thi được
- Execute: quá trình thực thi bởi trình thông dịch và thường diễn ra chậm nếu bắt gặp lệnh lặp hay so sánh
- Garbage Collection (GC): giải phóng bộ nhớ sau khi thực thi xong
Tuy nhiên, với sự phát triển mạnh mẽ của công nghệ web, JavaScript ngày càng chậm chạp với phong cách thông dịch của mình nên các nhà phát triển đã phải bắt tay vào cải thiện tốc độ, khả năng thực thi của JavaScript và JIT (Just-In-Time) xuất hiện (2008).
Nguyên tắc hoạt động của JIT
Sở dĩ JavaScript chậm vì chúng phải rất mất thời gian thực thi các lệnh lặp – là điểm yếu của thông dịch. Chi tiết có thể khác nhau ở các trình duyệt nhưng nguyên tắc hoạt động chung của JIT là:
- Thêm một trình giám sát (monitor) các đoạn mã chương trình đang thực thi. Nếu đoạn mã nào đó của chương trình được lặp lại vài lần thì monitor sẽ đánh dấu chúng là warm; nếu đoạn mã nào đó được lặp lại nhiều lần hay rất nhiều lần, chúng được monitor đánh dấu là hot. Hai kiểu đoạn mã này sẽ được tối ưu bởi các kiểu biên dịch khác nhau.
- Các đoạn mã được đánh dấu warm sẽ được xử lý hay tối ưu bởi kiểu biên dịch dựa theo dòng lệnh (baseline compiler) và quá trình xử lý này thường diễn ra nhanh chóng. Khi chương trình đang thực thi, nếu monitor bắt gặp đoạn mã nào giống với (thường là các kiểu biến) các đoạn mã đã được biên dịch thì các đoạn mã đã được biên dịch sẽ được sử dụng. Điều này làm giảm thời gian thực thi và tăng tốc độ thực thiện.
- Các đoạn mã được đánh dấu là hot thì phức tạp hơn và được biên dịch dựa trên một vài giả định (biên dịch lúc này gọi là biên dịch tối ưu optimizing compiler). Các đoạn mã được biên dịch sẽ được kiểm tra xem các giả định có chính xác không, nếu đúng thì đoạn mã đó sẽ được thực thi và tạo ra một phiên bản nhanh hơn so với đoạn mã cũ, ngược lại, JIT sẽ xóa đoạn mã này. Khi JIT xóa các đoạn mã được biên dịch không phù hợp, quá trình thực thi sẽ trở lại với trình thông dịch hay biên dịch kiểu dòng lệnh. Quá trình này gọi là deoptimization (giải tối ưu). Như vậy, quá trình tối ưu/giải tối ưu có thể gây ra những ứng xử không mong đợi cho quá trình thực thi.
Với JIT, quá trình thực thi của JavaScript lúc này sẽ trông như sau:
- Parse: quá trình xử lý mã JavaScript thành các định dạng để trình thông dịch có thể thực thi được
- Compile + optimize: giai đoạn diễn ra quá trình biên dịch bởi baseline compiler và optimizing compiler
- Re-optimize: Khi tối ưu các đoạn mã với optimizing compiler thất bại với các giả định, JIT sẽ cố gắng điều chỉnh lại các giả định và thực hiện tối ưu lần kế tiếp (re-optimize). Số lần tối ưu lại không vượt quá giới hạn cho phép bởi trình duyệt. Nếu tối ưu lại không thành công sẽ là quá trình giải tối ưu.
- Execute: thực thi mã
- Garbage Collection (GC): giải phóng bộ nhớ sau khi thực thi xong
Và hiệu năng của JavaScript có sự cải thiện rất đáng kế:
WebAssembly
JavaScript đã được cải thiện đáng kể nhờ JIT nhưng với sự phát triển rất nhanh của ứng dụng web, rất nhiều thư viện được viết từ nhiều ngôn ngữ lập trình khác nhau cần được tận dụng và khai thác triệt để. Trong quá trình nỗ lực cải thiện hiệu suất của JavaScript, một thứ tuyệt vời đã xuất hiện. Đó là WebAssembly.
Với WebAssemby, chúng ta có thể tận dụng các thư viện từ các ngôn ngôn ngữ lập trình cấp cao như C, C++, Rust và có thể nhiều ngôn ngữ khác trong tương lai (tất nhiên ngoại trừ JavaScript). Việc cho phép các ngôn ngữ khác thực thi trên các trình duyệt web đã là một cuộc cách mạng nhưng WebAssembly còn thực thi nhanh hơn cả JavaScript. Chúng ta có thể thấy được điều đó một cách trực quan như sau (hình trên là thực thi của JavaScript, hình dưới là của WebAssembly):
- Decode: mã WebAssembly không cần trải qua quá trình parse (như mã JavaScript) mà chỉ việc kiểm tra xem có bị lỗi hay không
- Compile + optimize: Giai đoạn này diễn ra nhanh hơn so với giai đoạn cùng tên bởi JavaScript
- Execute: thực thi mã
Cho đến thời điểm này, WebAssembly chưa có quá trình GC.
Mặc dù có một số ưu điểm so với JavaScript nhưng JavaScript vẫn có vị thế riêng của nó và WebAssembly vẫn cần nhiều cải tiến trong tương lại như việc giao tiếp giữa JavaScript và WebAssembly, khả năng làm việc trực tiếp với DOM, xử lý ngoại lệ, v.v. Sẽ là lý tưởng nếu JavaScript và WebAssembly cùng sử dụng song song trong cùng một ứng dụng web.
Hi vọng bài viết này và bài viết https://ngocminhtran.com/2020/02/19/mot-nhap-mon-ve-webassembly/ giúp chúng ta có một cái nhìn rõ hơn về WebAssembly dưới gốc độ lý thuyết lẫn thực hành để có thể tiếp tục tìm hiểu và ứng dụng WebAssembly trong các ứng dụng web.
Ý kiến bài viết