10 điểm bởi spilist2 2021-10-09 | 2 bình luận | Chia sẻ qua WhatsApp

Giới thiệu

  • Khi chạy JS trong trình duyệt, JS engine của trình duyệt được tối ưu rất tốt nên thực thi nhanh, nhưng hiện nay JS cũng được dùng rất nhiều trong các môi trường khác. (serverless, máy chơi game, iOS, v.v.)

  • WASM là công nghệ giúp chạy JS nhanh trong các runtime như vậy.

Cách hoạt động

  • Nếu có JS engine, mã JS sẽ được chuyển thành bytecode thông qua interpreter và JIT compiler, v.v.

  • Trong môi trường không có JS engine, phải phân phối kèm JS engine cùng với mã; bằng cách phân phối JS engine dưới dạng mô-đun WASM, nó có thể trở nên portable trên nhiều môi trường khác nhau.

  • Mã JS sẽ chạy bên trong một JS engine được cô lập trong WASM engine.

  • JS engine mà WASM engine sử dụng là SpiderMonkey, và Firefox cũng dùng nó.

  • Bản thân WASM không thể tự tạo ra mã máy, nên phải đi qua bước biên dịch bằng JS.

  • Nhưng vì không thể dùng JIT, WASM lẽ ra phải chậm. Vậy WASM rốt cuộc làm thế nào để “tăng tốc” việc thực thi JS?

WASM được dùng ở đâu

Dùng JS trên iOS (hoặc môi trường không dùng được JIT)

- Máy chơi game, ứng dụng iOS không đặc quyền, smart TV, v.v. không thể dùng JIT vì lý do bảo mật.

    (→ Ở đây tác giả nói như thể việc JIT compilation có vấn đề bảo mật là điều hiển nhiên, nhưng tôi tìm hiểu vẫn chưa rõ lý do.)

- Vì vậy ở những nơi này phải dùng interpreter, nhưng thực ra các ứng dụng chạy trên các nền tảng như thế thường chạy rất lâu và có lượng mã lớn, nên tốt hơn là tránh việc bị chậm đi do dùng interpreter.

- Vậy làm sao vẫn dùng được JS mà tránh được vấn đề suy giảm hiệu năng của interpreter?

Dùng JS trong serverless

- Môi trường serverless có JIT, nhưng vấn đề là thời gian cold start dài làm tăng latency. (chỉ riêng việc nạp engine đã mất ít nhất 5 ms)

- Có các kỹ thuật tối ưu để che giấu cold start time, nhưng khi tầng mạng tốt lên (ví dụ QUIC) thì chúng dần bớt ý nghĩa, và khi chạy đồng thời nhiều hàm serverless thì các kỹ thuật tối ưu đó cũng không còn hiệu quả mấy.

- Có thể tránh cold start time bằng cách tái sử dụng instance, nhưng điều đó cũng có nghĩa là trạng thái được chia sẻ giữa các request, và trở thành rủi ro bảo mật.

- Vì những lý do này, trong thực tế người ta cũng ngày càng hay nhét nhiều thứ vào một hàm serverless duy nhất thay vì tuân theo best practice.

- Tức là nếu chỉ giải quyết được vấn đề cold start, thì sẽ không cần đến nhiều kỹ thuật né tránh khác và rất nhiều vấn đề sẽ được xử lý luôn.
  • WASM bọc và cô lập JS, còn bản thân mã WASM thì ngắn và đơn giản nên cũng dễ giám sát hơn, đồng thời giảm rủi ro bảo mật.

JS engine dành nhiều thời gian vào đâu

Giai đoạn khởi tạo

- (khởi tạo engine) Phần này tương ứng với serverless. Engine phải tự chuẩn bị cho chính nó và thêm các built-in function vào môi trường. Đây là một trong những lý do cold start của serverless chậm.

- (khởi tạo ứng dụng) Parse hàm thành bytecode, cấp phát bộ nhớ cho biến, gán giá trị cho biến

Giai đoạn runtime

- Từ đây trở đi, throughput sẽ bị ảnh hưởng bởi nhiều điều kiện khác nhau.

    - which language features are used

    - whether the code behaves predictably from the JS engine’s point of view

    - what sort of data structures are used

    - whether the code runs long enough to benefit from the JS engine’s optimizing compiler

Làm cho JS engine nhanh hơn nghĩa là tăng tốc cả hai phần: khởi tạo và runtime. Cụ thể hơn, là giảm thời gian khởi tạo và tăng throughput trong runtime, tức là tốc độ xử lý mã.

Giảm thời gian khởi tạo

  • WASM dùng một pre-initializer tên là Wizer để giảm thời gian khởi tạo. (với ứng dụng nhỏ, JS on WASM nhanh hơn khoảng 13 lần so với JS isolate)

    • Ở bước build trước khi phân phối mã, pre-initializer sẽ chạy thử toàn bộ mã JS đến hết giai đoạn khởi tạo một lần.

    • Làm như vậy thì trong linear memory của JS engine, mã JS đã được lưu dưới dạng bytecode và việc cấp phát bộ nhớ cũng đã hoàn tất.

    • Sau đó chỉ việc sao chép nguyên trạng này và gắn vào data section của WASM.

  • Khi JS engine được instantiate, nó có thể truy cập toàn bộ dữ liệu trong data section. Nếu cần một vùng nhớ cụ thể thì chỉ việc sao chép từ data section sang. Vì vậy không cần start time, và đó là lý do gọi là pre-initialization.

  • Hiện tại data section được gắn trong cùng mô-đun với JS engine, nhưng trong tương lai dự định dùng module linking để tách data section thành mô-đun riêng, nhờ đó nhiều ứng dụng có thể dùng chung JS engine.

  • Và thực ra kỹ thuật pre-khởi tạo này không nhất thiết chỉ áp dụng cho JS engine; đây là một khái niệm có thể dùng cho bất kỳ runtime nào như Python, Ruby, Lua, v.v.

Tăng throughput

  • Nếu mã JS chỉ chạy trong thời gian ngắn thì vốn dĩ cũng không đi qua JIT, nên throughput của WASM sẽ tương tự trình duyệt. Nhưng với mã chạy lâu, sự khác biệt throughput do có hay không có JIT là rất lớn.

  • WASM không thể dùng JIT, nên thay vào đó nó chọn cách biên dịch AOT (ahead-of-time), đồng thời tận dụng những kỹ thuật có thể lấy từ JIT.

  • Một trong các kỹ thuật tối ưu của JIT là inline caching: giữ lại các đoạn mã đã từng chạy trước đó để tái sử dụng.

  • Trong WASM, các pattern thường dùng trong JS được tạo sẵn thành stub. Ví dụ như truy cập thuộc tính của object.

    • Bình thường để xử lý đúng việc truy cập thuộc tính của object thì cần thông tin shape và offset, nhưng AOT không thể biết trước những thứ này.

    • Tuy nhiên có thể chuẩn bị sẵn một stub truy cập thuộc tính nhận shape và offset làm tham số. Mã stub này có thể được tái sử dụng ở nhiều nơi.

  • WASM tạo sẵn các common pattern như vậy thành stub. Điều này không phụ thuộc vào việc mã JS thực tế trông như thế nào. Nhờ đó có thể giảm lượng mã máy mà JS engine phải tạo ra, giảm thời gian khởi tạo và cải thiện cache locality.

  • Đã xác nhận rằng chỉ cần chuẩn bị 2kb stub như vậy là có thể bao phủ khoảng 95% mã JS thực tế.

  • Vì kỹ thuật này tối ưu theo kiểu ahead-of-time, tức là tối ưu khi chưa biết nội dung mã (không profiling), nên nếu profiling nhiều hơn thì vẫn có thể tối ưu thêm giống như JIT.

    • Tuy nhiên bản thân profiling cũng không hề dễ, nên họ vẫn đang tiếp tục nỗ lực.

2 bình luận

 
kunggom 2021-10-10

Về các vấn đề bảo mật liên quan đến JIT, trước đây đã từng có nhắc đến nội dung này trong một bài viết trên blog của nhóm MS Edge được giới thiệu ở đây. Về cơ bản, do engine JIT rất phức tạp nên không chỉ làm tăng bề mặt tấn công, mà các phương pháp như tối ưu hóa suy đoán (Speculative Optimization) được áp dụng trong JIT để cải thiện hiệu năng dường như cũng có xu hướng lặp đi lặp lại việc gây ra những vấn đề bảo mật theo các mẫu nhất định. Vì lý do này, người ta nói rằng tỷ lệ lỗ hổng bảo mật liên quan đến JIT trong các lỗi bảo mật của trình duyệt web là khá cao.

https://vi.news.hada.io/topic?id=4771

https://microsoftedge.github.io/edgevr/posts/Super-Duper-Secure-Mode/

https://docs.google.com/spreadsheets/d/…

 
spilist2 2021-10-10

Ôi, cảm ơn nhé! Mãi mà tôi lại không vào xem GeekNews.