การปีนเขาอันยากลำบากของ GitHub: การเพิ่มประสิทธิภาพบรรทัด Diff เพื่อประสิทธิภาพสูงสุด
พูลรีเควสต์เป็นหัวใจสำคัญของ GitHub ที่ซึ่งวิศวกรจำนวนนับไม่ถ้วนทุ่มเทเวลาส่วนสำคัญในชีวิตการทำงานของพวกเขา ด้วยขนาดที่มหาศาลของ GitHub การจัดการพูลรีเควสต์ที่มีตั้งแต่การแก้ไขเล็กน้อยเพียงบรรทัดเดียวไปจนถึงการเปลี่ยนแปลงขนาดใหญ่ที่ครอบคลุมไฟล์นับพันและโค้ดนับล้านบรรทัด ประสบการณ์การตรวจสอบจึงต้องรวดเร็วและตอบสนองเป็นพิเศษ การเปิดตัวประสบการณ์ใหม่บนพื้นฐาน React สำหรับแท็บ Files changed ซึ่งตอนนี้เป็นค่าเริ่มต้นสำหรับผู้ใช้ทุกคน ถือเป็นการลงทุนที่สำคัญในการรับรองประสิทธิภาพที่แข็งแกร่ง โดยเฉพาะอย่างยิ่งสำหรับพูลรีเควสต์ขนาดใหญ่ที่ท้าทายเหล่านี้ ความมุ่งมั่นนี้เกี่ยวข้องกับการแก้ปัญหาที่ยากลำบากอย่างต่อเนื่อง เช่น การเรนเดอร์ที่ได้รับการปรับปรุง เวลาแฝงในการโต้ตอบ และการใช้หน่วยความจำ
ก่อนการเพิ่มประสิทธิภาพเหล่านี้ ในขณะที่ผู้ใช้ส่วนใหญ่ได้รับประสบการณ์ที่ตอบสนอง แต่พูลรีเควสต์ขนาดใหญ่กลับนำไปสู่ประสิทธิภาพที่ลดลงอย่างเห็นได้ชัดอย่างหลีกเลี่ยงไม่ได้ ในกรณีที่รุนแรง ฮีป JavaScript เกิน 1 GB, จำนวนโหนด DOM เกิน 400,000 และการโต้ตอบกับหน้าเว็บกลายเป็นเรื่องช้ามากหรือไม่สามารถใช้งานได้เลย เมตริกการตอบสนองที่สำคัญ เช่น Interaction to Next Paint (INP) พุ่งสูงเกินระดับที่ยอมรับได้ ทำให้ผู้ใช้รู้สึกถึงความล่าช้าในการป้อนข้อมูลอย่างชัดเจน บทความนี้เจาะลึกการเดินทางอย่างละเอียดที่ GitHub ได้ดำเนินการเพื่อปรับปรุงเมตริกประสิทธิภาพหลักเหล่านี้อย่างมาก พลิกโฉมประสบการณ์การตรวจสอบ diff
การนำทางคอขวดด้านประสิทธิภาพ: แนวทางหลายกลยุทธ์
เมื่อเริ่มการตรวจสอบประสิทธิภาพสำหรับแท็บ Files changed ก็เป็นที่ชัดเจนอย่างรวดเร็วว่าโซลูชัน 'กระสุนเงิน' เพียงอย่างเดียวจะไม่เพียงพอ เทคนิคที่ออกแบบมาเพื่อรักษาวางทุกคุณสมบัติและพฤติกรรมดั้งเดิมของเบราว์เซอร์ มักจะถึงขีดจำกัดเมื่อต้องรับมือกับโหลดข้อมูลที่สูงมาก ในทางกลับกัน มาตรการบรรเทาผลกระทบที่มุ่งเป้าไปที่การป้องกันสถานการณ์เลวร้ายที่สุดเพียงอย่างเดียว อาจทำให้เกิดข้อเสียที่ไม่พึงประสงค์สำหรับการตรวจสอบในชีวิตประจำวัน
แทนที่จะเป็นเช่นนั้น ทีมวิศวกรของ GitHub ได้พัฒนาชุดกลยุทธ์ที่ครอบคลุม โดยแต่ละกลยุทธ์ได้รับการออกแบบมาอย่างพิถีพิถันเพื่อจัดการกับขนาดและความซับซ้อนของพูลรีเควสต์ที่แตกต่างกัน กลยุทธ์เหล่านี้สร้างขึ้นบนสามหัวข้อหลัก:
- การเพิ่มประสิทธิภาพที่เน้นไปที่คอมโพเนนต์บรรทัด Diff: การเพิ่มประสิทธิภาพของประสบการณ์ diff หลักสำหรับพูลรีเควสต์ส่วนใหญ่ สิ่งนี้ทำให้มั่นใจว่าการตรวจสอบขนาดกลางและขนาดใหญ่ยังคงรวดเร็วโดยไม่ลดทอนฟังก์ชันการทำงานที่คาดหวัง เช่น การค้นหาในหน้าแบบเนทีฟ
- การลดทอนประสิทธิภาพอย่างสวยงามด้วย Virtualization: การรับรองการใช้งานสำหรับพูลรีเควสต์ที่ใหญ่ที่สุดโดยการจัดลำดับความสำคัญของการตอบสนองและความเสถียร และการจำกัดสิ่งที่เรนเดอร์อย่างชาญฉลาดในแต่ละช่วงเวลา
- การลงทุนในส่วนประกอบพื้นฐานและการปรับปรุงการเรนเดอร์: การนำการปรับปรุงมาใช้เพื่อให้เกิดประโยชน์ที่เพิ่มขึ้นในทุกขนาดของพูลรีเควสต์ โดยไม่ขึ้นอยู่กับโหมดการดูเฉพาะของผู้ใช้
เสาหลักเชิงกลยุทธ์เหล่านี้เป็นแนวทางในการทำงานของทีม ทำให้พวกเขาสามารถจัดการกับสาเหตุหลักของปัญหาประสิทธิภาพได้อย่างเป็นระบบ และเตรียมพร้อมสำหรับการปรับปรุงสถาปัตยกรรมในภายหลัง
การวิเคราะห์ V1: ต้นทุนของบรรทัด Diff ที่มีราคาแพง
การใช้งาน React-based เริ่มต้นของ GitHub ซึ่งเรียกว่า v1 เป็นรากฐานสำหรับมุมมอง diff ที่ทันสมัย เวอร์ชันนี้เป็นความพยายามอย่างจริงจังในการย้ายมุมมอง Rails แบบคลาสสิกมายัง React โดยให้ความสำคัญกับการสร้างคอมโพเนนต์ React ขนาดเล็กที่นำกลับมาใช้ใหม่ได้ และการรักษาโครงสร้าง DOM tree ที่ชัดเจน อย่างไรก็ตาม แนวทางนี้ แม้จะดูมีเหตุผลในช่วงแรก แต่กลับกลายเป็นคอขวดที่สำคัญในระดับขนาดใหญ่
ใน v1 การเรนเดอร์แต่ละบรรทัด diff เป็นการดำเนินการที่ใช้ทรัพยากรมาก บรรทัดเดียวในมุมมองแบบรวมมักจะแปลงเป็น DOM element ประมาณ 10 รายการ ในขณะที่มุมมองแบบแยกต้องใช้ใกล้เคียง 15 รายการ จำนวนนี้จะเพิ่มขึ้นอีกด้วยการไฮไลท์ไวยากรณ์ ซึ่งจะเพิ่มแท็ก <span> เข้ามาอีกมาก ในชั้น React, diff แบบรวมมีคอมโพเนนต์อย่างน้อยแปดรายการต่อบรรทัด และมุมมองแบบแยกมีอย่างน้อย 13 รายการ เหล่านี้เป็นจำนวนพื้นฐาน โดยสถานะ UI เพิ่มเติม เช่น ความคิดเห็น การวางเมาส์ และการโฟกัส จะเพิ่มคอมโพเนนต์เข้าไปอีก
สถาปัตยกรรม v1 ยังประสบปัญหาการเพิ่มจำนวนของตัวจัดการเหตุการณ์ React แม้จะดูไม่เป็นอันตรายในขนาดเล็ก แต่บรรทัด diff เดียวสามารถมีตัวจัดการเหตุการณ์ 20 รายการหรือมากกว่านั้น เมื่อคูณด้วยหลายพันบรรทัดในพูลรีเควสต์ขนาดใหญ่ สิ่งนี้จะเพิ่มขึ้นอย่างรวดเร็ว ทำให้เกิดโอเวอร์เฮดที่มากเกินไปและการใช้ฮีป JavaScript ที่เพิ่มขึ้น ความซับซ้อนนี้ไม่เพียงส่งผลกระทบต่อประสิทธิภาพเท่านั้น แต่ยังทำให้การพัฒนาและการบำรุงรักษายากขึ้นอีกด้วย การออกแบบเริ่มต้น ซึ่งมีประสิทธิภาพสำหรับข้อมูลที่มีขอบเขต ประสบปัญหาอย่างมากเมื่อเผชิญกับลักษณะที่ไม่มีขอบเขตของขนาดพูลรีเควสต์ที่หลากหลายของ GitHub
สรุปได้ว่า สำหรับทุกบรรทัด diff ใน v1 ระบบมี:
- องค์ประกอบ DOM tree อย่างน้อย 10-15 รายการ
- คอมโพเนนต์ React อย่างน้อย 8-13 รายการ
- ตัวจัดการเหตุการณ์ React อย่างน้อย 20 รายการ
- คอมโพเนนต์ React ขนาดเล็กที่นำกลับมาใช้ใหม่ได้จำนวนมาก
สถาปัตยกรรมนี้สัมพันธ์โดยตรงกับขนาดพูลรีเควสต์ที่ใหญ่ขึ้นด้วย INP ที่ช้าลงและการใช้ฮีป JavaScript ที่เพิ่มขึ้น ทำให้จำเป็นต้องมีการประเมินและออกแบบใหม่ขั้นพื้นฐาน
การปฏิวัติการเรนเดอร์: ผลกระทบของการเพิ่มประสิทธิภาพ V2
การเปลี่ยนผ่านไปสู่ v2 ถือเป็นการยกเครื่องสถาปัตยกรรมครั้งสำคัญ โดยมุ่งเน้นไปที่การเปลี่ยนแปลงที่ละเอียดและมีผลกระทบ ทีมงานยอมรับปรัชญาที่ว่า 'ไม่มีการเปลี่ยนแปลงใดเล็กเกินไปเมื่อพูดถึงประสิทธิภาพ โดยเฉพาะอย่างยิ่งในระดับที่ใหญ่' ตัวอย่างสำคัญคือการลบแท็ก <code> ที่ไม่จำเป็นออกจากเซลล์หมายเลขบรรทัด แม้การลดจำนวนโหนด DOM สองรายการต่อบรรทัด diff อาจดูเล็กน้อย แต่สำหรับ 10,000 บรรทัด สิ่งนี้เทียบเท่ากับการลดโหนดใน DOM ลง 20,000 รายการทันที ซึ่งแสดงให้เห็นว่าการเพิ่มประสิทธิภาพแบบกำหนดเป้าหมายและค่อยเป็นค่อยไปสามารถนำมาซึ่งการปรับปรุงที่สำคัญได้อย่างไร
การเปรียบเทียบภาพด้านล่างเน้นความซับซ้อนที่ลดลงจาก v1 ไป v2 ในระดับคอมโพเนนต์:

สถาปัตยกรรมการจัดองค์ประกอบที่คล่องตัว
นวัตกรรมหลักใน v2 คือการทำให้โครงสร้างคอมโพเนนต์ง่ายขึ้น ทีมงานลดจำนวนคอมโพเนนต์ React ต่อบรรทัด diff จากแปดเหลือสองรายการ สิ่งนี้ทำได้โดยการกำจัดโครงสร้างคอมโพเนนต์ที่ซ้อนกันลึก และสร้างคอมโพเนนต์เฉพาะสำหรับแต่ละบรรทัด diff แบบแยกและแบบรวม แม้ว่าจะมีการทำซ้ำโค้ดบางส่วน แต่มันช่วยลดความซับซ้อนในการเข้าถึงข้อมูลและลดความซับซ้อนโดยรวมลงอย่างมาก การจัดการเหตุการณ์ก็ถูกรวมศูนย์เช่นกัน ตอนนี้ถูกจัดการโดยตัวจัดการระดับบนสุดตัวเดียวโดยใช้ค่า data-attribute แทนที่ตัวจัดการเหตุการณ์แต่ละรายการจำนวนมากของ v1 แนวทางนี้ช่วยลดความซับซ้อนของโค้ดและเพิ่มประสิทธิภาพได้อย่างมาก
การจัดการสถานะอัจฉริยะและการเข้าถึงข้อมูลแบบ O(1)
บางทีการเปลี่ยนแปลงที่มีผลกระทบมากที่สุดคือการย้ายสถานะแอปที่ซับซ้อน เช่น การแสดงความคิดเห็นและเมนูบริบท ไปยังคอมโพเนนต์ลูกที่เรนเดอร์แบบมีเงื่อนไข ในสภาพแวดล้อมเช่น GitHub ที่พูลรีเควสต์สามารถมีได้มากกว่าหลายพันบรรทัด การที่ทุกบรรทัดต้องมีสถานะการแสดงความคิดเห็นที่ซับซ้อนเป็นสิ่งที่ไม่ประสิทธิภาพเมื่อมีเพียงส่วนน้อยเท่านั้นที่จะมีความคิดเห็น โดยการย้ายสถานะนี้ไปยังคอมโพเนนต์ที่ซ้อนกัน ความรับผิดชอบหลักของคอมโพเนนต์บรรทัด diff จึงกลายเป็นการเรนเดอร์โค้ดอย่างแท้จริง ซึ่งสอดคล้องกับหลักการความรับผิดชอบเดียว (Single Responsibility Principle)
นอกจากนี้ v2 ยังแก้ไขปัญหาการค้นหาแบบ O(n) และ useEffect hooks ที่มากเกินไปซึ่งเป็นปัญหาของ v1 ทีมงานได้นำกลยุทธ์สองส่วนมาใช้: จำกัดการใช้ useEffect อย่างเข้มงวดในระดับบนสุดของไฟล์ diff และกำหนดกฎการ linting เพื่อป้องกันการนำกลับมาใช้ใหม่ในคอมโพเนนต์ที่ห่อหุ้มบรรทัด สิ่งนี้ช่วยให้มั่นใจถึงการ memoization ที่แม่นยำและพฤติกรรมที่คาดเดาได้ ในขณะเดียวกัน เครื่องสถานะส่วนกลางและ diff ถูกออกแบบใหม่เพื่อใช้ประโยชน์จากการค้นหาแบบ O(1) (เวลาคงที่) โดยใช้อ็อบเจกต์ JavaScript Map สิ่งนี้ช่วยให้สามารถใช้ selectors ที่รวดเร็วและสอดคล้องกันสำหรับการดำเนินการทั่วไป เช่น การเลือกบรรทัดและการจัดการความคิดเห็น ซึ่งช่วยเพิ่มคุณภาพโค้ด ปรับปรุงประสิทธิภาพ และลดความซับซ้อนได้อย่างมากโดยการรักษาโครงสร้างข้อมูลที่ราบเรียบและถูกแมป แนวทางที่พิถีพิถันนี้ในการ เพิ่มประสิทธิภาพเวิร์กโฟลว์ของนักพัฒนา และสถาปัตยกรรมพื้นฐานช่วยให้มั่นใจได้ถึงระบบที่แข็งแกร่งและปรับขนาดได้
ผลกระทบที่วัดผลได้: V2 นำมาซึ่งผลกำไรที่วัดผลได้
การเพิ่มประสิทธิภาพทางสถาปัตยกรรมและระดับโค้ดที่พิถีพิถันซึ่งนำมาใช้ใน v2 ได้สร้างการปรับปรุงที่ลึกซึ้งและวัดผลได้ในเมตริกประสิทธิภาพหลัก ระบบใหม่ทำงานได้เร็วขึ้นอย่างมาก พร้อมการลดการใช้ฮีป JavaScript และคะแนน INP ลงอย่างมหาศาล ตารางต่อไปนี้แสดงให้เห็นถึงการปรับปรุงที่น่าทึ่งที่สังเกตได้ในพูลรีเควสต์ตัวอย่างที่มีการเปลี่ยนแปลง 10,000 บรรทัดในการตั้งค่า split diff:
| เมตริก | v1 | v2 | การปรับปรุง |
|---|---|---|---|
| ฮีป JavaScript | 1GB+ | 250MB | 75% |
| โหนด DOM | 400,000+ | 80,000 | 80% |
| INP p95 | 1000ms+ | 100ms | 90% |
ตัวเลขเหล่านี้เน้นย้ำถึงความสำเร็จของกลยุทธ์หลายด้านของ GitHub การลดขนาดฮีป JavaScript 75% และการลดโหนด DOM 80% ไม่เพียงหมายถึงการใช้ทรัพยากรเบราว์เซอร์ที่เบาลงเท่านั้น แต่ยังส่งผลโดยตรงต่ออินเทอร์เฟซที่เสถียรและตอบสนองได้ดีขึ้น การปรับปรุงที่น่าทึ่งที่สุดคือ การลด INP p95 (เปอร์เซ็นไทล์ที่ 95 ของความล่าช้าในการโต้ตอบ) ลง 90% หมายความว่าตอนนี้ 95% ของการโต้ตอบของผู้ใช้เสร็จสมบูรณ์ภายในเวลาเพียง 100 มิลลิวินาที ซึ่งแทบจะกำจัดความล่าช้าในการป้อนข้อมูลที่เคยเป็นปัญหาในพูลรีเควสต์ขนาดใหญ่ใน v1 สิ่งนี้ช่วยเพิ่มประสบการณ์ผู้ใช้อย่างมีนัยสำคัญ ทำให้การตรวจสอบโค้ดขนาดใหญ่รู้สึกราบรื่นและตอบสนองได้ดีเหมือนกับการตรวจสอบขนาดเล็ก
ความมุ่งมั่นของ GitHub ในการปรับปรุงอย่างต่อเนื่อง ซึ่งเห็นได้จากการเจาะลึกการเพิ่มประสิทธิภาพบรรทัด diff นี้ เป็นเครื่องพิสูจน์ถึงความทุ่มเทของพวกเขาในการจัดหาแพลตฟอร์มสำหรับนักพัฒนาระดับโลก ด้วยการวิเคราะห์คอขวดด้านประสิทธิภาพอย่างเข้มงวดและการนำโซลูชันทางสถาปัตยกรรมที่กำหนดเป้าหมายมาใช้ พวกเขาไม่เพียงแก้ไขปัญหาความสามารถในการปรับขนาดที่สำคัญเท่านั้น แต่ยังสร้างมาตรฐานใหม่สำหรับการตอบสนองในผลิตภัณฑ์หลักของพวกเขาด้วย การมุ่งเน้นที่ประสิทธิภาพนี้ทำให้วิศวกรสามารถมีส่วนร่วมในงานที่สำคัญ เช่น การตรวจสอบโค้ดได้อย่างมีประสิทธิภาพ ซึ่งท้ายที่สุดจะนำไปสู่ คุณภาพและความปลอดภัยของโค้ด ที่สูงขึ้น และสภาพแวดล้อมการพัฒนาที่มีประสิทธิผลมากขึ้น
คำถามที่พบบ่อย
What is the 'Files changed' tab in GitHub pull requests and why was its performance critical?
What were the primary performance challenges GitHub faced with large pull requests in the v1 architecture?
How did GitHub approach solving the complex performance issues, moving beyond a 'silver bullet' solution?
What were the key limitations of the 'v1' diff rendering architecture that made it unsustainable for scale?
What specific architectural changes were implemented in 'v2' to drastically improve diff line performance?
How did the GitHub engineering team achieve quantifiable improvements in JavaScript heap, DOM nodes, and INP metrics with v2?
What is Interaction to Next Paint (INP) and why is its improvement significant for GitHub's user experience?
อัปเดตข่าวสาร
รับข่าว AI ล่าสุดในกล่องจดหมายของคุณ
