- Porffor là một dự án nghiên cứu biên dịch JavaScript trước thay vì tại thời điểm chạy, tạo ra WebAssembly và binary native
- Nhờ cách không đóng gói kèm interpreter, dự án đặt mục tiêu tạo đầu ra nhỏ hơn và nhanh hơn 10–30 lần so với các dự án JS→Wasm hiện có
- Ngay cả với bản build native, do không đóng gói runtime, kích thước binary có thể giảm tới 1000 lần; ví dụ giảm từ khoảng 90MB xuống dưới 100KB
- Được viết bằng JS, không có
eval, và nhấn mạnh kiến trúc hỗ trợ native TypeScript mà không cần bước build riêng - AOT thuận lợi cho tối ưu hóa bằng phân tích tĩnh và biên dịch trước khi chạy, nhưng khó xử lý việc đánh giá JS động như
eval, và hiện vẫn ở giai đoạn đầu khi nhiều JS chưa chạy được
Cách thực thi mà Porffor tạo ra
- Porffor là một dự án nghiên cứu biên dịch Ahead-of-Time JavaScript sang WebAssembly và binary native
- Binary TypeScript được biên dịch bằng Porffor đang phục vụ trang này
- Được viết từ đầu với AOT trong tâm trí, dự án có cấu trúc nhằm thử các tối ưu hóa vốn khó thực hiện trong cách chạy JS truyền thống
Khác biệt giữa đầu ra WebAssembly và native
-
JS → Wasm
- Đầu ra WebAssembly của Porffor nhỏ hơn và nhanh hơn 10–30 lần so với các dự án JS→Wasm hiện có
- Khác biệt cốt lõi nằm ở việc biên dịch JS trực tiếp và không bundle interpreter
- Chạy JS trong Wasm cho phép thực thi trong sandbox, nhưng có thể gây tổn thất hiệu năng lớn; Porffor tập trung vào việc giảm chi phí này
- Các trường hợp có thể áp dụng:
- Hosting JS phía server: trong edge runtime, sandboxing bằng Wasm có thể cung cấp thực thi an toàn mà không cần cách ly quá mức
- Overhead thấp của AOT tạo khả năng chạy nhiều khách hàng hơn trên cùng phần cứng với tổn thất hiệu năng tối thiểu so với JIT
- Kháng reverse engineering: JS nhạy cảm có thể khó bị reverse engineering hơn khi ở dạng code đã biên dịch so với obfuscation
-
JS → Native
- Vì JS được biên dịch thực sự mà không đóng gói runtime, kích thước binary có thể nhỏ hơn tới 1000 lần
- Kích thước ví dụ là khoảng 90MB → dưới 100KB
- Nội bộ sẽ biên dịch JS sang C rồi biên dịch sang native, nên ở nơi nào dùng được C thì có thể dùng JS
- Các trường hợp có thể áp dụng:
- Chạy JS nhanh trên embedded, máy chơi game console, v.v.
- Ứng dụng JS CLI nhỏ được biên dịch thành file thực thi một cú nhấp dưới 1MB
Lợi ích và hạn chế của AOT
- Interpreter truyền thống hoặc nhiều tầng JIT phải cân bằng giữa thời gian khởi động và hiệu năng JS
- AOT biên dịch trước rồi mới chạy, nên tốc độ biên dịch quan trọng với trải nghiệm nhà phát triển nhưng không ảnh hưởng đến trải nghiệm người dùng
- Cách này tạo dư địa để thực hiện tối ưu hóa dựa trên phân tích tĩnh như C++ và Rust
- Nhược điểm chính là không có đánh giá JS động như
eval, và phải xây dựng một JS engine mới - Vì vẫn ở giai đoạn đầu, nhiều JS chưa chạy được, nhưng công việc cải thiện đang diễn ra
- Để theo dõi tiến độ tương thích ECMAScript, mỗi commit đều chạy bộ test chính thức Test262
1 bình luận
Các ý kiến trên Hacker News
Oliver, nhà phát triển chính của Porffor, thông báo rằng anh sẽ làm toàn thời gian cho Porffor: https://x.com/canadahonk/status/1818347311417938237
https://news.ycombinator.com/user?id=defunkt
Tôi từng nghĩ đến điều tương tự, nhưng cho rằng rất khó đạt hiệu năng tốt hơn nhiều trong JavaScript. Có lẽ phương án tốt nhất chỉ là transpile JS thành các lời gọi V8 C++
Những tối ưu hóa thật sự ấn tượng xuất hiện khi biên dịch TypeScript hoặc thứ gì đó gần giống vậy. Tận dụng kiểu dữ liệu có thể mang lại lợi ích lớn, còn những phần không có kiểu về cơ bản sẽ rơi về các lời gọi JS chậm. Interface có thể được rút gọn thành bảng hàm ảo hoặc lời gọi trực tiếp, và cũng có thể thao tác trên struct thay vì map. Có thể có kiểu
IntvàFloat, giữ chúng trong thanh ghi rồi hạ xuốngNumberkhi cầnVấn đề cốt lõi là cả TS lẫn V8 đều là các mục tiêu phi tiêu chuẩn thay đổi nhanh. Dự án kiểu này cần một đội ngũ lớn, và riêng việc duy trì tương thích đã là một công việc riêng
Ví dụ đơn giản là TypeScript không phân biệt số nguyên và số dấu phẩy động, mà xử lý tất cả là số. Vì vậy mọi truy cập mảng đều cần chuyển kiểu. Nếu TypeScript được thiết kế để hỗ trợ biên dịch tĩnh, có lẽ nó đã có sự phân biệt này
Vấn đề lớn hơn là subtyping cấu trúc của TypeScript. Do đặc tính này, về thực chất trình biên dịch gần như không thể xác định tĩnh cấu trúc vật lý của các đối số không nguyên thủy được truyền vào hàm. JIT có thể phân tích hình dạng động, nên trong mọi thao tác truy cập trường, hiệu năng có thể tệ hơn JIT
Đã có nhiều công việc xây dựng công cụ phân tích kiểu tĩnh cho JS, và cũng có thể phân tích rất kỹ. Một ví dụ tôi nghĩ đến là TAJS, dù hơi cũ
Giá mà TypeScript cho phép chỉ định kiểu như
integer. Trong biên dịch TS→JS thông thường, dùconst val: intđược xử lý y hệtconst val: number, các runtime hiện đại hiểu TS vẫn có thể tận dụng thông tin bổ sung đóTôi tự hỏi liệu cú pháp như
const counter: Numbercó được chấp nhận khôngKhông rõ site đã thay đổi, hay tôi đang bỏ lỡ điều gì
Ở windmill.dev, khi người dùng triển khai mã, họ dùng Bun build để gộp script và toàn bộ dependency vào một tệp JS duy nhất, rồi load tệp đó để cải thiện cold start và mức dùng bộ nhớ. Vì kích thước bundle, kết quả được lưu trên S3
Nếu có thể bundle mọi thứ thành native thì cuộc chơi sẽ hoàn toàn khác. Dù cold start của Bun có tốt đến đâu, cũng khó thắng được việc chạy native trực tiếp bằng một binary nhỏ
Thật vui khi thấy ngày càng nhiều runtime JS tiếp cận Wasm theo nhiều cách. Dự án này gợi nhớ đến Static Hermes, engine JS của Facebook nhằm tăng tốc cho các dự án React Native trên iOS và Android
Cả hai đều nhắm tới việc tuân thủ JS test262; Porffor hỗ trợ cả đầu ra native lẫn Wasm, trong khi Static Hermes hiện chủ yếu tập trung vào đầu ra native. Porffor được viết bằng JS thuần và hướng tới khả năng tự biên dịch chính nó, còn Static Hermes phụ thuộc vào LLVM. Porffor từng có hỗ trợ async/promise/await còn hạn chế, còn Static Hermes hỗ trợ với một số giới hạn. Static Hermes viết bằng C++, Porffor chủ yếu viết bằng JS. Cả hai đều hỗ trợ TypeScript, nhưng Static Hermes chuyển TS AST sang Flow, còn Porffor hỗ trợ native. Static Hermes có interpreter dự phòng cho các tình huống JS khó biên dịch như
eval, còn Porffor chỉ hỗ trợ biên dịch trướcNhìn chung, tôi kỳ vọng dự án này có thể có đà phát triển và làm cho engine JavaScript ở edge nhanh hơn. Để lại nhận xét với tư cách Syrus từ Wasmer
https://github.com/facebook/hermes/discussions/1137
https://github.com/tc39/test262
https://wasmer.io
Tuy vậy đó không phải trọng tâm của chúng tôi; chúng tôi chủ yếu tập trung vào React Native. Trong môi trường đó, WASM không có nhiều ý nghĩa
Tính năng quan trọng nhất của Static Hermes là trình kiểm tra kiểu bảo đảm tính đúng đắn khi chạy. Porffor rất thú vị, tôi đã theo dõi một thời gian và đang cổ vũ cho dự án thành công
Đây là cách tiếp cận tương tự Kiesel: https://kiesel.dev/
Array.prototype.filter,Math.sin,atobthì một phần đã tự biên dịch chính chúngGần đây Porffor cũng đã bắt đầu hỗ trợ async/promise/await cơ bản. Vẫn chưa hoạt động tốt lắm
JavaScript có một tập con dễ biên dịch, còn phần khó là cái đuôi dài nằm ngoài tập đó. Dù vậy, thật tuyệt khi có nghiên cứu về việc ranh giới nằm ở đâu và có thể thu được bao nhiêu lợi ích từ tập con đó
Tôi thật sự thích việc nó hỗ trợ
String.blink. Việc nhà phát triển có khiếu hài hước và tinh thần nghịch ngợm luôn là một dấu hiệu tốtViệc triển khai cũng nhỏ nhặt cỡ
function() { return "" + this + ""; }, nên ngay cả khi ECMAScript host không phải trình duyệt web thì cũng đáng triển khai. Trong trường hợp đó, nó là tùy chọn. Tôi không nghĩ điều này liên quan đến “hài hước hay nghịch ngợm”String.blinknằm trong test262, nên để đạt mục tiêu của dự án thì thực tế phải hỗ trợ nóTôi tò mò không biết mình đã bỏ lỡ sắc thái tinh tế nào. Tôi không hiểu vì sao “engine JS biên dịch trước” lại là mô tả tốt hơn “trình biên dịch JS-to-Wasm”. Nếu chủ yếu là chiến lược định khung thì cũng ổn
Cơ chế phiên bản được mô tả ở đây có vẻ hơi đáng ngờ
Nếu một thay đổi nào đó gây hồi quy ở một số test Test262 thì số phiên bản cũng có thể phải lùi lại. Nói cách khác, Porffor không thể vừa có số phiên bản tăng đơn điệu, vừa có khả năng để các thay đổi cần thiết gây hồi quy Test262
https://github.com/CanadaHonk/porffor?tab=readme-ov-file#ver...
Trong tiếng Wales, nó có nghĩa là “màu tím”
Thật mới mẻ khi thấy nhiều engine JS cho các mục đích khác nhau xuất hiện
Để nhúng plugin vào ứng dụng, tôi đã làm việc nhằm cung cấp thêm API tương thích Node cho quickjs thông qua llrt
https://github.com/awslabs/llrt