Code Velocity
დეველოპერის ხელსაწყოები

დიფის ხაზების მუშაობა: GitHub-ის აღმართი ოპტიმიზაციისკენ

·7 წუთი კითხვა·GitHub·ორიგინალი წყარო
გაზიარება
დიაგრამა, რომელიც ასახავს GitHub-ის დიფის ხაზების მუშაობის გაუმჯობესებას, ოპტიმიზებულ ხედში შემცირებული DOM კვანძებისა და JavaScript-ის ჰიპის ხაზგასმით.

GitHub-ის აღმართი: დიფის ხაზების ოპტიმიზაცია პიკური მუშაობისთვის

პულ რექუესთები GitHub-ის ცოცხალი გულია, სადაც უამრავი ინჟინერი პროფესიული ცხოვრების მნიშვნელოვან ნაწილს უთმობს. GitHub-ის უზარმაზარი მასშტაბის გათვალისწინებით, პულ რექუესთების დამუშავება, რომლებიც მერყეობს მცირე ერთხაზიანი შესწორებებიდან ათასობით ფაილსა და მილიონობით ხაზზე გაშლილ კოლოსალურ ცვლილებებამდე, განხილვის გამოცდილება უნდა დარჩეს განსაკუთრებულად სწრაფი და პასუხისმგებლიანი. Files changed ჩანართისთვის ახალი React-ზე დაფუძნებული გამოცდილების ბოლო გამოქვეყნებამ, რომელიც ახლა ყველა მომხმარებლისთვის ნაგულისხმევია, მნიშვნელოვანი ინვესტიცია აღნიშნა მტკიცე მუშაობის უზრუნველსაყოფად, განსაკუთრებით ამ რთული დიდი პულ რექუესთებისთვის. ეს ვალდებულება მოიცავდა რთული პრობლემების მუდმივ გადაწყვეტას, როგორიცაა ოპტიმიზებული რენდერინგი, ურთიერთქმედების შეყოვნება და მეხსიერების მოხმარება.

ამ ოპტიმიზაციებამდე, სანამ მომხმარებელთა უმრავლესობა სარგებლობდა პასუხისმგებლიანი გამოცდილებით, დიდი პულ რექუესთები აუცილებლად იწვევდა მუშაობის შესამჩნევ შემცირებას. ექსტრემალურ შემთხვევებში JavaScript-ის ჰიპი აღემატებოდა 1 გბ-ს, DOM კვანძების რაოდენობა 400,000-ს აჭარბებდა, ხოლო გვერდის ურთიერთქმედებები ძლიერ შენელებული ან თუნდაც გამოუსადეგარი ხდებოდა. ძირითადი პასუხისმგებლობის მეტრიკები, როგორიცაა Interaction to Next Paint (INP), სცდებოდა მისაღებ დონეს, რაც მომხმარებლებისთვის შეყვანის შეყოვნების ხელშესახებ შეგრძნებას ქმნიდა. ეს სტატია დეტალურად აღწერს GitHub-ის მიერ განვლილ გზას ამ ძირითადი მუშაობის მეტრიკების დრამატული გაუმჯობესებისთვის, რითაც გარდაქმნა დიფის განხილვის გამოცდილება.

მუშაობის ბოთლ넥ების ნავიგაცია: მრავალსტრატეგიული მიდგომა

Files changed ჩანართისთვის მუშაობის კვლევის დაწყებისას, სწრაფად გამოჩნდა, რომ ერთი „ვერცხლის ტყვიის“ გადაწყვეტა არ იქნებოდა საკმარისი. ტექნიკები, რომლებიც მიზნად ისახავდა ყოველი ფუნქციისა და ბრაუზერის მშობლიური ქცევის შენარჩუნებას, ხშირად აღწევდა ზღვარს ექსტრემალური მონაცემთა დატვირთვით. პირიქით, შემამსუბუქებელმა ზომებმა, რომლებიც მიმართული იყო მხოლოდ ყველაზე ცუდი სცენარების თავიდან აცილებისკენ, შესაძლოა არასასურველი კომპრომისები შემოეტანა ყოველდღიური მიმოხილვებისთვის.

ამის ნაცვლად, GitHub-ის საინჟინრო გუნდმა შეიმუშავა სტრატეგიების ყოვლისმომცველი ნაკრები, რომელთაგან თითოეული ზედმიწევნით იყო შექმნილი პულ რექუესთის კონკრეტული ზომებისა და სირთულეების მოსაგვარებლად. ეს სტრატეგიები აგებული იყო სამ ძირითად თემაზე:

  1. დიფის ხაზის კომპონენტების ფოკუსირებული ოპტიმიზაცია: ძირითადი დიფის გამოცდილების ეფექტურობის გაზრდა პულ რექუესთების უმრავლესობისთვის. ამან უზრუნველყო საშუალო და დიდი მიმოხილვების სწრაფი შესრულება მშობლიური find-in-page-ის მსგავსი მოსალოდნელი ფუნქციების კომპრომისის გარეშე.
  2. ნატიფი დეგრადაცია ვირტუალიზაციით: უზრუნველყოფილი იქნა გამოყენებადობა ყველაზე დიდი პულ რექუესთებისთვის პასუხისმგებლობისა და სტაბილურობის პრიორიტეტიზებით, და ინტელექტუალურად შეზღუდული იქნა ის, რაც ნებისმიერ მომენტში რენდერდება.
  3. ინვესტიცია ფუნდამენტურ კომპონენტებსა და რენდერინგის გაუმჯობესებებში: განხორციელდა გაუმჯობესებები, რომლებმაც კუმულაციური სარგებელი მოუტანა პულ რექუესთის ყველა ზომას, მომხმარებლის კონკრეტული ნახვის რეჟიმის მიუხედავად.

ამ სტრატეგიულმა სვეტებმა გუნდს საშუალება მისცა სისტემატურად მოეგვარებინა მუშაობის პრობლემების ძირეული მიზეზები და მოემზადებინა ნიადაგი შემდგომი არქიტექტურული დახვეწებისთვის.

V1-ის დეკონსტრუქცია: ძვირადღირებული დიფის ხაზის ფასი

GitHub-ის თავდაპირველმა React-ზე დაფუძნებულმა იმპლემენტაციამ, რომელიც მოხსენიებულია როგორც v1, საფუძველი ჩაუყარა თანამედროვე დიფის ხედს. ეს ვერსია იყო გულწრფელი მცდელობა, რომ კლასიკური Rails ხედი React-ში გადაეტანათ, პრიორიტეტი მიენიჭა მცირე, ხელახლა გამოყენებადი React კომპონენტების შექმნას და DOM ხის მკაფიო სტრუქტურის შენარჩუნებას. თუმცა, ეს მიდგომა, თავდაპირველად ლოგიკური, მნიშვნელოვანი ბოთლნეკი აღმოჩნდა მასშტაბირებისას.

v1-ში, თითოეული დიფის ხაზის რენდერინგი ძვირადღირებული ოპერაცია იყო. ერთიან ხედში ერთი ხაზი, როგორც წესი, დაახლოებით 10 DOM ელემენტს ნიშნავდა, ხოლო გაყოფილი ხედი 15-ს უახლოვდებოდა. ეს რაოდენობა კიდევ უფრო გაიზრდებოდა სინტაქსის ხაზგასმით, რაც გაცილებით მეტ <span> ტეგს შემოიტანდა. React-ის დონეზე, გაერთიანებული დიფები შეიცავდა მინიმუმ რვა კომპონენტს თითო ხაზზე, ხოლო გაყოფილი ხედები მინიმუმ 13-ს. ეს იყო საბაზისო რაოდენობები, და დამატებითი UI მდგომარეობები, როგორიცაა კომენტარები, hover და focus, კიდევ უფრო მეტ კომპონენტს ამატებდა.

v1 არქიტექტურა ასევე განიცდიდა React-ის მოვლენის დამმუშავებლების გამრავლებას. მიუხედავად იმისა, რომ მცირე მასშტაბით უვნებელი ჩანდა, ერთ დიფის ხაზს შეეძლო 20 ან მეტი მოვლენის დამმუშავებლის გადატანა. როდესაც ეს ათასობით ხაზზე მრავლდებოდა დიდ პულ რექუესთში, ეს სწრაფად გროვდებოდა, რაც იწვევდა ზედმეტ ხარჯებს და JavaScript-ის ჰიპის გაზრდილ გამოყენებას. ამ სირთულემ გავლენა მოახდინა არა მხოლოდ მუშაობაზე, არამედ განვითარებასა და შენარჩუნებაზეც უფრო რთული გახადა. თავდაპირველი დიზაინი, რომელიც ეფექტური იყო შემოსაზღვრული მონაცემებისთვის, მნიშვნელოვნად იბრძოდა GitHub-ის მრავალფეროვანი პულ რექუესთის ზომების შეუზღუდავი ბუნების წინაშე.

მოკლედ, ყოველი v1 დიფის ხაზისთვის სისტემას ჰქონდა:

  • მინიმუმ 10-15 DOM ხის ელემენტი
  • მინიმუმ 8-13 React კომპონენტი
  • მინიმუმ 20 React მოვლენის დამმუშავებელი
  • მრავალი მცირე, ხელახლა გამოყენებადი React კომპონენტი

ეს არქიტექტურა პირდაპირ უკავშირებდა დიდი პულ რექუესთის ზომებს INP-ის შენელებას და JavaScript-ის ჰიპის გაზრდილ გამოყენებას, რაც ფუნდამენტურ გადაფასებასა და გადაკეთებას მოითხოვდა.

რენდერინგის რევოლუცია: V2 ოპტიმიზაციების გავლენა

v2-ზე გადასვლამ მნიშვნელოვანი არქიტექტურული გადაკეთება აღნიშნა, რომელიც ფოკუსირებული იყო დეტალურ, ეფექტურ ცვლილებებზე. გუნდმა მიიღო ფილოსოფია, რომ „არც ერთი ცვლილება არ არის ზედმეტად მცირე, როდესაც საქმე მუშაობას ეხება, განსაკუთრებით მასშტაბისას.“ ნათელი მაგალითი იყო არასაჭირო <code> ტეგების მოცილება ხაზის ნომრების უჯრებიდან. მიუხედავად იმისა, რომ თითო დიფის ხაზზე ორი DOM კვანძის ამოღება უმნიშვნელო ჩანდეს, 10,000 ხაზზე, ეს მყისიერად 20,000-ით ნაკლებ კვანძს ნიშნავდა DOM-ში, რაც აჩვენებს, თუ როგორ მოაქვს მიზანმიმართულ, ინკრემენტულ ოპტიმიზაციებს მნიშვნელოვანი გაუმჯობესება.

ქვემოთ მოცემული ვიზუალური შედარება ხაზს უსვამს შემცირებულ სირთულეს v1-დან v2-მდე კომპონენტის დონეზე:

V1 Diff Components and HTML. We had 8 react components for a single diff line. V2 Diff Components and HTML. We had 3 react components for a single diff line.

გამარტივებული კომპონენტური არქიტექტურა

v2-ში ძირითადი ინოვაცია კომპონენტების ხის გამარტივება იყო. გუნდმა რვა React კომპონენტიდან თითო დიფის ხაზზე ორამდე შეამცირა. ეს მიღწეული იქნა ღრმად ჩადგმული კომპონენტების ხეების აღმოფხვრით და გაყოფილი და გაერთიანებული დიფის ხაზებისთვის სპეციალური კომპონენტების შექმნით. მიუხედავად იმისა, რომ ამან გარკვეული კოდის დუბლირება შემოიტანა, მან მკვეთრად გაამარტივა მონაცემთა წვდომა და შეამცირა საერთო სირთულე. მოვლენის დამუშავებაც ცენტრალიზებული იყო, ახლა მას ერთი ზედა დონის დამმუშავებელი მართავს data-attribute მნიშვნელობების გამოყენებით, რითაც ჩაანაცვლა v1-ის მრავალი ინდივიდუალური მოვლენის დამმუშავებელი. ამ მიდგომამ მკვეთრად გაამარტივა როგორც კოდი, ასევე მუშაობა.

ინტელექტუალური მდგომარეობის მართვა და O(1) მონაცემთა წვდომა

შესაძლოა, ყველაზე ეფექტური ცვლილება იყო აპლიკაციის კომპლექსური მდგომარეობის, როგორიცაა კომენტარები და კონტექსტური მენიუები, პირობითად რენდერირებულ შვილ კომპონენტებში გადატანა. ისეთ გარემოში, როგორიცაა GitHub, სადაც პულ რექუესთები ათასობით ხაზს შეიძლება აღემატებოდეს, არაეფექტურია, რომ ყოველმა ხაზმა ატაროს რთული კომენტარების მდგომარეობა, როდესაც მხოლოდ მცირე ნაწილს ექნება კომენტარები. ამ მდგომარეობის ჩადგმულ კომპონენტებში გადატანით, დიფის ხაზის კომპონენტის ძირითადი პასუხისმგებლობა მხოლოდ კოდის რენდერინგი გახდა, რაც შეესაბამებოდა ერთპასუხისმგებლობის პრინციპს.

გარდა ამისა, v2-მ მოაგვარა O(n) ძიებების და ზედმეტი useEffect ჰუკების პრობლემა, რაც v1-ს აწუხებდა. გუნდმა მიიღო ორნაწილიანი სტრატეგია: useEffect გამოყენების მკაცრად შეზღუდვა დიფის ფაილების ზედა დონეზე და linting წესების დაწესება, რათა თავიდან აეცილებინათ მათი ხელახლა შემოღება ხაზის შეფუთვის კომპონენტებში. ამან უზრუნველყო ზუსტი მემოიზაცია და პროგნოზირებადი ქცევა. პარალელურად, გლობალური და დიფის მდგომარეობის მანქანები გადაკეთდა O(1) მუდმივი დროის ძიებების გამოსაყენებლად JavaScript Map ობიექტების გამოყენებით. ამან საშუალება მისცა სწრაფი, თანმიმდევრული სელექტორების გამოყენებას საერთო ოპერაციებისთვის, როგორიცაა ხაზის შერჩევა და კომენტარების მართვა, რითაც მნიშვნელოვნად გაუმჯობესდა კოდის ხარისხი, მუშაობა და შემცირდა სირთულე გაბრტყელებული, რუკირებული მონაცემთა სტრუქტურების შენარჩუნებით. დეველოპერის სამუშაო პროცესების ოპტიმიზაციისა და ძირეული არქიტექტურისადმი ეს ზედმიწევნითი მიდგომა უზრუნველყოფს მტკიცე, მასშტაბირებად სისტემას.

გაზომვადი გავლენა: V2-მა მოიტანა რაოდენობრივი მოგება

v2-ში განხორციელებულმა ზედმიწევნითმა არქიტექტურულმა და კოდის დონის ოპტიმიზაციებმა გამოიწვია ღრმა, რაოდენობრივი გაუმჯობესებები ძირითადი მუშაობის მეტრიკებში. ახალი სისტემა მუშაობს მნიშვნელოვნად სწრაფად, JavaScript-ის ჰიპის გამოყენებისა და INP ქულების მასიური შემცირებით. ქვემოთ მოცემული ცხრილი აჩვენებს დრამატულ გაუმჯობესებებს, რომლებიც დაფიქსირდა წარმომადგენლობით პულ რექუესთზე 10,000 ხაზის ცვლილებით გაყოფილ დიფის პარამეტრებში:

მეტრიკაv1v2გაუმჯობესება
JavaScript-ის ჰიპი1GB+250MB75%
DOM კვანძები400,000+80,00080%
INP p951000ms+100ms90%

ეს მაჩვენებლები ხაზს უსვამს GitHub-ის მრავალმხრივი სტრატეგიის წარმატებას. JavaScript-ის ჰიპის ზომის 75%-იანი შემცირება და DOM კვანძების 80%-იანი შემცირება არა მხოლოდ უფრო მსუბუქ ბრაუზერის კვალს ნიშნავს, არამედ პირდაპირ უწყობს ხელს უფრო სტაბილურ და პასუხისმგებლიან ინტერფეისს. ყველაზე გასაოცარი გაუმჯობესება, INP p95-ის (ურთიერთქმედების შეყოვნების 95-ე პროცენტული) 90%-იანი შემცირება, ნიშნავს, რომ მომხმარებლის ურთიერთქმედებების 95% ახლა სრულდება სულ რაღაც 100 მილიწამში, რითაც თითქმის აღმოფხვრილია შეყვანის შეყოვნება, რომელიც აწუხებდა დიდ პულ რექუესთებს v1-ში. ეს მნიშვნელოვნად აუმჯობესებს მომხმარებლის გამოცდილებას, რაც დიდ კოდის მიმოხილვებს ისეთივე გლუვსა და პასუხისმგებლიანს ხდის, როგორც მცირეებს.

GitHub-ის უწყვეტი გაუმჯობესების ვალდებულება, რასაც მოწმობს დიფის ხაზის ოპტიმიზაციის ეს სიღრმისეული ანალიზი, არის მათი ერთგულების დასტური მსოფლიო დონის დეველოპერის პლატფორმის უზრუნველსაყოფად. მუშაობის ბოთლნეკების მკაცრი ანალიზით და მიზანმიმართული არქიტექტურული გადაწყვეტილებების განხორციელებით, მათ არა მხოლოდ მოაგვარეს კრიტიკული მასშტაბირების პრობლემები, არამედ დააწესეს ახალი სტანდარტი პასუხისმგებლობისთვის მათ ძირითად პროდუქტში. ეს ფოკუსირება მუშაობაზე უზრუნველყოფს, რომ ინჟინრებს შეუძლიათ ეფექტურად ჩაერთონ გადამწყვეტ ამოცანებში, როგორიცაა კოდის მიმოხილვები, რაც საბოლოოდ იწვევს კოდის უფრო მაღალ ხარისხსა და უსაფრთხოებას და უფრო პროდუქტიულ განვითარების გარემოს.

ხშირად დასმული კითხვები

What is the 'Files changed' tab in GitHub pull requests and why was its performance critical?
The 'Files changed' tab is a core component of GitHub's pull request workflow, allowing engineers to review code modifications. Its performance is critical because it's where developers spend significant time, and slowdowns, especially with large pull requests, can severely impede productivity and user experience. GitHub prioritized its optimization to ensure responsiveness across all scales of code changes, from minor fixes to extensive refactorings, which can involve millions of lines across thousands of files. Maintaining a smooth and efficient review process is paramount for collaborative development.
What were the primary performance challenges GitHub faced with large pull requests in the v1 architecture?
In its initial React-based architecture (v1), GitHub encountered significant performance degradation when handling large pull requests. Key issues included the JavaScript heap exceeding 1 GB, DOM node counts soaring past 400,000, and page interactions becoming extremely sluggish or even unusable. The Interaction to Next Paint (INP) metric, which measures responsiveness, showed unacceptably high values. These problems stemmed from an inefficient rendering strategy where each diff line was resource-intensive, with too many DOM elements, React components, and event handlers, particularly in cases involving thousands of lines of code.
How did GitHub approach solving the complex performance issues, moving beyond a 'silver bullet' solution?
Recognizing that no single solution would address the diverse range of pull request sizes and complexities, GitHub adopted a multi-faceted strategic approach. They focused on three core themes: targeted optimizations for diff-line components to keep medium and large reviews fast, graceful degradation with virtualization to maintain usability for the largest pull requests by limiting rendered content, and investing in foundational components and rendering improvements to yield compounding benefits across all pull request sizes. This comprehensive strategy allowed them to tailor solutions to specific problem areas.
What were the key limitations of the 'v1' diff rendering architecture that made it unsustainable for scale?
The v1 architecture, while initially sensible for smaller diffs, proved unsustainable for large-scale pull requests. Each diff line was costly, requiring 10-15 DOM elements, 8-13 React components, and over 20 event handlers. This was compounded by deep component nesting, excessive `useEffect` hooks, and O(n) data lookups, leading to unnecessary re-renders and increased complexity. The abstraction layers, meant to share code, inadvertently added overhead by carrying logic for both split and unified views, even when only one was active. This design led to a significant increase in JavaScript heap, DOM count, and poor INP scores for larger diffs.
What specific architectural changes were implemented in 'v2' to drastically improve diff line performance?
The v2 architecture introduced several critical changes. It streamlined the component tree, reducing React components per diff line from eight to two by creating dedicated components for split and unified views, even with some code duplication. Event handling was centralized to a single top-level handler using `data-attribute` values, replacing numerous individual handlers. Complex app state, such as commenting features, was moved into conditionally rendered child components, ensuring that diff lines primarily focused on rendering code. Furthermore, v2 restricted `useEffect` hooks to top-level diff files and adopted O(1) constant-time data access using `JavaScript Map` for efficient state lookups, significantly reducing re-renders and improving data management.
How did the GitHub engineering team achieve quantifiable improvements in JavaScript heap, DOM nodes, and INP metrics with v2?
The cumulative effect of v2's architectural changes led to substantial quantifiable improvements. For a pull request with 10,000 line changes, the JavaScript heap size was reduced from 1GB+ to 250MB, a 75% improvement. DOM nodes decreased from 400,000+ to 80,000, an 80% reduction. The Interaction to Next Paint (INP) p95 (95th percentile) saw an astounding 90% improvement, dropping from 1000ms+ to just 100ms. These results were achieved through meticulous optimization, including removing extraneous DOM elements, simplifying the React component structure, centralizing event handling, and optimizing state management and data access patterns, leading to a much faster and more responsive user experience.
What is Interaction to Next Paint (INP) and why is its improvement significant for GitHub's user experience?
Interaction to Next Paint (INP) is a crucial web performance metric that assesses a page's responsiveness by measuring the latency of all interactions made by a user with the page. It records the time from when a user interacts (e.g., click, tap, keypress) until the next frame is painted to the screen, reflecting the visual feedback of that interaction. For GitHub, a high INP meant users experienced noticeable input lag, making the platform feel slow and unresponsive. By reducing INP p95 from over 1000ms to 100ms in v2, GitHub significantly enhanced the perceived speed and fluidity of the 'Files changed' tab, ensuring a smoother and more satisfying developer experience, especially during code review.

იყავით ინფორმირებული

მიიღეთ უახლესი AI სიახლეები ელფოსტაზე.

გაზიარება