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 ความคิดเห็น
ความคิดเห็นบน Hacker News
การมาถึงของ Ruby 3.3 ทำให้ Ruby ซึ่งเป็นภาษาที่ให้ความสำคัญกับความสุขของนักพัฒนา สลัดภาพลักษณ์เดิมที่ช้าออกไปและแสดงความเร็วที่น่าประทับใจ
Ruby 3.3 เป็นรีลีสที่สำคัญและอัดแน่นด้วยฟีเจอร์ที่สุดในรอบ 10 ปี และรู้สึกประหลาดใจที่มันออก JIT ได้ก่อน Python
แจ้งว่าสามารถใช้ Ruby 3.3 บน Heroku ได้แล้ว
ภาษา Ruby ออกรีลีสใหม่ทุกปีในช่วงคริสต์มาส
ตั้งคำถามว่าหากรู้ Python และ NodeJS อยู่แล้ว การเรียน Ruby ยังมีคุณค่าหรือไม่ โดยรู้สึกว่า Ruby น่าสนใจแต่ก็ยาก
การแก้ชื่ออย่าง
Socket.getaddrinfoอาจหยุดการทำงานได้ โดยจะสร้าง worker pthread ขึ้นมาทุกครั้งที่ต้องมีการแก้ชื่อ แล้วรันgetaddrinfo(3)Prism น่าสนใจ และตั้งคำถามว่ามีตัวอย่างการใช้ Prism เป็นเครื่องมือวิเคราะห์โค้ด Ruby หรือไม่
ตัวแปรสภาพแวดล้อม
RUBY_MAX_CPU=nใช้กำหนดจำนวนสูงสุดของ native threads โดยค่าเริ่มต้นคือ 8กำลังมองหาลิงก์ตัวอย่างที่ดีของการใช้ Prism และรู้สึกผิดหวังที่ในหน้าประกาศรีลีสแทบไม่มีอะไรนอกจาก "Notable APIs"
กล่าวว่านี่คือของขวัญคริสต์มาสที่สมบูรณ์แบบ