1 คะแนน โดย GN⁺ 2025-11-19 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • YJIT และ ZJIT คือสถาปัตยกรรม JIT compiler ใน Ruby 3.x ที่ แปลงโค้ด Ruby เป็นภาษาเครื่องเพื่อเพิ่มความเร็วในการทำงาน
  • YJIT จะ นับจำนวนครั้งที่มีการเรียกแต่ละฟังก์ชันหรือบล็อก และเมื่อถึงค่าขีดจำกัดที่กำหนด ก็จะแปลงโค้ดส่วนนั้นเป็นภาษาเครื่อง
  • โค้ดที่แปลงแล้วจะถูกเก็บไว้ใน YJIT block โดยแต่ละบล็อกจะแปลง YARV instruction หลายรายการให้เป็น คำสั่งภาษาเครื่อง ARM64 ที่สอดคล้องกัน
  • ใช้ Branch Stub เพื่อสังเกตชนิดข้อมูลจริงระหว่างรันไทม์ และเลือกสร้างคำสั่งภาษาเครื่องให้ตรงกับชนิดข้อมูลนั้นแบบเลือกสรร
  • โครงสร้างนี้เป็นกลไกสำคัญในการทำให้ Ruby ได้ทั้งประสิทธิภาพการรันที่ดีขึ้นและการจัดการ dynamic type ที่มีประสิทธิภาพ ไปพร้อมกัน

Chapter 4: คอมไพล์ Ruby เป็นภาษาเครื่อง

Interpreting vs. Compiling Ruby Code

  • ต้นฉบับไม่มีรายละเอียดเพิ่มเติม

การนับจำนวนการเรียกเมธอดและบล็อก

  • YJIT ติดตาม จำนวนครั้งที่มีการเรียกฟังก์ชันและบล็อก ของโปรแกรมเพื่อระบุ โค้ดฮอตสปอต
    • เก็บค่า jit_entry และ jit_entry_calls ไว้ข้างลำดับคำสั่ง YARV ของแต่ละฟังก์ชันหรือบล็อก
    • jit_entry จะเป็น null ในตอนแรก และภายหลังจะเก็บพอยน์เตอร์ไปยังโค้ดภาษาเครื่องที่ YJIT สร้างขึ้น
    • jit_entry_calls จะเพิ่มขึ้นทีละ 1 ทุกครั้งที่ถูกเรียก
  • เมื่อจำนวนการเรียกถึงค่าขีดจำกัด YJIT จะ คอมไพล์โค้ดส่วนนั้นเป็นภาษาเครื่อง
    • ค่าเริ่มต้นใน Ruby 3.5 คือ 30 ครั้งสำหรับโปรแกรมขนาดเล็ก และ 120 ครั้งสำหรับแอปพลิเคชันขนาดใหญ่
    • เปลี่ยนได้ตอนรันด้วยออปชัน --yjit-call-threshold
  • ด้วยวิธีนี้ YJIT จะเปลี่ยนเฉพาะโค้ดที่ถูกรันบ่อยให้เป็นภาษาเครื่องเพื่อ สร้างเส้นทางการทำงานที่มีประสิทธิภาพ

YJIT Blocks

  • YJIT เก็บคำสั่งภาษาเครื่องที่สร้างขึ้นไว้ใน YJIT block
    • YJIT block แตกต่างจาก Ruby block และใช้แทน ช่วงย่อยบางส่วนของคำสั่ง YARV
    • ฟังก์ชันหรือบล็อก Ruby แต่ละตัวประกอบด้วย YJIT block หลายบล็อก
  • ในโปรแกรมตัวอย่าง เมื่อบล็อกถูกรันเป็นครั้งที่ 30 YJIT จะเริ่มคอมไพล์
    • แปลงคำสั่ง YARV ตัวแรก getlocal_WC_1 เป็นภาษาเครื่องและสร้าง YJIT block ใหม่
    • จากนั้นคอมไพล์คำสั่ง getlocal_WC_0 เพิ่มและรวมไว้ในบล็อกเดียวกัน
  • ตาม Figure 4-8 YJIT จะสร้าง คำสั่ง ARM64 เพื่อโหลดค่าเข้ารีจิสเตอร์ x1 และ x9 ของโปรเซสเซอร์ M1
    • getlocal_WC_1 จะเก็บตัวแปรภายในจากสแตกเฟรมก่อนหน้า และ getlocal_WC_0 จะเก็บตัวแปรจากสแตกปัจจุบันลงในสแตก
    • คำสั่งภาษาเครื่องที่สร้างขึ้นจะทำงานแบบเดียวกัน

YJIT Branch Stubs

  • เมื่อ YJIT คอมไพล์คำสั่ง opt_plus จะเกิด ปัญหาที่ไม่รู้ชนิดของ operand ล่วงหน้า
    • ไม่ว่าจะเป็นจำนวนเต็ม สตริง หรือจำนวนทศนิยม ล้วนต้องใช้คำสั่งภาษาเครื่องต่างกันตามชนิดข้อมูล
    • ตัวอย่าง: การบวกจำนวนเต็มใช้คำสั่ง adds ส่วนการบวกจำนวนทศนิยมต้องใช้คำสั่งอีกแบบ
  • เพื่อแก้ปัญหานี้ YJIT จึงใช้ การสังเกตระหว่างรันไทม์แทนการวิเคราะห์ล่วงหน้า
    • ระหว่างที่โปรแกรมทำงาน จะตรวจสอบชนิดของค่าที่ถูกส่งเข้ามาจริง แล้วจึงสร้างโค้ดภาษาเครื่องที่เหมาะสม
  • กลไกนี้ทำงานผ่าน Branch Stub
    • เมื่อมีสาขา (branch) ใหม่ที่ยังไม่มีบล็อกเชื่อมต่ออยู่ จะเชื่อมไปยัง stub ชั่วคราวก่อน
    • หลังจากทราบชนิดข้อมูลจริงแล้ว stub นั้นจะถูกแทนที่ด้วยบล็อกที่เหมาะสม

ZJIT (มีเพียงการกล่าวถึง)

  • มีหัวข้อเกี่ยวกับ ZJIT อยู่ในสารบัญ แต่ไม่มีคำอธิบายโดยละเอียดในเนื้อหา

สรุป

  • YJIT คือ JIT compiler ใน Ruby 3.5 ที่ออกแบบมาเพื่อเพิ่มประสิทธิภาพการรันของภาษาแบบ dynamic type
  • หัวใจสำคัญคือ การทริกเกอร์การคอมไพล์ตามจำนวนครั้งที่ถูกเรียก, โครงสร้างแบบ YJIT block และ การตรวจสอบชนิดข้อมูลระหว่างรันไทม์ผ่าน Branch Stub
  • มีการแปลงเป็นคำสั่งภาษาเครื่องจริงบนสถาปัตยกรรม ARM64 เพื่อ เพิ่มความเร็วในการรันโค้ด Ruby
  • ZJIT ถูกกล่าวถึงในฐานะ JIT รุ่นถัดไป แต่ไม่มีรายละเอียดเพิ่มเติมในเนื้อหา

1 ความคิดเห็น

 
GN⁺ 2025-11-19
ความคิดเห็นจาก Hacker News
  • เมื่อก่อนเคยมีช่วงที่ MacRuby คอมไพล์เป็น native code บน macOS ด้วย LLVM และผสานกับเฟรมเวิร์ก Objective‑C ได้
    เป็นไอเดียที่เท่มากทีเดียว แต่สุดท้ายดูเหมือน Apple จะเปลี่ยนทิศไปทาง Swift
    ถ้ามีเวอร์ชันใหม่ออกมา ฉันตั้งใจว่าจะซื้อหนังสือ Ruby Under a Microscope มาอ่านแน่นอน ยังชอบ Ruby อยู่เสมอ แต่ไม่ค่อยมีโอกาสได้ใช้จริงมากนัก

    • หลังจากผู้สร้าง MacRuby ออกจาก Apple ก็ไปสร้าง RubyMotion
      ตอนนี้มีคนอื่นรับช่วงต่ออยู่ แต่บรรยากาศเหมือนจะโฟกัสกับ DragonRuby มากกว่าในปัจจุบัน (เป็น Ruby implementation ที่เน้นเกม)
    • หลังจากผู้เขียนออกไป MacRuby ก็สืบต่อมาเป็น RubyMotion
      อ้างอิงได้จาก บทความวิกิ ด้วย
    • ตอนนี้ก็ยังใช้ Objective‑C ทำแอปสำหรับ macOS, iOS และ iPadOS ได้อยู่
      เพียงแต่ API รุ่นเก่าอาจไม่ได้รับการรองรับอีกแล้ว
    • ในมุมของคนที่เคยใช้มาหลายภาษา การที่ Apple ย้ายไป Swift ให้ความรู้สึกคล้ายกับตอน Microsoft เปลี่ยนจาก VB6 ไป VB.Net
      VB6 นั้นพัฒนาได้เร็วมาก และยังทำงานกับ Direct3D หรือ ASP Classic ได้ด้วย
      ความสง่างามและความสะดวกในการพัฒนา ของ Ruby ทำให้นึกถึงยุคนั้น
      ถ้า Ruby มีเครื่องมือ GUI ระดับเดียวกับ VB6 ความนิยมของมันอาจต่างไปมากก็ได้
  • ดีใจมากที่เห็น Pat ยังเดินหน้ากับโปรเจ็กต์นี้ต่อ
    หนังสือ Ruby Under a Microscope เล่มแรกและบทความในบล็อกของเขาเคยให้ แรงบันดาลใจ กับฉันมาก
    เคยเจอเขาด้วยตัวเองที่งานประชุม Euruko ครั้งหนึ่ง เขาเป็นคนที่ยอดเยี่ยมจริง ๆ

    • ขอบคุณสำหรับคอมเมนต์ที่อบอุ่น
  • ตอนที่อ่าน Ruby Under a Microscope ครั้งแรก สนุกมากจริง ๆ
    ถึงขั้นเคยเอาไปใช้ตอน แก้โจทย์ CTF ด้วย
    ช่วงหลังไม่ได้ตามรายละเอียดภายในของ Ruby แล้ว แต่ถ้ามีฉบับใหม่ออกมาก็ตั้งใจว่าจะซื้อแน่นอน

    • ฉันใช้ Ruby หนักมากตั้งแต่ปี 2002 ถึง 2010 หลังจากนั้นก็แทบไม่ได้จับอีกเลย
      พอเห็นโพสต์นี้ก็อยากกลับไปอ่านหนังสือเวอร์ชันใหม่อีกครั้ง
  • พอพูดถึงการคอมไพล์ Ruby ก็เลยสงสัยว่าเคยลอง Sorbet compiler ที่นักพัฒนาของ Stripe สร้างไว้หรือยัง
    โพสต์เปิดซอร์ส Sorbet Compiler

    • น่าเสียดายที่ตอนนี้มันหายไปจาก repository แล้ว และดูเหมือนจะไม่ได้พัฒนาต่อ
      AOT compilation เป็นเรื่องที่ยากมากสำหรับ Ruby
      สิ่งที่ทำให้แนวทางของ Sorbet น่าสนใจก็คือมันสามารถสร้าง fast path จากการ ตรวจสอบชนิดข้อมูล ของ Ruby ได้
      ฉันเองก็กำลังทำ Ruby compiler เป็นโปรเจ็กต์ส่วนตัว โดยอ้างอิงจาก hokstad.com/compiler และ
      writing-a-compiler-in-ruby
      ตอนนี้กำลังโฟกัสที่การให้ผ่าน RubySpec อยู่ แล้วภายหลังก็คิดว่าจะลองทำ optimization บนพื้นฐานของชนิดข้อมูลด้วย
  • แม้จะไม่เกี่ยวกับการคอมไพล์ Ruby โดยตรง แต่หนังสือ Enterprise Integration with Ruby ให้ มุมมองเชิงลึก กับการใช้ Ruby ในงานนอกเหนือจากเว็บได้มากทีเดียว

  • ตั้งแต่รู้จัก MRuby ฉันก็สนุกกับการเปลี่ยนโปรเจ็กต์และสคริปต์ของตัวเองให้เป็นไฟล์ executable แบบแยกเดี่ยว

  • ดีใจที่ Ruby Under a Microscope ยังได้รับการอัปเดตอยู่เรื่อย ๆ
    คิดว่าเป็น หนังสือที่ควรอ่าน สำหรับคนที่อยากเข้าใจการทำงานภายในของ Ruby

  • ฉันสงสัยว่าเวลาที่บล็อกของ YJIT ถูกเรียกหลายครั้ง มันติดตามการคอมไพล์แยกตามชนิดของอินพุตอย่างไร
    อยากรู้ว่า Ruby จัดการกับชนิดต่าง ๆ อย่าง int หรือ float อย่างไร

    • นั่นแหละคือ แกนหลักของ YJIT
      มันใช้แนวทาง "wait‑and‑see" โดยเลื่อนการคอมไพล์ออกไปจนกว่าจะได้ชนิดจริงเข้ามา
      จากนั้นก็เก็บเวอร์ชันของบล็อกแยกตามแต่ละชนิด แล้วเรียกใช้ให้เหมาะกับสถานการณ์
      อัลกอริทึมนี้เรียกว่า Basic Block Versioning
      Maxime Chevalier‑Boisvert จาก Shopify อธิบายไว้ดีมากใน วิดีโอบรรยาย RubyConf 2021
      ส่วน ZJIT ซึ่งเป็น JIT engine ตัวใหม่ ดูเหมือนจะใช้วิธีที่ต่างออกไป
  • การทำให้ภาษาที่มี dynamic type เร็วขึ้นด้วย JIT มักต้องแลกกับ การใช้หน่วยความจำที่เพิ่มขึ้น
    ถ้าไม่ใช่บริษัทใหญ่แบบ Shopify เรื่องนี้อาจกลายเป็นปัญหาที่หนักกว่าก็ได้

    • แต่บริษัทเล็กก็มักมีแอปพลิเคชันขนาดเล็กกว่าด้วย
      ทุกวันนี้ cloud instance มักให้หน่วยความจำราว 4GiB ต่อคอร์ ดังนั้น JIT code ระดับหลายร้อย MB ก็ยังถือว่ารับไหว
  • วิธีที่ YJIT หา hotspot ด้วยการนับแค่จำนวนครั้งที่มีการเรียกฟังก์ชันดูค่อนข้างเรียบง่าย
    เลยสงสัยว่ามันมีความสามารถแบบ JavaScript JIT ที่ตรวจจับงานคำนวณหนักภายในลูปหรือเปล่า
    โครงสร้างบล็อกของ Ruby อาจช่วยให้ทำ optimization แบบนี้ได้ด้วย

    • ใช่เลย Ruby จัดการตัวลูปเป็นบล็อก
      ทำให้ JIT สามารถมองบล็อกเป็นเหมือนฟังก์ชันแยกต่างหาก และ optimize การวนซ้ำได้อย่างเป็นธรรมชาติ
      ประเด็นนี้จะพูดลึกขึ้นอีกในบทถัดไป