15 คะแนน โดย darjeeling 2026-03-29 | ยังไม่มีความคิดเห็น | แชร์ทาง WhatsApp

ผลลัพธ์สำคัญ

แพลตฟอร์ม ประสิทธิภาพ JIT ที่ดีขึ้น (เทียบกับอินเทอร์พรีเตอร์แบบ tail-calling)
macOS AArch64 +11~12%
x86_64 Linux +5~6%

ช่วงของเบนช์มาร์ก: ตั้งแต่ช้าลง 20% ไปจนถึงเร็วขึ้นมากกว่า 100% (ไม่รวมไมโครบेनช์มาร์ก unpack_sequence)

  • บรรลุเป้าหมาย: ทำได้ถึงเป้าหมายของ 3.15 (ดีขึ้น 5%) เร็วกว่ากำหนดมากกว่า 1 ปี
  • รองรับ free-threading: ยังไม่เสร็จสมบูรณ์ กำลังดำเนินการโดยตั้งเป้าไว้ที่ 3.15/3.16

บทเรียนสำคัญ

  1. สปอนเซอร์ถอนตัว = วิกฤต → เปลี่ยนเป็นการขับเคลื่อนโดยคอมมูนิตี้
    แม้สปอนเซอร์หลักของทีม Faster CPython จะถอนตัวในปี 2025 แต่ด้วยการมีส่วนร่วมโดยสมัครใจจากคอมมูนิตี้ กลับมีผู้มีส่วนร่วมเพิ่มขึ้นและสร้างผลงานได้

  2. แยกปัญหาซับซ้อนออกเป็นชิ้นเล็ก ๆ
    แม้จะเป็นโปรเจกต์ที่เข้าถึงยากอย่าง JIT แต่ด้วยการแตกงานเป็นหน่วยย่อยและมีคู่มือที่ชัดเจน ทำให้คนที่ไม่ใช่ผู้เชี่ยวชาญแต่มีประสบการณ์เขียน C ก็สามารถมีส่วนร่วมได้

  3. การลด bus factor คือเครื่องชี้วัดของโปรเจกต์ที่แข็งแรง
    ผู้มีส่วนร่วมในส่วนกลาง (optimizer) เพิ่มจาก 2 คนเป็น 4 คน และนักพัฒนาที่ไม่ใช่ core developer ก็เติบโตขึ้นมาเป็นผู้มีส่วนร่วมหลักได้

  4. โครงสร้างพื้นฐานด้านการวัดผลเปลี่ยนความเร็วในการพัฒนาได้
    ระบบที่รายงานประสิทธิภาพ JIT ทุกวัน (doesjitgobrrr.com) มีบทบาทชี้ขาดทั้งในการตรวจพบ regression ได้ตั้งแต่เนิ่น ๆ และในการสร้างแรงจูงใจ

  5. การแลกเปลี่ยนระหว่างคอมมูนิตี้ช่วยยกระดับความสามารถทางเทคนิค
    การแลกเปลี่ยนกับทีม PyPy และการพูดคุยอย่างไม่เป็นทางการกับนักพัฒนาคอมไพเลอร์ นำไปสู่ความก้าวหน้าทางเทคนิคอย่างเป็นรูปธรรม


พื้นหลังและผลลัพธ์

[IMG] กราฟประสิทธิภาพ JIT (ณ วันที่ 17 มีนาคม 2026, ยิ่งต่ำยิ่งเร็วกว่าอินเทอร์พรีเตอร์)
(ประสิทธิภาพ JIT ณ วันที่ 17 มีนาคม 2026 ยิ่งต่ำยิ่งเร็วกว่าเมื่อเทียบกับอินเทอร์พรีเตอร์ ที่มาของภาพ: doesjitgobrrr.com)

มีข่าวดีคือ บน macOS AArch64 นั้น CPython JIT ทำได้ถึงเป้าหมายด้านประสิทธิภาพที่ตั้งไว้ (แบบถ่อมตัวมาก) เร็วกว่ากำหนด มากกว่า 1 ปี และบน x86_64 Linux ก็ทำได้เร็วกว่ากำหนด หลายเดือน สำหรับ JIT ใน 3.15 alpha นั้น เร็วกว่าอินเทอร์พรีเตอร์แบบ tail-calling ราว 11~12% บน macOS AArch64 และเร็วกว่าอินเทอร์พรีเตอร์มาตรฐาน 5~6% บน x86_64 Linux ตัวเลข เหล่านี้เป็นค่าเฉลี่ยเรขาคณิตและยังเป็นตัวเลขเบื้องต้น ช่วงผลลัพธ์จริงมีตั้งแต่ ช้าลง 20% ไปจนถึงเร็วขึ้นมากกว่า 100% (unpack_sequence ไมโครบेनช์มาร์กไม่รวมอยู่ด้วย) แม้ยังไม่มีการรองรับ free-threading อย่างสมบูรณ์ แต่กำลังตั้งเป้าไว้สำหรับ 3.15/3.16 ตอนนี้ JIT กลับมาอยู่บนเส้นทางอีกครั้งแล้ว

จะเน้นย้ำแค่ไหนก็ไม่พอว่ามันยากขนาดไหนกว่าจะมาถึงจุดนี้ได้ เคยมีช่วงหนึ่งที่มีความสงสัยอย่างจริงจังว่าโปรเจกต์ JIT จะสร้างการเพิ่มความเร็วที่มีความหมายได้จริงหรือไม่ เมื่อมองย้อนกลับไป JIT ดั้งเดิมของ CPython แทบไม่ได้เพิ่มความเร็วเลย เมื่อ 8 เดือนก่อน ผมเขียน บทความย้อนมอง JIT ว่า JIT ดั้งเดิมของ CPython ใน 3.13 และ 3.14 หลายครั้งกลับช้ากว่าอินเทอร์พรีเตอร์ด้วยซ้ำ ตอนนั้นก็เป็นช่วงที่ทีม Faster CPython สูญเสียการสนับสนุนจากสปอนเซอร์หลักพอดี ผมเป็นอาสาสมัครจึงไม่ได้รับผลกระทบโดยตรง แต่เพื่อนร่วมงานที่ทำงานอยู่ที่นั่นได้รับผลกระทบ และช่วงหนึ่งอนาคตของ JIT ก็ดูไม่แน่นอน

แล้วอะไรที่ต่างไปจาก 3.13 และ 3.14? ผมจะไม่เล่าเป็นเรื่องฮีโร่ว่าเรากอบกู้ JIT จากวิกฤตล้มเหลวได้ด้วยไหวพริบอย่างไร เพราะพูดตามตรงแล้ว ความสำเร็จในตอนนี้ส่วนใหญ่เกิดจาก โชค จังหวะที่เหมาะสม สถานที่ที่เหมาะสม คนที่เหมาะสม และการตัดสินใจที่เหมาะสม ผมคิดอย่างจริงจังเลยว่าหากขาดใครคนใดคนหนึ่งในบรรดาผู้มีส่วนร่วมหลักของ JIT อย่าง Savannah Ostrowski, Mark Shannon, Diego Russo, Brandt Bucher และผมเอง สิ่งนี้คงไม่เกิดขึ้น ผมขอเอ่ยชื่อผู้มีส่วนร่วม JIT คนอื่น ๆ เพิ่มอีกสักหน่อยเพื่อไม่ให้ตกหล่น: Hai Zhu, Zheaoli, Tomas Roun, Reiden Ong, Donghee Na และอาจมีอีกหลายคนที่ผมไม่ได้กล่าวถึง

ผมอยากพูดถึง ผู้คน และ โชค เล็กน้อย ซึ่งเป็นส่วนที่ไม่ค่อยถูกพูดถึงของ JIT ถ้าคุณสนใจรายละเอียดทางเทคนิค ดูได้ที่นี่


ตอนที่ 1: JIT ที่ขับเคลื่อนโดยคอมมูนิตี้

ทีม Faster CPython สูญเสียสปอนเซอร์หลักในปี 2025 ผมจึงเสนอ แนวคิดการดูแลโดยคอมมูนิตี้ ทันที ตอนนั้นยังไม่แน่ใจนักว่าจะไปได้ดีหรือไม่ โปรเจกต์ JIT เป็นที่รู้กันว่าไม่เหมาะกับผู้มีส่วนร่วมหน้าใหม่ เพราะในอดีตต้องอาศัยความรู้เฉพาะทางล่วงหน้าจำนวนมาก

ในงาน CPython core sprint ที่เคมบริดจ์ ทีมแกนหลักของ JIT ได้มาพบกันและวาง แผน ว่า ภายใน 3.15 จะทำ JIT ให้เร็วขึ้น 5% และภายใน 3.16 จะทำให้เร็วขึ้น 10% พร้อมรองรับ free-threading อีกสิ่งที่ได้รับความสนใจน้อยกว่าแต่สำคัญต่อสุขภาพของโปรเจกต์มากคือ การลด bus factor เราต้องการผู้ดูแลที่ยัง active มากกว่า 2 คนในแต่ละส่วนของ JIT ทั้ง 3 ชั้น ได้แก่ ตัวเลือก region ในฝั่ง frontend, optimizer ในฝั่ง middle-end และ code generator ในฝั่ง backend

ก่อนหน้านี้ ใน middle-end ของ JIT มีผู้มีส่วนร่วมแบบต่อเนื่องที่ active อยู่เพียง 2 คนเท่านั้น แต่วันนี้ middle-end ของ JIT มีผู้มีส่วนร่วมแบบต่อเนื่องที่ active อยู่ 4 คนแล้ว และนักพัฒนาที่ไม่ใช่ core developer 2 คน (Hai Zhu และ Reiden) ก็เติบโตขึ้นมาเป็นสมาชิกที่มีความสามารถและมีคุณค่า

สิ่งที่ได้ผลในการดึงคนเข้ามาคือแนวปฏิบัติด้านวิศวกรรมซอฟต์แวร์ทั่วไป: แยกปัญหาซับซ้อนออกเป็นส่วนที่จัดการได้ Brandt เริ่มทำสิ่งนี้ก่อนใน 3.14 โดยเปิด mega issue หลายรายการเพื่อแบ่งงาน optimization ของ JIT ออกเป็นงานง่าย ๆ เช่น “ลอง optimize คำสั่งเดี่ยวใน JIT” ผมนำไอเดียของ Brandt มาต่อยอดและใช้กับ 3.15 ด้วย โชคดีที่ผมได้งานที่ง่ายกว่า คือ issue สำหรับแปลงคำสั่งของอินเทอร์พรีเตอร์ให้อยู่ในรูปแบบที่ optimize ได้ง่าย ผมจัดทำ คำแนะนำแบบละเอียดมาก ให้พร้อมลงมือทำทันทีเพื่อสนับสนุนผู้มีส่วนร่วมหน้าใหม่ และยังแยกขอบเขตของแต่ละหน่วยงานให้ชัดเจนด้วย ดูเหมือนว่าสิ่งนี้จะช่วยได้ มีผู้มีส่วนร่วม 11 คนรวมถึงผมทำงานใน issue นั้น และช่วยกันแปลงเกือบทั้งอินเทอร์พรีเตอร์ให้อยู่ในรูปแบบที่เป็นมิตรต่อ JIT optimizer แก่นสำคัญคือเราเปลี่ยน JIT จากก้อนทึบที่เข้าใจยาก ให้กลายเป็นสิ่งที่ โปรแกรมเมอร์ C ที่ไม่มีประสบการณ์ด้าน JIT ก็มีส่วนร่วมได้

วิธีอื่นที่ได้ผลเช่นกันคือ การให้กำลังใจผู้คนและร่วมฉลองความสำเร็จทั้งเล็กและใหญ่ ทุก PR ของ JIT มีผลลัพธ์ที่ชัดเจน และผมคิดว่านั่นช่วยให้ผู้คนรู้สึกว่ามีทิศทาง

ความพยายาม optimization โดยคอมมูนิตี้ให้ผลจริง JIT พัฒนาจากที่เร็วกว่า 1% บน x86_64 Linux ไปเป็นเร็วกว่า 3~4% (ดูเส้นสีน้ำเงินด้านล่าง):

[IMG] ประสิทธิภาพ JIT เทียบกับอินเทอร์พรีเตอร์ในช่วงที่คอมมูนิตี้ช่วยกัน optimize
(ที่มาของภาพ: doesjitgobrrr.com)


ตอนที่ 2: การตัดสินใจที่โชคดี

Trace Recording

ย้ำอีกครั้งว่า ผมคิดว่าส่วนใหญ่เป็นเรื่องของโชค ที่งาน CPython core sprint ในเคมบริดจ์ Brandt โน้มน้าวให้ผมเขียน frontend ของ JIT ใหม่เป็นแบบ tracing ตอนแรกผมไม่ชอบแนวคิดนี้ แต่ก็มีความดื้ออยู่บ้างเลยคิดว่า “งั้นลองเขียนใหม่เพื่อพิสูจน์ว่ามันใช้ไม่ได้”

ต้นแบบแรกใช้งานได้ใน 3 วัน แต่ต้องใช้เวลาอีก 1 เดือนกว่าจะทำให้มัน JIT ได้อย่างถูกต้องพร้อมผ่าน test suite ผลลัพธ์แรกแย่มาก บน x86_64 Linux มันช้าลงราว 6% ตอนที่ผมเกือบจะยอมแพ้ ก็เกิดอุบัติเหตุที่โชคดีขึ้น: ผมเข้าใจข้อเสนอของ Mark ผิด

Mark เสนอให้ผูก dispatch table แบบ threaded เข้ากับอินเทอร์พรีเตอร์ เพื่อให้อินเทอร์พรีเตอร์มี dispatch table สองชุด (ชุดหนึ่งสำหรับอินเทอร์พรีเตอร์ปกติ อีกชุดสำหรับ tracing) แต่ผมเข้าใจผิดและสร้างเวอร์ชันที่สุดโต่งกว่านั้น: แทนที่จะมีเวอร์ชัน tracing ของคำสั่งทั่วไป ผมทำให้มีเพียงคำสั่งเดียวที่ทำหน้าที่ tracing แล้วให้ทุกคำสั่งในตารางชุดที่สองชี้ไปยังคำสั่งนั้น ปรากฏว่านี่เป็นการตัดสินใจที่ดีมาก วิธีสองตารางแบบแรกเริ่มทำให้ขนาดอินเทอร์พรีเตอร์เพิ่มเป็นสองเท่า เกิด code bloat และช้าลงตามธรรมชาติอย่างมาก แต่การใช้เพียงคำสั่งเดียวร่วมกับสองตาราง ทำให้ขนาดอินเทอร์พรีเตอร์เพิ่มขึ้นแค่เท่ากับคำสั่งเดียว และยังรักษาให้อินเทอร์พรีเตอร์พื้นฐานเร็วมากได้ เราเรียกกลไกนี้อย่างเอ็นดูว่า dual dispatch

ตัวเลขที่แสดงว่า Trace Recording สำคัญแค่ไหนคือ coverage ของโค้ด JIT เพิ่มขึ้น 50% ซึ่งหมายความว่า optimization ทั้งหมดในอนาคตคงจะได้ผลน้อยลง (พูดแบบง่าย ๆ) ถึง 50% หากไม่มีมัน

ขอบคุณ Brandt และ Mark ที่ช่วยนำทางจนเจอทางแก้ที่ยอดเยี่ยมนี้โดยบังเอิญ

การกำจัด reference count

อีกหนึ่งการตัดสินใจที่โชคดีคือการลองกำจัด reference count ออก แนวคิดนี้เดิมเป็นงานของ Matt Page ใน bytecode optimizer ของ CPython ผมพบว่าแม้จะมีงานใน bytecode optimizer แล้ว แต่ในโค้ดที่ JIT แล้วก็ยังคงเหลือ branch หนึ่งจุดต่อการลด reference count แต่ละครั้ง ผมเลยคิดว่า “ถ้าลองลบ branch นี้ออกจะเป็นอย่างไร” โดยไม่รู้เลยว่าจะช่วยได้มากแค่ไหน ปรากฏว่าแม้จะเป็นเพียง branch เดียวก็มีต้นทุนค่อนข้างสูงจริง ๆ และถ้ามี branch มากกว่า 1 จุดในทุกคำสั่ง Python มันก็จะสะสมขึ้นเรื่อย ๆ

อีกเรื่องที่โชคดีคือ งานนี้แยกทำแบบขนานได้ง่ายมาก และยังเป็นเครื่องมือที่ดีในการสอนคนให้เข้าใจทั้งอินเทอร์พรีเตอร์และ JIT นี่จึงกลายเป็น optimization หลักที่ผมใช้มอบหมายงานให้คนอื่นใน Python 3.15 JIT ส่วนใหญ่เป็นกระบวนการ refactor แบบ manual แต่ก็เปิดโอกาสให้ผู้คนได้เรียนรู้โดยไม่ต้องโดนส่วนที่ซับซ้อนที่สุดของ JIT ถาโถมใส่


ตอนที่ 3: ทีมที่ยอดเยี่ยม

เรามีทีมอินฟราที่เยี่ยมมาก จริง ๆ แล้วมันคือคนคนเดียว และตามความเป็นจริง “ทีม” ของเราตอนนี้ก็คือเครื่อง 4 เครื่องที่กำลังทำงานอยู่ในตู้เสื้อผ้าของ Savannah ถึงอย่างนั้น Savannah ก็ทำงานได้เทียบเท่าทั้งทีมอินฟราเพื่อ JIT หากไม่มีวิธีรายงานตัวเลขด้านประสิทธิภาพ JIT ก็คงไม่มีทางที่ JIT จะพัฒนาได้เร็วขนาดนี้ ผลการรัน JIT รายวันเป็นตัวเปลี่ยนเกมในวงจรป้อนกลับ มันช่วยจับ regression ของ JIT และทำให้เรายืนยันได้ว่า optimization ของเราทำงานจริง

Mark ยอดเยี่ยมมากในเชิงเทคนิค ผมคงไม่พูดมากกว่านี้ เพราะคิดว่าอินเทอร์เน็ตชื่นชมเขามากพอแล้ว :)

Diego ก็ยอดเยี่ยมเช่นกัน เขาดูแลงาน JIT บนฮาร์ดแวร์ ARM และช่วงหลังยังเริ่มทำให้ JIT เป็นมิตรกับ profiler มากขึ้น จะย้ำแค่ไหนก็ไม่พอว่านี่เป็นปัญหาที่ยากเพียงใด

Brandt เป็นผู้วางรากฐานเดิมของ backend สำหรับ machine code ถ้าไม่มีสิ่งนี้ ผู้มีส่วนร่วมหน้าใหม่ก็คงต้องมาเขียน assembler เอง และนั่นอาจทำให้ผู้คนห่างหายไปมากกว่านี้


ตอนที่ 4: คุยกับผู้คน

ผมอยากเน้นถึงคุณค่าของการพูดคุยกับผู้คนและการแบ่งปันไอเดีย

ขอบคุณ CF Bolz-Tereick ที่สอนผมเรื่อง PyPy มากมาย ผมใช้เวลาหลายเดือนดูซอร์สโค้ดของ PyPy และคิดว่านั่นทำให้ผมกลายเป็นนักพัฒนา JIT ที่ดีขึ้นในภาพรวม CF ยังช่วยเหลืออย่างใจดีมากทุกครั้งที่ผมต้องการความช่วยเหลือ

ผมมีวงแชตคอมไพเลอร์แบบสบาย ๆ ร่วมกับ Max Bernstein และถ้าไม่มีมัน ผมคงหมดแรงจูงใจไปนานแล้ว Max เป็นทั้งนักเขียนที่มีผลงานมากมายและผู้เชี่ยวชาญด้านคอมไพเลอร์ที่เข้าถึงง่าย

ไอเดียไม่ได้เกิดขึ้นอย่างโดดเดี่ยว ผมคิดว่าการได้คลุกคลีกับนักพัฒนาคอมไพเลอร์อยู่พักหนึ่งทำให้ผมเขียน JIT ได้เก่งขึ้น อย่างน้อยที่สุด การได้ไปดู PyPy ก็ช่วยเปิดมุมมองของผมได้มาก!


สรุป

ผู้คนสำคัญมาก และถ้ามีโชคช่วยอีกนิด JIT go brrr.

ยังไม่มีความคิดเห็น

ยังไม่มีความคิดเห็น