Phát hiện tính năng WebAssembly

Tìm hiểu cách sử dụng các tính năng mới nhất của WebAssembly trong khi vẫn hỗ trợ người dùng trên tất cả các trình duyệt.

WebAssembly 1.0 đã được phát hành cách đây 4 năm, nhưng sự phát triển không dừng lại ở đó. Các tính năng mới được bổ sung thông qua quy trình chuẩn hoá đề xuất. Như thường lệ với các tính năng mới trên web, thứ tự triển khai và tiến trình của những tính năng đó có thể khác nhau đáng kể giữa các công cụ. Nếu muốn sử dụng các tính năng mới đó, bạn cần đảm bảo không có người dùng nào bị bỏ sót. Trong bài viết này, bạn sẽ tìm hiểu một phương pháp để đạt được mục tiêu này.

Một số tính năng mới cải thiện kích thước mã bằng cách thêm hướng dẫn mới cho các thao tác phổ biến, một số tính năng bổ sung dữ liệu hiệu suất mạnh mẽ, và một số tính năng khác cải thiện trải nghiệm của nhà phát triển cũng như khả năng tích hợp với phần còn lại của môi trường web.

Bạn có thể xem danh sách đầy đủ các đề xuất và giai đoạn tương ứng trong kho lưu trữ chính thức hoặc theo dõi trạng thái triển khai trong các công cụ trên trang lộ trình tính năng chính thức.

Để đảm bảo người dùng thuộc tất cả các trình duyệt có thể sử dụng ứng dụng của bạn, bạn cần phải tìm hiểu xem mình muốn sử dụng tính năng nào. Sau đó, hãy chia chúng thành các nhóm dựa trên khả năng hỗ trợ của trình duyệt. Sau đó, hãy biên dịch cơ sở mã riêng cho từng nhóm đó. Cuối cùng, về phía trình duyệt, bạn cần phát hiện các tính năng được hỗ trợ và tải gói JavaScript và Wasm tương ứng.

Tính năng chọn và nhóm

Hãy cùng tìm hiểu các bước đó bằng cách chọn một số bộ tính năng tuỳ ý làm ví dụ. Giả sử tôi đã xác định rằng tôi muốn sử dụng SIMD, luồng và xử lý ngoại lệ trong thư viện của mình vì các lý do liên quan đến kích thước và hiệu suất. Hỗ trợ trình duyệt của họ như sau:

Bảng hiển thị hỗ trợ trình duyệt cho các tính năng được chọn.
Xem bảng tính năng này trên webassembly.org/roadmap.

Bạn có thể chia trình duyệt thành các nhóm thuần tập sau để đảm bảo mỗi người dùng đều có được trải nghiệm tối ưu nhất:

  • Trình duyệt dựa trên Chrome: Threads, SIMD và xử lý ngoại lệ đều được hỗ trợ.
  • Firefox: Thread và SIMD được hỗ trợ, nhưng không hỗ trợ xử lý ngoại lệ.
  • Safari: Các luồng được hỗ trợ, SIMD và xử lý ngoại lệ thì không.
  • Các trình duyệt khác: giả định chỉ hỗ trợ WebAssembly cơ sở.

Bảng chi tiết này chia theo việc hỗ trợ tính năng trong phiên bản mới nhất của mỗi trình duyệt. Trình duyệt hiện đại có thể dùng lâu dài và tự động cập nhật, vì vậy, trong hầu hết các trường hợp, bạn chỉ cần lo lắng về bản phát hành mới nhất. Tuy nhiên, miễn là bạn thêm WebAssembly cơ sở làm nhóm thuần tập dự phòng, bạn vẫn có thể cung cấp một ứng dụng hoạt động ngay cả cho người dùng có trình duyệt lỗi thời.

Biên dịch cho các bộ tính năng khác nhau

WebAssembly không có cách tích hợp sẵn để phát hiện các tính năng được hỗ trợ trong thời gian chạy. Do đó, tất cả hướng dẫn trong mô-đun phải được hỗ trợ trên mục tiêu. Do đó, bạn cần biên dịch mã nguồn thành Wasm riêng cho từng nhóm tính năng.

Mỗi chuỗi công cụ và hệ thống xây dựng đều khác nhau và bạn cần tham khảo tài liệu của trình biên dịch riêng để biết cách tinh chỉnh các tính năng đó. Để đơn giản, tôi sẽ dùng một thư viện C++ gồm một tệp trong ví dụ sau và trình bày cách biên dịch thư viện đó bằng Emscripten.

Tôi sẽ dùng SIMD thông qua quy trình mô phỏng SSE2, các luồng thông qua tính năng hỗ trợ thư viện Pthreads và chọn giữa xử lý ngoại lệ Wasmtriển khai JavaScript dự phòng:

# First bundle: threads + SIMD + Wasm exceptions
$ emcc main.cpp -o main.threads-simd-exceptions.mjs -pthread -msimd128 -msse2 -fwasm-exceptions
# Second bundle: threads + SIMD + JS exceptions fallback
$ emcc main.cpp -o main.threads-simd.mjs -pthread -msimd128 -msse2 -fexceptions
# Third bundle: threads + JS exception fallback
$ emcc main.cpp -o main.threads.mjs -pthread -fexceptions
# Fourth bundle: basic Wasm with JS exceptions fallback
$ emcc main.cpp -o main.basic.mjs -fexceptions

Bản thân mã C++ có thể sử dụng #ifdef __EMSCRIPTEN_PTHREADS__#ifdef __SSE2__ để chọn theo điều kiện giữa phương thức triển khai song song (luồng và SIMD) của cùng một hàm và phương thức triển khai nối tiếp tại thời điểm biên dịch. Ứng dụng sẽ hiển thị như sau:

void process_data(std::vector<int>& some_input) {
#ifdef __EMSCRIPTEN_PTHREADS__
#ifdef __SSE2__
  // …implementation using threads and SIMD for max speed
#else
  // …implementation using threads but not SIMD
#endif
#else
  // …fallback implementation for browsers without those features
#endif
}

Việc xử lý ngoại lệ không cần có lệnh #ifdef, vì bạn có thể dùng lệnh này theo cách tương tự từ C++, bất kể phương thức triển khai cơ bản được chọn thông qua cờ biên dịch là gì.

Đang tải đúng gói

Sau khi tạo gói cho tất cả nhóm thuần tập tính năng, bạn cần tải đúng gói từ ứng dụng JavaScript chính. Để làm việc đó, trước tiên, hãy phát hiện xem tính năng nào được hỗ trợ trong trình duyệt hiện tại. Bạn có thể làm việc đó thông qua thư viện wasm-feature-detect. Bằng cách kết hợp gói đó với tính năng nhập động, bạn có thể tải gói được tối ưu hoá nhất trong bất kỳ trình duyệt nào:

import { simd, threads, exceptions } from 'https://unpkg.com/wasm-feature-detect?module';

let initModule;
if (await threads()) {
  if (await simd()) {
    if (await exceptions()) {
      initModule = import('./main.threads-simd-exceptions.mjs');
    } else {
      initModule = import('./main.threads-simd.mjs');
    }
  } else {
    initModule = import('./main.threads.mjs');
  }
} else {
  initModule = import('./main.basic.mjs');
}

const Module = await initModule();
// now you can use `Module` Emscripten object like you normally would

Từ cuối cùng

Trong bài đăng này, tôi đã hướng dẫn bạn cách chọn, xây dựng và chuyển đổi giữa các gói cho nhiều bộ tính năng.

Khi số lượng tính năng tăng lên,số lượng nhóm thuần tập tính năng có thể trở nên không thể duy trì. Để giảm nhẹ vấn đề này, bạn có thể chọn nhóm thuần tập tính năng dựa trên dữ liệu người dùng trong thế giới thực, bỏ qua những trình duyệt ít phổ biến hơn và để họ quay trở lại nhóm thuần tập ít tối ưu hơn một chút. Miễn là ứng dụng của bạn vẫn hoạt động cho tất cả người dùng, phương pháp này có thể mang lại sự cân bằng hợp lý giữa tính năng nâng cao tăng dần và hiệu suất trong thời gian chạy.

Trong tương lai, WebAssembly có thể có một phương pháp tích hợp để phát hiện các tính năng được hỗ trợ và chuyển đổi giữa các cách triển khai khác nhau của cùng một chức năng trong mô-đun. Tuy nhiên, cơ chế đó sẽ là một tính năng sau MVP mà bạn cần phát hiện và tải có điều kiện bằng phương pháp nêu trên. Cho đến lúc đó, phương pháp này vẫn là cách duy nhất để xây dựng và tải mã bằng các tính năng WebAssembly mới trên tất cả trình duyệt.