2 điểm bởi GN⁺ 2024-07-31 | 1 bình luận | Chia sẻ qua WhatsApp
  • 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 WebAssemblybinary 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

 
GN⁺ 2024-07-31
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

  • 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 IntFloat, giữ chúng trong thanh ghi rồi hạ xuống Number khi cần
    Vấ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

    • Nếu không có các phần mở rộng bổ sung, TypeScript không hữu ích như người ta tưởng. Vì ngay từ đầu nó không được thiết kế cho mục đích đó
      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
    • Với tư cách là người đóng góp cho Porffor, tôi không đồng ý. JavaScript cũng còn khá nhiều chỗ để cải thiện ở thời điểm biên dịch
      Đã 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ũ
    • Một dự án có liên quan phần nào đến ý tưởng này là AssemblyScript: https://www.assemblyscript.org
    • ECMAScript 4 từng là nỗ lực đưa hệ thống kiểu tốt hơn vào ngôn ngữ, nhưng đáng tiếc đã thất bại từ lâu
      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ệt const 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: Number có được chấp nhận không
    • Sau khi nói “tôi từng nghĩ đến chuyện này nhưng khó đạt hiệu năng tốt hơn”, lại đang nói đến cách tiếp cận yêu cầu đúng những thứ đã được giải thích ngay ở phần trên trang chủ của site
      Khô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ỏ

    • Với tư cách nhà phát triển, tôi đồng ý. Đây có vẻ là một trường hợp ứng dụng thú vị mà Porffor có thể hỗ trợ. Hy vọng một lúc nào đó có thể trao đổi thêm
  • 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ước
    Nhì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

    • Nhân tiện, Static Hermes hỗ trợ đầy đủ khả năng biên dịch JS sang WASM. Vì đã có backend LLVM sẵn nên tính năng này gần như có được miễn phí. Có thể xem ví dụ tại https://x.com/tmikov/status/1706138872412074204
      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
    • Với tư cách là người đóng góp cho Porffor, tôi thấy đây là một so sánh hay. Tuy nhiên, về mặt kỹ thuật Porffor cũng hỗ trợ promise. Chỉ là nó hoạt động đồng bộ
      Đây là cách tiếp cận tương tự Kiesel: https://kiesel.dev/
    • Có vài chỉnh sửa nhỏ. Porffor vẫn chưa hoàn toàn self-hosting, nhưng tôi kỳ vọng là có thể. Tuy nhiên, các tính năng tích hợp sẵn như Array.prototype.filter, Math.sin, atob thì một phần đã tự biên dịch chính chúng
      Gầ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
    • Có vẻ như bạn nói việc phụ thuộc vào LLVM nghe như điều gì đó xấu
  • 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ốt

    • Nếu mục tiêu là “ECMAScript host hành xử như trình duyệt web” thì đương nhiên phải hỗ trợ. Vì nó là một phần của đặc tả: https://tc39.es/ecma262/multipage/additional-ecmascript-feat...
      Việ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.blink nằ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ó những dự án bundle sẵn interpreter JS để làm JS-to-WASM. Vì vậy có khả năng cách diễn đạt đó nhằm làm rõ hơn sự khác biệt với phương thức ấy
  • 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...

    • Có lẽ ý định là các công việc gây hồi quy Test262 chỉ mang tính tạm thời và được thực hiện trên nhánh riêng, rồi chỉ merge vào main khi đã bao gồm tất cả sửa đổi cần thiết để loại bỏ hồi quy. Số phiên bản mới chỉ cần được dùng sau khi merge đó xảy ra
  • Trong tiếng Wales, nó có nghĩa là “màu tím

    • Từ nguyên của nó bắt nguồn từ tiếng Hy Lạp chỉ màu tím, và từ tiếng Anh có cùng gốc có lẽ phổ biến nhất là porphyry, một loại khoáng vật 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