Tối ưu hóa hiệu suất hiển thị dòng diff trên GitHub
GitHub đã thực hiện một cuộc tái cấu trúc toàn diện nhằm cải thiện hiệu suất của tab "Files changed", đặc biệt đối với các Pull Request (PR) quy mô lớn. Bằng cách đơn giản hóa cây thành phần React, áp dụng kỹ thuật ảo hóa và tối ưu hóa quản lý trạng thái, GitHub đã giảm đáng kể tiêu tốn bộ nhớ và cải thiện đáng kể chỉ số phản hồi của giao diện.

Tối ưu hóa hiệu suất hiển thị dòng diff trên GitHub
Pull Request (PR) được coi là trái tim của GitHub, nơi mà hầu hết thời gian làm việc của các kỹ sư đều được dành cho đây. Tuy nhiên, với quy mô khổng lồ của GitHub—nơi các PR có thể là những sửa lỗi một dòng hoặc các thay đổi kéo dài hàng nghìn tệp tin và hàng triệu dòng code—trải nghiệm duyệt PR phải luôn đảm bảo tốc độ và độ phản hồi nhanh chóng.
GitHub gần đây đã phát hành trải nghiệm mới dựa trên React cho tab "Files changed" (đã trở thành trải nghiệm mặc định). Một trong những mục tiêu chính là đảm bảo hiệu suất tốt hơn cho mọi người dùng, đặc biệt là khi xử lý các PR lớn. Điều này đòi hỏi việc đầu tư vào các vấn đề kỹ thuật khó như tối ưu hóa rendering, độ trễ tương tác và tiêu thụ bộ nhớ.
Thách thức về hiệu suất
Trước khi tối ưu hóa, trải nghiệm của hầu hết người dùng vẫn nhanh chóng. Tuy nhiên, khi xem các PR lớn, hiệu suất sẽ giảm rõ rệt. Trong các trường hợp cực đoan, heap JavaScript có thể vượt quá 1 GB, số lượng nút DOM vượt qua 400.000 và các tương tác trên trang trở nên chậm chạp甚至 không thể sử dụng được. Các chỉ số Interaction to Next Paint (INP)—một chỉ số quan trọng để xác định độ phản hồi—đang ở mức cao hơn mức chấp nhận được, khiến người dùng cảm nhận rõ rệt độ trễ khi nhập liệu.
Mô tả tổng quan về cấu trúc diff trên GitHub
Chiến lược: Sự đơn giản hóa từ v1 đến v2
Khi bắt đầu điều tra, rõ ràng là không có một giải pháp "vàng" duy nhất. Các kỹ thuật bảo toàn mọi tính năng có thể vẫn gặp giới hạn ở mức cực đoan. Vì vậy, chúng tôi đã phát triển một tập hợp các chiến lược, mỗi chiến lược được thiết kế để giải quyết một khía cạnh cụ thể về kích thước và độ phức tạp của PR.
Những chiến lược này tập trung vào các chủ đề sau:
- Tối ưu hóa tập trung vào các thành phần dòng diff.
- Sự suy giảm mượt mà thông qua kỹ thuật ảo hóa (virtualization).
- Đầu tư vào các thành phần nền tảng và cải thiện rendering.
Tối ưu hóa các thành phần diff
Với mục tiêu cải thiện hiệu suất, đội ngũ của chúng tôi có 3 mục tiêu chính:
- Giảm bộ nhớ và kích thước heap JavaScript.
- Giảm số lượng nút DOM.
- Giảm INP trung bình và cải thiện đáng kể các đo lường p95 và p99.
Để đạt được điều này, chúng tôi tập trung vào sự đơn giản hóa: ít state hơn, ít phần tử hơn, ít JavaScript và ít React components hơn.
So sánh giữa phiên bản v1 và v2 của giao diện diff
Trong phiên bản v1, mỗi dòng diff đều tốn kém để render. Trong chế độ unified view, một dòng đòi hỏi khoảng 10 phần tử DOM; trong chế độ split view, gần 15. Điều này chưa kể đến syntax highlighting, vốn thêm nhiều thẻ span và làm tăng thêm số lượng DOM.
Chúng tôi đã chuyển từ cấu trúc có nhiều thành phần React lồng nhau sang các thành phần riêng biệt cho từng chế độ. Điều này dẫn đến một số sự trùng lặp mã, nhưng kết quả là mã đơn giản và nhanh hơn. Quan trọng nhất, việc xử lý sự kiện (event handling) giờ được quản lý bởi một trình xử lý cấp cao duy nhất sử dụng các giá trị data-attribute. Ví dụ, khi bạn nhấp và kéo để chọn nhiều dòng, trình xử lý kiểm tra thuộc tính dữ liệu của từng sự kiện để xác định các dòng cần highlight, thay vì mỗi dòng có một hàm mouse enter riêng.
Quản lý trạng thái và truy cập dữ liệu O(1)
Thay vì để mỗi dòng diff mang theo state phức tạp cho các bình luận—một thực tế không khả thi khi các PR lên tới hàng nghìn dòng—chúng tôi đã di chuyển state vào các thành phần con tương ứng. Điều này đảm bảo rằng trách nhiệm chính của thành phần dòng diff là chỉ render code.
Chúng tôi cũng đã thay đổi cách truy cập dữ liệu từ O(n) (tìm kiếm tuyến tính) sang O(1) (truy cập thời gian hằng số) bằng cách sử dụng JavaScript Map. Việc này giúp xây dựng các bộ chọn nhanh và nhất quán cho các hoạt động phổ biến như chọn dòng và quản lý bình luận.
Kỹ thuật ảo hóa cho các PR lớn nhất
Khi làm việc với các PR cực lớn (p95+, có hơn 10.000 dòng diff), các mẹo hiệu suất thông thường không đủ. Đó là lúc kỹ thuật ảo hóa (virtualization) bước vào.
Kỹ thuật này đảm bảo rằng chỉ có phần visible (phần hiển thị trên màn hình) của danh sách lớn nằm trong DOM tại bất kỳ thời điểm nào. Thay vì tải tất cả (gây tốn bộ nhớ và làm chậm), nó hiển thị động những gì bạn thấy và thay thế các phần tử khi cuộn. Việc tích hợp TanStack Virtual vào giao diện diff đã mang lại tác động lớn: giảm 10 lần tiêu thụ bộ nhớ JavaScript và nút DOM cho các PR p95+, và giảm INP từ 275-700+ ms xuống còn 40-80 ms.
Chi tiết cấu trúc thành phần sau khi tối ưu hóa
Các cải tiến bổ sung
Để đẩy hiệu suất lên cao hơn nữa, chúng tôi đã tối ưu hóa nhiều khu vực trên toàn bộ hệ sinh thái:
- Front-end: Cắt giảm các re-render không cần thiết của React và tinh chỉnh quản lý trạng thái. Thay đổi CSS selectors nặng nề và sử dụng GPU transforms cho các thao tác kéo và thay đổi kích thước.
- Monitoring: Theo dõi hiệu suất ở mức tương tác (INP), phân đoạn theo kích thước diff và gán thẻ bộ nhớ trên Datadog.
- Server-side: Tối ưu hóa rendering để chỉ hydrate các dòng diff visible, giúp giảm thời gian tương tác và kiểm soát bộ nhớ.
- Loading: Sử dụng progressive diff loading và smart background fetches để người dùng có thể xem và tương tác với nội dung sớm hơn.
Kết quả
Những cải tiến tập trung này đã làm cho giao diện cảm thấy nhẹ nhàng hơn, nhanh hơn và sẵn sàng cho mọi nhu cầu của người dùng. Các chỉ số đo lường cho thấy hiệu quả rõ rệt:
- Giảm 27% tổng dòng code.
- Giảm 47% số lượng loại thành phần unique.
- Giảm 74% tổng số thành phần được render.
- Giảm 50% tiêu thụ bộ nhớ.
- Giảm 78% thời gian INP cho các PR lớn.
So sánh hiệu suất trước và sau tối ưu hóa
Những con số này chứng minh rằng việc tái cấu trúc mục tiêu, ngay cả trong một mã nguồn lớn và đã trưởng thành, có thể mang lại lợi ích đáng kể cho tất cả người dùng. Đôi khi, tập trung vào các cải tiến nhỏ và đơn giản có thể mang lại tác động lớn nhất.



