1 คะแนน โดย GN⁺ 2023-12-26 | 1 ความคิดเห็น | แชร์ทาง WhatsApp

Ruby 3.3.0 เปิดตัวแล้ว

  • Ruby 3.3.0 เวอร์ชันใหม่เปิดตัวแล้ว โดยมีการนำ parser ใหม่อย่าง Prism มาใช้, ใช้ Lrama เป็น parser generator, เพิ่ม RJIT ซึ่งเป็น JIT compiler ที่เขียนด้วย Ruby ล้วน และมีการปรับปรุงประสิทธิภาพของ YJIT อย่างเด่นชัด

Prism parser

  • Prism เป็น recursive descent parser สำหรับภาษา Ruby ที่พกพาได้, ทนทานต่อข้อผิดพลาด และดูแลรักษาง่าย โดยจัดมาให้เป็น default gem
  • Prism เหมาะสำหรับใช้งานใน production และยังคงมีการพัฒนาอย่างต่อเนื่อง โดยสามารถใช้แทน Ripper ได้
  • มีเอกสารรายละเอียดสำหรับวิธีใช้งาน Prism
  • Prism เป็นทั้งไลบรารีภาษา C ที่ใช้ภายใน CRuby และเป็น Ruby gem ที่เครื่องมือทุกชนิดสามารถนำไปใช้ได้หากต้อง parse โค้ด Ruby
  • เมธอดสำคัญใน Prism API ได้แก่ Prism.parse(source), Prism.parse_comments(source), Prism.parse_success?(source) เป็นต้น
  • สามารถมีส่วนร่วมได้โดยส่ง pull request หรือ issue เข้าไปยัง repository ของ Prism โดยตรง
  • หากต้องการทดลองใช้ Prism compiler แบบ experimental สามารถใช้ ruby --parser=prism หรือ RUBYOPT="--parser=prism" ได้ แต่ควรใช้เพื่อการดีบักเท่านั้น

Lrama parser generator

  • แทนที่ Bison ด้วย Lrama LALR parser generator
  • ผู้ที่สนใจสามารถดูวิสัยทัศน์ในอนาคตของ parser ของ Ruby ได้
  • เพื่อให้ง่ายต่อการบำรุงรักษา internal Lrama parser ถูกแทนที่ด้วย LR parser ที่สร้างโดย Racc
  • รองรับกฎแบบ parameterized (?, *, +) และมีแผนจะนำไปใช้ใน Ruby parse.y

YJIT

  • มีการปรับปรุงประสิทธิภาพสำคัญเมื่อเทียบกับ Ruby 3.2
  • ปรับปรุงการรองรับ splat และ rest arguments
  • มีการจัดสรร register ให้กับงาน stack operation ของ virtual machine
  • มีการคอมไพล์ call ที่มี optional arguments ได้มากขึ้น และยังคอมไพล์ exception handler ได้ด้วย
  • ประเภทการเรียกที่ยังไม่รองรับและ megamorphic call sites จะไม่ fallback กลับไปยัง interpreter อีกต่อไป
  • เมธอดพื้นฐานอย่าง #blank? ของ Rails และ #present? แบบพิเศษถูก inline
  • มีการ optimize เป็นพิเศษสำหรับ Integer#*, Integer#!=, String#!=, String#getbyte, Kernel#block_given?, Kernel#is_a?, Kernel#instance_of?, Module#=== เป็นต้น
  • ความเร็วในการคอมไพล์เร็วขึ้นเล็กน้อยเมื่อเทียบกับ Ruby 3.2
  • ใน Optcarrot เร็วกว่า interpreter มากกว่า 3 เท่า!
  • การใช้หน่วยความจำดีขึ้นอย่างมากเมื่อเทียบกับ Ruby 3.2
  • metadata ของโค้ดที่คอมไพล์แล้วใช้หน่วยความจำน้อยลงมาก
  • --yjit-call-threshold ถูกเพิ่มอัตโนมัติจาก 30 เป็น 120 สำหรับแอปพลิเคชันที่มี ISEQ มากกว่า 40,000 รายการ
  • เพิ่ม --yjit-cold-threshold เพื่อข้ามการคอมไพล์ ISEQ ที่เย็นแล้ว
  • บน Arm64 จะสร้างโค้ดที่กะทัดรัดขึ้น
  • code GC ถูกปิดใช้งานเป็นค่าเริ่มต้น
  • --yjit-exec-mem-size ถูกปฏิบัติเป็น hard limit ที่เมื่อถึงแล้วจะหยุดคอมไพล์โค้ดใหม่
  • ไม่มีผลกระทบด้านประสิทธิภาพจาก code GC และเมื่อใช้ Pitchfork จะได้พฤติกรรม copy-on-write ที่ดีขึ้นตอนเซิร์ฟเวอร์ re-fork
  • หากต้องการสามารถเปิด code GC ได้ด้วย --yjit-code-gc
  • เพิ่ม RubyVM::YJIT.enable เพื่อเปิดใช้งาน YJIT ระหว่าง runtime
  • Rails 7.2 มีแผนจะใช้วิธีนี้เพื่อเปิดใช้ YJIT เป็นค่าเริ่มต้น
  • หากต้องการเปิด YJIT หลังจากแอปบูตเสร็จแล้วเท่านั้น ก็สามารถใช้วิธีนี้ได้
  • หากต้องการปิด YJIT ตอนบูต แต่ยังใช้ตัวเลือก YJIT อื่น ๆ ได้ ให้ใช้ --yjit-disable
  • มีการให้สถิติของ YJIT เพิ่มขึ้นเป็นค่าเริ่มต้น
  • yjit_alloc_size และสถิติที่เกี่ยวข้องกับ metadata อีกหลายรายการมีให้ใช้งานเป็นค่าเริ่มต้น
  • สถิติ ratio_in_yjit ที่สร้างโดย --yjit-stats สามารถใช้ได้ใน release build แล้ว โดยไม่ต้องใช้ special stats หรือ development build อีกต่อไป
  • เพิ่มความสามารถด้าน profiling มากขึ้น
  • เพิ่ม --yjit-perf เพื่อให้ profiling ร่วมกับ Linux perf ทำได้ง่ายขึ้น
  • --yjit-trace-exits รองรับการสุ่มตัวอย่างผ่าน --yjit-trace-exits-sample-rate=N
  • มีการทดสอบที่เข้มข้นขึ้นและแก้ไขบั๊กจำนวนมาก

RJIT

  • เพิ่ม RJIT ซึ่งเป็น JIT compiler ที่เขียนด้วย Ruby ล้วน และมาแทนที่ MJIT
  • RJIT รองรับเฉพาะสถาปัตยกรรม x86-64 บนแพลตฟอร์ม Unix เท่านั้น
  • ต่างจาก MJIT ตรงที่ไม่ต้องใช้ C compiler ระหว่าง runtime
  • RJIT มีไว้เพื่อการทดลองเท่านั้น
  • ใน production ควรใช้ YJIT ต่อไป
  • หากสนใจการพัฒนา Ruby JIT แนะนำให้ดูการบรรยายของ k0kubun ในวันที่ 3 ของ RubyKaigi

M:N thread scheduler

  • มีการนำ M:N thread scheduler มาใช้
  • เนื่องจาก M Ruby threads ถูกจัดการโดย N native threads (threads ของระบบปฏิบัติการ) จึงช่วยลดต้นทุนในการสร้างและจัดการ thread
  • M:N thread scheduler อาจทำให้ความเข้ากันได้กับ C extensions เสียไป จึงถูกปิดใช้งานไว้เป็นค่าเริ่มต้นใน main Ractor
  • สามารถเปิดใช้ M:N threads ใน main Ractor ได้ด้วยตัวแปรสภาพแวดล้อม RUBY_MN_THREADS=1
  • ใน non-main Ractor จะเปิดใช้ M:N threads อยู่เสมอ
  • ตัวแปรสภาพแวดล้อม RUBY_MAX_CPU=n ใช้กำหนดจำนวนสูงสุดของ N (จำนวนสูงสุดของ native threads) โดยค่าเริ่มต้นคือ 8
  • เนื่องจากสามารถรัน Ruby thread ได้เพียงหนึ่งตัวต่อ Ractor แอปพลิเคชันแบบ single Ractor (ซึ่งเป็นแอปส่วนใหญ่) จึงใช้ native thread เพียง 1 ตัว
  • อาจมีการใช้ native threads มากกว่า N เพื่อรองรับงานที่มีการบล็อก

การปรับปรุงประสิทธิภาพ

  • defined?(@ivar) ถูก optimize ด้วย Object Shapes
  • การ resolve ชื่อ เช่น Socket.getaddrinfo ตอนนี้สามารถ interrupt ได้แล้ว (ในสภาพแวดล้อมที่รองรับและมี pthreads)
  • มีการปรับปรุงประสิทธิภาพหลายด้านใน garbage collector
    • เมื่อ young objects ถูกอ้างอิงโดย old objects จะไม่ถูกเลื่อนชั้นไปยัง old generation ทันทีอีกต่อไป ทำให้ความถี่ของ major GC collection ลดลงอย่างมาก
    • มีการเพิ่มตัวแปรปรับแต่ง REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO เพื่อควบคุมจำนวน unprotected objects ที่ทำให้เกิด major GC collection โดยค่าเริ่มต้นตั้งไว้ที่ 0.01 (1%) ซึ่งช่วยลดความถี่ของ major GC collection อย่างมาก
    • มีการใส่ Write Barriers ให้กับ core types จำนวนมากที่ก่อนหน้านี้ยังขาดอยู่ ส่งผลให้เวลาในการทำ minor GC collection และความถี่ของ major GC collection ลดลงอย่างมาก
    • ตอนนี้ core classes ส่วนใหญ่ใช้ Variable Width Allocation แล้ว ทำให้การ allocate และ free เร็วขึ้น ใช้หน่วยความจำน้อยลง และลด heap fragmentation
    • มีการเพิ่มการรองรับ weak references ให้กับ garbage collector

การเปลี่ยนแปลงสำคัญอื่น ๆ

  • IRB ได้รับการปรับปรุงหลายอย่าง รวมถึงการผสาน irb:rdbg ขั้นสูง, การรองรับ pager สำหรับคำสั่ง ls, show_source และ show_cmds, การเพิ่มความแม่นยำและประโยชน์ของข้อมูลที่ได้จากคำสั่ง ls และ show_source, รวมถึง auto-completion แบบ experimental ที่ใช้ type analysis
  • IRB ยังผ่านการ refactor ครั้งใหญ่เพื่อให้รองรับการปรับปรุงในอนาคตได้ง่ายขึ้น และได้รับการแก้ไขบั๊กอีกหลายสิบรายการ

ปัญหาความเข้ากันได้

  • การเรียก it ภายใน block โดยไม่ส่ง arguments จะเลิกใช้งานแล้ว และใน Ruby 3.4 จะอ้างถึง block parameter ตัวแรกแทน
  • มีการลบตัวแปรสภาพแวดล้อมที่เลิกใช้งานแล้วออก

การอัปเดต standard library

  • RubyGems และ Bundler จะแสดงคำเตือนหากผู้ใช้ require gem ต่อไปนี้โดยไม่ได้เพิ่มไว้ใน Gemfile หรือ gemspec เนื่องจาก gem เหล่านี้จะกลายเป็น bundled gem ใน Ruby เวอร์ชันอนาคต
  • มีการเพิ่มหรืออัปเดต default gem หลายรายการ เช่น prism 0.19.0, RubyGems 3.5.3, abbrev 0.1.2 เป็นต้น
  • bundled gem หลายรายการถูกเลื่อนระดับมาจาก default gem หรือได้รับการอัปเดต เช่น racc 1.7.3, minitest 5.20.0 เป็นต้น

ความเห็นของ GN⁺

  • การนำ Prism parser มาใช้: หนึ่งในจุดเด่นสำคัญที่สุดของ Ruby 3.3.0 คือการมาถึงของ Prism parser ใหม่ ซึ่งจะช่วยให้นักพัฒนา Ruby parse โค้ดได้มีประสิทธิภาพมากขึ้น ทนทานต่อข้อผิดพลาดมากขึ้น และดูแลรักษาได้ง่ายขึ้น
  • การปรับปรุงประสิทธิภาพของ YJIT: การปรับปรุงประสิทธิภาพครั้งใหญ่ของ YJIT จะช่วยเพิ่มความเร็วในการรันแอปพลิเคชัน Ruby อย่างมาก โดยเฉพาะการลดการใช้หน่วยความจำและการ optimize GC ที่จะส่งผลดีต่อประสิทธิภาพและความเสถียรของแอป Ruby ขนาดใหญ่
  • M:N thread scheduler: การนำ M:N thread scheduler มาใช้มีศักยภาพในการเพิ่มประสิทธิภาพให้แอปพลิเคชัน Ruby แบบมัลติเธรด โดยช่วยลดต้นทุนการจัดการ thread และเปิดทางให้การประมวลผลแบบขนานมีประสิทธิภาพมากขึ้น

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

 
GN⁺ 2023-12-26
ความคิดเห็นบน Hacker News
  • การมาถึงของ Ruby 3.3 ทำให้ Ruby ซึ่งเป็นภาษาที่ให้ความสำคัญกับความสุขของนักพัฒนา สลัดภาพลักษณ์เดิมที่ช้าออกไปและแสดงความเร็วที่น่าประทับใจ

    • ประสิทธิภาพของ Ruby ดีขึ้นอย่างมากผ่านนวัตกรรมต่าง ๆ เช่น เทคโนโลยี YJIT, object shapes และการปรับแต่ง GC
    • บริษัทขนาดใหญ่ที่ใช้งาน Ruby เช่น Shopify กำลังประสบกับการปรับปรุงด้านประสิทธิภาพของ Ruby 3.3
    • โดยส่วนตัวรู้สึกตื่นเต้นมากกับอนาคตของ Ruby และตั้งตารอที่จะนำ Ruby 3.3 ไปใช้กับเว็บไซต์ production ของลูกค้า
  • Ruby 3.3 เป็นรีลีสที่สำคัญและอัดแน่นด้วยฟีเจอร์ที่สุดในรอบ 10 ปี และรู้สึกประหลาดใจที่มันออก JIT ได้ก่อน Python

    • ฟีเจอร์ต่าง ๆ เช่น Prism, Lrama และ IRB ได้ถูกพูดถึงในโพสต์ Hacker News ก่อนหน้านี้
    • ฟีเจอร์อย่าง Ractor, ตัวจัดตารางเธรดแบบ M:N, Fibre และ Async ยังไม่ได้ถูกกล่าวถึงมากพอในบริบทของ Rails และอยากฟังประสบการณ์จากคนที่ใช้งานฟีเจอร์เหล่านี้ใน production
  • แจ้งว่าสามารถใช้ Ruby 3.3 บน Heroku ได้แล้ว

  • ภาษา Ruby ออกรีลีสใหม่ทุกปีในช่วงคริสต์มาส

  • ตั้งคำถามว่าหากรู้ Python และ NodeJS อยู่แล้ว การเรียน Ruby ยังมีคุณค่าหรือไม่ โดยรู้สึกว่า Ruby น่าสนใจแต่ก็ยาก

  • การแก้ชื่ออย่าง Socket.getaddrinfo อาจหยุดการทำงานได้ โดยจะสร้าง worker pthread ขึ้นมาทุกครั้งที่ต้องมีการแก้ชื่อ แล้วรัน getaddrinfo(3)

    • ตั้งคำถามว่า runtime ของภาษาอื่นก็ทำแบบเดียวกันหรือไม่ แม้การสร้างเธรดอาจดูหนัก แต่จาก benchmark พบว่า overhead ต่ำมาก
  • Prism น่าสนใจ และตั้งคำถามว่ามีตัวอย่างการใช้ Prism เป็นเครื่องมือวิเคราะห์โค้ด Ruby หรือไม่

  • ตัวแปรสภาพแวดล้อม RUBY_MAX_CPU=n ใช้กำหนดจำนวนสูงสุดของ native threads โดยค่าเริ่มต้นคือ 8

    • ตั้งข้อสงสัยว่าค่าเริ่มต้นควรเท่ากับจำนวน logical cores หรือไม่ เช่นเดียวกับ Tokio ของ Rust และ runtime แบบ M:N อื่น ๆ อีกมาก
  • กำลังมองหาลิงก์ตัวอย่างที่ดีของการใช้ Prism และรู้สึกผิดหวังที่ในหน้าประกาศรีลีสแทบไม่มีอะไรนอกจาก "Notable APIs"

  • กล่าวว่านี่คือของขวัญคริสต์มาสที่สมบูรณ์แบบ