ประสิทธิภาพของ Ruby พัฒนาขึ้นมากในเวอร์ชันแล้วรุ่นเล่า… และทีมพัฒนา Ruby พยายามทุกวิถีทางเพื่อทำให้ Ruby เร็วยิ่งขึ้น!
หนึ่งในความพยายามเหล่านี้คือโครงการ 3×3
เป้าหมาย?
Ruby 3.0 จะเร็วกว่า Ruby 2.0 ถึง 3 เท่า .
ส่วนหนึ่งของโครงการนี้คือคอมไพเลอร์ MJIT ใหม่ ซึ่งเป็นหัวข้อของบทความนี้
อธิบาย MJIT
MJIT ย่อมาจาก “Method Based Just-in-Time Compiler”
หมายความว่าอย่างไร
Ruby รวบรวมโค้ดของคุณเป็น คำแนะนำ YARV , คำแนะนำเหล่านี้ดำเนินการโดย Ruby Virtual Machine
JIT เพิ่มเลเยอร์นี้อีกชั้นหนึ่ง
จะ รวบรวมคำสั่งที่ใช้บ่อย เป็นรหัสไบนารี่
ผลลัพธ์ที่ได้คือไบนารีที่ปรับให้เหมาะสมซึ่งรันโค้ดของคุณเร็วขึ้น
มันทำงานอย่างไร
มาดูกันว่า MJIT ทำงานอย่างไรเพื่อให้เข้าใจดีขึ้น
คุณสามารถเปิดใช้งาน JIT ด้วย Ruby 2.6 &the --jit
ตัวเลือก
ถูกใจสิ่งนี้ :
ruby --jit app.rb
Ruby 2.6 มาพร้อมกับชุดตัวเลือกเฉพาะ JIT ที่จะช่วยให้เราค้นพบว่ามันทำงานอย่างไร คุณสามารถดูตัวเลือกเหล่านี้ได้โดยเรียกใช้ ruby --help
.
นี่คือรายการตัวเลือก
- –jit-เดี๋ยว
- –jit-verbose
- –jit-save-temps
- –jit-max-cache
- –jit-min-calls
ละเอียด Thisนี้ ตัวเลือกดูเหมือนเป็นจุดเริ่มต้นที่ดี!
เราจะใช้ --jit-wait
. ด้วย ซึ่งจะทำให้ Ruby รอจนกว่าการรวบรวมโค้ด JIT เสร็จสิ้นก่อนที่จะเรียกใช้
ระหว่างการทำงานปกติ JIT จะคอมไพล์โค้ดในเธรดผู้ปฏิบัติงาน และไม่รอให้จบ
นี่คือคำสั่งที่คุณสามารถเรียกใช้เพื่อทดสอบสิ่งนี้:
ruby --disable-gems --jit --jit-verbose=1 --jit-wait -e "4.times { 123 }"
ภาพพิมพ์นี้ :
Successful MJIT finish
มันไม่น่าสนใจมากใช่ไหม
JIT ไม่ได้ทำอะไรเลย
ทำไม?
เพราะโดยค่าเริ่มต้น JIT จะทำงานเมื่อมีการเรียกเมธอด 5 ครั้งเท่านั้น (jit-min-calls
) ขึ้นไป
ถ้าเราเรียกใช้สิ่งนี้:
ruby --disable-gems --jit --jit-verbose=1 --jit-wait -e "5.times { 123 }"
ตอนนี้เราได้รับสิ่งที่น่าสนใจ :
JIT success (32.1ms): block in <main>@-e:1 -> /tmp/_ruby_mjit_p13921u0.c
นี่พูดว่าอะไรนะ
JIT รวบรวมบล็อกเพราะเราเรียกมัน 5 ครั้ง สิ่งนี้บอกคุณ:
- ใช้เวลาในการคอมไพล์ (
32.1ms
) - ตรง สิ่งที่รวบรวม (
block in <main>
) - ไฟล์ที่สร้างขึ้น (
/tmp/_ruby_mjit_p13921u0.c
) เป็นแหล่งที่มาของการรวบรวมนี้
ไฟล์นี้เป็นซอร์สโค้ด C ซึ่งรวบรวมเป็นไฟล์อ็อบเจ็กต์ (.o
) แล้วลงในไฟล์ไลบรารีที่ใช้ร่วมกัน (.so
)
คุณสามารถเข้าถึงไฟล์เหล่านี้ได้หากคุณเพิ่ม --jit-save-temps
ตัวเลือก
นี่คือตัวอย่าง :
นี่คือความเข้าใจในปัจจุบันของฉันเกี่ยวกับวิธีการทำงานของ JIT :
- นับการเรียกใช้เมธอด
- เมื่อเมธอดหนึ่งถูกเรียก 5 ครั้ง (ค่าเริ่มต้นสำหรับ
jit-min-calls
) ทริกเกอร์ JIT - ไฟล์ C ที่มีคำแนะนำสำหรับวิธีนี้ถูกสร้างขึ้น (เป็นคำสั่ง YARV แต่อยู่ในบรรทัด)
- การคอมไพล์เกิดขึ้นในเบื้องหลัง (เว้นแต่
--jit-wait
) โดยใช้คอมไพเลอร์ C ปกติเช่น GCC - เมื่อคอมไพล์เสร็จสิ้น ไฟล์ไลบรารีที่ใช้ร่วมกันที่ได้จะถูกใช้เมื่อมีการเรียกเมธอดนี้
มาดูกันว่าวิธีนี้ได้ผลแค่ไหน
การทดสอบ MJIT:เร็วกว่าจริงหรือไม่
เป้าหมายของ MJIT คือทำให้ Ruby เร็วขึ้น
ทำตอนนี้ดีแค่ไหน
มาดูกัน!
ขั้นแรก เกณฑ์มาตรฐานไมโคร:
เกณฑ์มาตรฐาน | ผลลัพธ์ (เทียบกับ Ruby 2.6 ที่ไม่มี JIT) |
---|---|
ในขณะที่ | เร็วขึ้น 8 เท่า |
ในขณะที่ต่อท้ายสตริง | เร็วขึ้น 10% |
ในขณะที่มีการคูณ (จำนวนเต็ม) | เร็วขึ้น 4 เท่า |
ในขณะที่มีการคูณ (Bignum) | ช้าลง 20% |
ตัวพิมพ์ใหญ่ | เร็วขึ้น 10% |
การจับคู่สตริง | ช้าลง 2% |
สตริงตรงกันหรือไม่ | เร็วขึ้น 10% |
อาร์เรย์ที่มีตัวเลขสุ่ม 10k | เร็วขึ้น 20% |
ดูเหมือนว่าการแสดงจะอยู่ทั่วๆ ไป แต่มีบางอย่างที่เราสรุปได้จากสิ่งนี้…
MJIT ชอบลูปมาก!
แต่การใช้งานที่ซับซ้อนกว่านี้จะเป็นอย่างไร
มาลองใช้แอปซินาตราง่ายๆ :
require 'sinatra' get '/' do "apples, oranges & bananas" end
มันอาจจะดูไม่มากนัก แต่โค้ดเล็กน้อยนี้ใช้วิธีการต่างๆ มากกว่า 500 วิธี เพียงพอที่จะให้ JIT มีงานทำ!
อย่างเจาะจง นี่คือ Sinatra 2.0.4 กับ Thin 1.7.2
คุณสามารถรันเบนช์มาร์กได้ด้วยคำสั่งนี้ (apache bench):
ab -c 20 -t 10 https://localhost:4567/
นี่คือผลลัพธ์ :
คุณสามารถบอกได้จากสิ่งเหล่านี้ว่า Ruby 2.6 เร็วกว่า 2.5 แต่ การเปิดใช้งาน JIT ทำให้ Sinatra ช้าลง 11% !
ทำไม?
ฉันไม่รู้ อาจเป็นเพราะค่าโสหุ้ยที่ JIT แนะนำ หรือเพราะโค้ดไม่ได้รับการปรับให้เหมาะสม
การทดสอบของฉันกับ C profiler (callgrind) เผยให้เห็นว่าการใช้โค้ดที่ปรับให้เหมาะสม JIT (ไฟล์ C ที่คอมไพล์ที่เราค้นพบก่อนหน้านี้) นั้นต่ำมากสำหรับ Sinatra (น้อยกว่า 2% ) แต่ก็สูงมาก (24.22% ) สำหรับคำสั่ง while ที่เพิ่มความเร็ว 8 เท่า
ผลลัพธ์ในขณะที่เปรียบเทียบกับ JIT :
ผลลัพธ์สำหรับการเปรียบเทียบ Sinatra กับ JIT :
นี่อาจเป็นส่วนหนึ่งของเหตุผล ฉันไม่ใช่ผู้เชี่ยวชาญด้านคอมไพเลอร์ ดังนั้นฉันจึงไม่สามารถสรุปได้จากเรื่องนี้
สรุป
MJIT คือ “Just-in-Time Compiler” ที่มีอยู่ใน Ruby 2.6 ซึ่งสามารถเปิดใช้งานได้ด้วย --jit
ธง. MJIT มีแนวโน้มดีและสามารถเร่งความเร็วโปรแกรมเล็กๆ บางโปรแกรมได้ แต่ก็ยังมีงานอีกมากที่ต้องทำ!
หากคุณชอบบทความนี้อย่าลืมแบ่งปันกับเพื่อน Ruby ของคุณ 🙂
ขอบคุณสำหรับการอ่าน