1 điểm bởi GN⁺ 2025-11-19 | 1 bình luận | Chia sẻ qua WhatsApp
  • YJIT và ZJIT là các kiến trúc trình biên dịch JIT trong Ruby 3.x, chuyển mã Ruby sang mã máy để tăng tốc độ thực thi
  • YJIT đếm số lần gọi của từng hàm hoặc block và khi đạt một ngưỡng nhất định sẽ chuyển đoạn mã đó sang mã máy
  • Mã đã chuyển đổi được lưu trong YJIT block, mỗi block sẽ chuyển nhiều lệnh YARV tương ứng thành các lệnh mã máy ARM64
  • Sử dụng Branch Stub để quan sát kiểu dữ liệu thực tế tại runtime và chọn lọc sinh ra các lệnh mã máy phù hợp
  • Cấu trúc này là cơ chế cốt lõi để đồng thời đạt được cải thiện hiệu năng thực thi của Ruby và hiệu quả xử lý kiểu động

Chương 4: Biên dịch Ruby thành mã máy

Diễn dịch vs. Biên dịch mã Ruby

  • Bản gốc không có nội dung chi tiết

Đếm số lần gọi hàm và block

  • YJIT theo dõi số lần gọi hàm và block của chương trình để xác định đoạn mã hotspot
    • Bên cạnh chuỗi lệnh YARV của mỗi hàm hoặc block, nó lưu các giá trị jit_entryjit_entry_calls
    • jit_entry ban đầu là null, về sau sẽ lưu con trỏ tới mã máy do YJIT sinh ra
    • jit_entry_calls tăng thêm 1 mỗi khi được gọi
  • Khi số lần gọi đạt ngưỡng, YJIT sẽ biên dịch đoạn mã đó sang mã máy
    • Ngưỡng mặc định của Ruby 3.5 là 30 lần với chương trình nhỏ120 lần với ứng dụng lớn
    • Có thể thay đổi khi chạy bằng tùy chọn --yjit-call-threshold
  • Với cách này, YJIT chỉ chuyển những đoạn mã được thực thi thường xuyên sang mã máy để đảm bảo đường thực thi hiệu quả

YJIT Blocks

  • YJIT lưu các lệnh mã máy đã tạo trong YJIT block
    • YJIT block khác với Ruby block, và tương ứng với một phần đoạn lệnh YARV
    • Mỗi hàm hoặc block Ruby được cấu thành từ nhiều YJIT block
  • Trong chương trình ví dụ, khi block được thực thi lần thứ 30 thì YJIT bắt đầu biên dịch
    • Nó chuyển lệnh YARV đầu tiên getlocal_WC_1 sang mã máy và tạo một YJIT block mới
    • Sau đó tiếp tục biên dịch lệnh getlocal_WC_0 và đưa vào cùng block đó
  • Theo Figure 4-8, YJIT sinh ra các lệnh ARM64 để nạp giá trị vào các thanh ghi x1, x9 của bộ xử lý M1
    • getlocal_WC_1 lưu biến cục bộ của stack frame trước đó, còn getlocal_WC_0 lưu biến của stack hiện tại lên stack
    • Các lệnh mã máy được sinh ra thực hiện cùng một hành vi

YJIT Branch Stubs

  • Khi YJIT biên dịch lệnh opt_plus, phát sinh vấn đề không biết kiểu của toán hạng
    • Tùy theo kiểu như số nguyên, chuỗi, số thực dấu chấm động..., các lệnh mã máy cần dùng sẽ khác nhau
    • Ví dụ: phép cộng số nguyên dùng lệnh adds, còn phép cộng số thực dấu chấm động cần lệnh khác
  • Để giải quyết việc này, YJIT không phân tích trước mà quan sát tại runtime
    • Trong lúc chương trình chạy, nó kiểm tra kiểu của các giá trị thực sự được truyền vào rồi sinh mã máy phù hợp
  • Cơ chế này sử dụng Branch Stub
    • Khi một nhánh (branch) mới chưa có block nào được nối vào, nó tạm thời được nối tới stub
    • Sau khi xác nhận được kiểu thực tế, stub đó sẽ được thay bằng block thích hợp

ZJIT (chỉ được nhắc đến)

  • Mục lục có phần liên quan đến ZJIT, nhưng phần thân bài không có giải thích cụ thể

Tóm tắt

  • YJIT là trình biên dịch JIT trong Ruby 3.5 nhằm nâng cao hiệu quả thực thi của ngôn ngữ kiểu động
  • Các điểm cốt lõi là kích hoạt biên dịch dựa trên số lần gọi, cấu trúc YJIT blockxác định kiểu tại runtime thông qua Branch Stub
  • Nó chuyển đổi thành các lệnh mã máy thực tế trên kiến trúc ARM64 để cải thiện tốc độ thực thi của mã Ruby
  • ZJIT được nhắc đến như JIT thế hệ tiếp theo, nhưng bài viết không có nội dung chi tiết

1 bình luận

 
GN⁺ 2025-11-19
Ý kiến Hacker News
  • Trước đây từng có thời MacRuby biên dịch ra mã máy native trên macOS bằng LLVM và tích hợp với framework Objective‑C
    Đây là một ý tưởng khá hay, nhưng cuối cùng có vẻ Apple đã chuyển hướng sang Swift
    Nếu có phiên bản mới, mình nhất định sẽ mua và đọc cuốn Ruby Under a Microscope. Mình vẫn rất thích Ruby, nhưng thực tế lại không có nhiều cơ hội dùng đến

    • Sau khi tác giả của MacRuby rời Apple, ông ấy đã tạo ra RubyMotion
      Giờ người khác đang tiếp tục duy trì, nhưng có vẻ hiện tại họ tập trung hơn vào DragonRuby (một implementation Ruby thiên về game)
    • Sau khi tác giả rời đi, MacRuby được tiếp nối bởi RubyMotion
      Tham khảo thêm bài viết trên wiki
    • Hiện giờ vẫn có thể dùng Objective‑C để làm ứng dụng cho macOS, iOS và iPadOS
      Tuy nhiên, các API cũ có thể không còn được hỗ trợ nữa
    • Với góc nhìn của người đã làm qua nhiều ngôn ngữ, việc Apple chuyển sang Swift cho cảm giác hơi giống như Microsoft chuyển từ VB6 sang VB.Net
      VB6 có tốc độ phát triển thực sự rất nhanh, và còn có thể làm cả Direct3D lẫn ASP Classic
      Sự thanh lịch và tiện lợi khi phát triển của Ruby khiến mình nhớ lại thời đó
      Nếu Ruby có công cụ GUI ở tầm VB6, có lẽ mức độ phổ biến của nó đã rất khác
  • Thật sự rất vui khi thấy Pat vẫn tiếp tục theo đuổi dự án này
    Cuốn Ruby Under a Microscope đầu tiên và các bài blog của anh ấy đã mang lại cho mình rất nhiều cảm hứng
    Mình cũng từng gặp anh ấy trực tiếp ở hội nghị Euruko, và anh ấy thực sự là một người tuyệt vời

    • Cảm ơn vì lời bình ấm áp
  • Lần đầu đọc Ruby Under a Microscope mình thấy cực kỳ thú vị
    Nhờ đó mà trước đây mình còn áp dụng được vào cả giải bài CTF
    Dạo này mình không còn theo sát phần triển khai nội bộ của Ruby nữa, nhưng nếu có bản mới thì chắc chắn sẽ mua

    • Từ 2002 đến 2010 mình dùng Ruby rất nhiều, nhưng sau đó gần như bỏ hẳn
      Bài viết này khiến mình muốn đọc lại phiên bản sách mới
  • Nhân nói về chuyện biên dịch Ruby, mình tò mò không biết có ai từng thử Sorbet compiler do các lập trình viên Stripe tạo ra chưa
    Bài công bố Sorbet Compiler

    • Giờ nó đã biến mất khỏi kho mã nguồn, và có vẻ không còn được phát triển nữa, khá đáng tiếc
      Biên dịch AOT thực sự rất khó với Ruby
      Điều thú vị trong cách tiếp cận của Sorbet là nó có thể tạo ra đường chạy nhanh dựa trên kiểm tra kiểu của Ruby
      Mình cũng đang làm một trình biên dịch Ruby như một dự án cá nhân, và đang tham khảo hokstad.com/compiler cùng
      writing-a-compiler-in-ruby
      Hiện tại mình đang tập trung vào việc vượt qua RubySpec, và sau này cũng định thử tối ưu hóa dựa trên kiểu
  • Dù không liên quan trực tiếp đến biên dịch Ruby, cuốn Enterprise Integration with Ruby đã cho mình rất nhiều gợi mở về cách sử dụng Ruby ngoài lĩnh vực web

  • Từ khi biết đến MRuby, mình bị cuốn vào niềm vui biến các dự án và script của mình thành file thực thi độc lập

  • Mình rất vui vì Ruby Under a Microscope vẫn đang tiếp tục được cập nhật
    Với những ai muốn hiểu cách Ruby vận hành bên trong, mình nghĩ đây là một cuốn sách bắt buộc phải đọc

  • Mình từng thắc mắc khi một block của YJIT được chạy nhiều lần thì nó theo dõi việc biên dịch theo từng kiểu đầu vào như thế nào
    Mình muốn hiểu Ruby xử lý các kiểu khác nhau như int hay float ra sao

    • Đó chính là trái tim của YJIT
      Nó dùng cách tiếp cận "chờ rồi xem" bằng cách trì hoãn biên dịch cho đến khi có kiểu thực tế được cung cấp
      Nó quản lý riêng các phiên bản block cho từng kiểu, rồi gọi phiên bản phù hợp theo ngữ cảnh
      Thuật toán này được gọi là Basic Block Versioning
      Maxime Chevalier‑Boisvert của Shopify giải thích rất rõ trong video thuyết trình tại RubyConf 2021
      Có vẻ engine JIT mới là ZJIT dùng một cách tiếp cận khác
  • Việc tăng tốc ngôn ngữ kiểu động bằng JIT thường phải đánh đổi bằng mức sử dụng bộ nhớ tăng lên
    Nếu không phải công ty lớn như Shopify thì đây có thể là vấn đề lớn hơn

    • Nhưng công ty nhỏ thường cũng có quy mô ứng dụng nhỏ hơn
      Các instance cloud ngày nay thường có khoảng 4GiB RAM mỗi core, nên vài trăm MB mã JIT là hoàn toàn có thể gánh được
  • Cách YJIT chỉ đếm số lần gọi hàm để tìm điểm nóng có vẻ hơi đơn giản
    Mình tự hỏi liệu nó có cơ chế phát hiện các phép tính nặng bên trong vòng lặp như JIT của JavaScript không
    Có vẻ cấu trúc block của Ruby cũng có thể giúp ích cho kiểu tối ưu hóa này

    • Đúng vậy, vì Ruby xử lý thân vòng lặp dưới dạng block
      Nên JIT có thể coi block như một hàm riêng và tối ưu hóa vòng lặp một cách tự nhiên
      Phần này sẽ được bàn sâu hơn trong chương tiếp theo