Bài đăng này là của cộng tác viên Chromium Ahmed Elwasefi, chia sẻ về cách anh trở thành cộng tác viên thông qua chương trình Google Summer of Code và các vấn đề về hiệu suất hỗ trợ tiếp cận mà anh đã xác định và khắc phục.
Khi sắp kết thúc năm cuối cùng của chương trình kỹ thuật máy tính tại Đại học Đức ở Cairo, tôi quyết định tìm hiểu các cơ hội đóng góp cho nguồn mở. Tôi bắt đầu khám phá danh sách các vấn đề phù hợp với người mới bắt đầu của Chromium và nhận thấy mình đặc biệt bị thu hút bởi tính năng hỗ trợ tiếp cận. Trong quá trình tìm kiếm người hướng dẫn, tôi đã gặp Aaron Leventhal. Chuyên môn và sự sẵn sàng giúp đỡ của anh đã truyền cảm hứng để tôi hợp tác với anh trong một dự án. Hoạt động cộng tác này đã trở thành trải nghiệm Google Summer of Code của tôi, nơi tôi được chấp nhận làm việc với Nhóm hỗ trợ tiếp cận của Chromium.
Sau khi hoàn thành thành công chương trình Google Summer of Code, tôi tiếp tục giải quyết một vấn đề cuộn chưa được giải quyết, do mong muốn cải thiện hiệu suất. Nhờ hai khoản tài trợ từ chương trình OpenCollective của Google, tôi có thể tiếp tục làm việc trên dự án này, đồng thời đảm nhận thêm các nhiệm vụ tập trung vào việc đơn giản hoá mã để đạt được hiệu suất tốt hơn.
Bài đăng trên blog này chia sẻ hành trình của tôi với Chromium trong hơn một năm rưỡi qua, chi tiết về những điểm cải tiến kỹ thuật mà chúng tôi đã thực hiện, đặc biệt là trong lĩnh vực hiệu suất.
Mức tác động của mã hỗ trợ tiếp cận đến hiệu suất trong Chrome
Mã hỗ trợ tiếp cận của Chrome giúp các công nghệ hỗ trợ như trình đọc màn hình truy cập vào web. Tuy nhiên, khi bật, tính năng này có thể ảnh hưởng đến thời gian tải, hiệu suất và thời lượng pin. Do đó, nếu không cần thiết, mã này sẽ vẫn không hoạt động để tránh làm chậm hiệu suất. Khoảng 5-10% người dùng bật mã hỗ trợ tiếp cận, thường là do các công cụ như trình quản lý mật khẩu và phần mềm diệt vi-rút sử dụng API hỗ trợ tiếp cận nền tảng. Các công cụ này dựa vào các API này để tương tác và sửa đổi nội dung trang, chẳng hạn như xác định vị trí trường mật khẩu cho trình quản lý mật khẩu và trình điền biểu mẫu.
Chúng tôi chưa biết tổng mức giảm trong các chỉ số cốt lõi, nhưng một thử nghiệm gần đây có tên là Tự động tắt tính năng hỗ trợ tiếp cận (tắt tính năng hỗ trợ tiếp cận khi không sử dụng) cho thấy mức giảm khá cao. Vấn đề này xảy ra do lượng lớn hoạt động tính toán và giao tiếp trong hai khu vực chính của cơ sở mã hỗ trợ tiếp cận của Chrome: trình kết xuất và trình duyệt. Trình kết xuất thu thập thông tin về nội dung web và các thay đổi đối với nội dung, tính toán các thuộc tính hỗ trợ tiếp cận cho một cây nút. Sau đó, mọi nút bị bẩn sẽ được chuyển đổi tuần tự và gửi qua một ống đến luồng giao diện người dùng chính của quy trình duyệt web. Luồng này nhận và chuyển đổi tuần tự thông tin này thành cây nút giống hệt nhau, sau đó chuyển đổi thành một dạng phù hợp cho các công nghệ hỗ trợ của bên thứ ba, chẳng hạn như trình đọc màn hình.
Cải tiến khả năng hỗ trợ tiếp cận của Chromium
Các dự án sau đây đã được hoàn thành trong chương trình Hackathon mùa hè và sau đó được tài trợ bởi chương trình Google OpenCollective.
Cải thiện bộ nhớ đệm
Trong Chrome, có một cấu trúc dữ liệu đặc biệt được gọi là cây hỗ trợ tiếp cận phản ánh cây DOM. Công cụ này dùng để giúp các công nghệ hỗ trợ truy cập vào nội dung trên web. Đôi khi, khi một thiết bị cần thông tin từ cây này, thiết bị đó có thể chưa sẵn sàng, vì vậy, trình duyệt phải lên lịch các yêu cầu đó cho sau.
Trước đây, việc lên lịch này được xử lý bằng một phương thức có tên là closures (phương thức đóng), trong đó có việc đặt lệnh gọi lại vào hàng đợi. Phương pháp này đã làm tăng thêm công việc do cách xử lý các hàm đóng.
Để cải thiện vấn đề này, chúng tôi đã chuyển sang một hệ thống sử dụng enum. Mỗi tác vụ được chỉ định một giá trị enum cụ thể và sau khi cây hỗ trợ tiếp cận sẵn sàng, phương thức chính xác cho tác vụ đó sẽ được gọi. Thay đổi này giúp mã dễ hiểu hơn và cải thiện hiệu suất hơn 20%.
Tìm và khắc phục các vấn đề về hiệu suất cuộn
Tiếp theo, tôi đã tìm hiểu cách cải thiện hiệu suất khi tắt tính năng chuyển đổi tuần tự hộp giới hạn. Hộp giới hạn là vị trí và kích thước của các phần tử trên trang web, bao gồm các thông tin chi tiết như chiều rộng, chiều cao và vị trí tương ứng với phần tử mẹ.
Để kiểm thử điều này, chúng tôi tạm thời xoá mã xử lý hộp giới hạn và chạy kiểm thử hiệu suất để xem tác động. Một thử nghiệm, focus-links.html cho thấy mức cải thiện rất lớn khoảng 1618%. Phát hiện này đã trở thành nền tảng cho các nghiên cứu tiếp theo.
Điều tra kiểm thử chậm
Tôi bắt đầu điều tra lý do khiến kiểm thử cụ thể đó bị chậm với các hộp giới hạn. Tất cả những gì kiểm thử đã làm là đặt tiêu điểm vào một số đường liên kết theo thứ tự. Do đó, vấn đề chính phải là việc tập trung vào các phần tử hoặc thao tác cuộn xảy ra với thao tác lấy tiêu điểm. Để kiểm thử điều này, tôi đã thêm {preventScroll: true}
vào lệnh gọi focus()
trong kiểm thử hiệu suất, dừng thao tác cuộn.
Khi tính năng cuộn bị tắt, thời gian kiểm thử giảm xuống còn 1,2 mili giây khi tính toán hộp giới hạn đang hoạt động. Điều này cho thấy rằng việc cuộn là vấn đề thực sự.
Tôi đã tạo một kiểm thử mới có tên là scroll-in-page.html để mô phỏng kiểm thử focus-links, nhưng thay vì sử dụng tiêu điểm, kiểm thử này sẽ cuộn qua các phần tử bằng scrollIntoView()
. Tôi đã thử nghiệm cả tính năng cuộn mượt mà và tức thì, có và không có tính năng tính toán hộp giới hạn.
Kết quả cho thấy rằng với tính năng cuộn tức thì và hộp giới hạn, quá trình này mất khoảng 66 mili giây. Tính năng cuộn mượt mà thậm chí còn chậm hơn ở mức khoảng 124 mili giây. Khi chúng ta tắt hộp giới hạn, quá trình này không mất chút thời gian nào vì không có sự kiện nào được kích hoạt.
Chúng tôi biết về trường hợp này, nhưng tại sao điều này lại xảy ra?
Giờ đây, chúng ta đã biết rằng thao tác cuộn là nguồn gốc của nhiều sự cố chậm trong quá trình chuyển đổi tuần tự chức năng hỗ trợ tiếp cận, nhưng chúng ta vẫn phải tìm hiểu lý do. Để phân tích vấn đề này, hai công cụ có tên là perf và pprof đã được dùng để phân tích công việc đã thực hiện trong quy trình trình duyệt. Các công cụ này thường được dùng trong C++ để lập hồ sơ. Các biểu đồ sau đây cho thấy một đoạn mã của phần thú vị.
Sau khi điều tra, chúng tôi nhận thấy vấn đề không phải là mã giải mã tuần tự mà là tần suất gọi mã đó. Để hiểu điều này, chúng ta cần xem cách hoạt động của các bản cập nhật hỗ trợ tiếp cận trong Chromium. Thông tin cập nhật không được gửi riêng lẻ; thay vào đó, có một vị trí trung tâm có tên là AXObjectCache
lưu trữ tất cả các thuộc tính. Khi một nút thay đổi, nhiều phương thức sẽ thông báo cho bộ nhớ đệm để đánh dấu nút đó là bẩn để chuyển đổi tuần tự sau này. Sau đó, tất cả thuộc tính của ghi chú đã thay đổi, bao gồm cả các thuộc tính không thay đổi, sẽ được chuyển đổi tuần tự và gửi đến trình duyệt. Mặc dù thiết kế này đơn giản hoá mã và giảm độ phức tạp bằng cách có một đường dẫn cập nhật duy nhất, nhưng thiết kế này sẽ bị chậm khi có các sự kiện "đánh dấu là không sạch" nhanh chóng, chẳng hạn như các sự kiện từ thao tác cuộn. Điều duy nhất thay đổi là các giá trị scrollX
và scrollY
; tuy nhiên, chúng ta luôn chuyển đổi tuần tự các thuộc tính còn lại với các giá trị này. Tốc độ cập nhật ở đây đạt hơn 20 lần/giây!
Quá trình chuyển đổi tuần tự hộp giới hạn giải quyết vấn đề này bằng cách sử dụng đường dẫn chuyển đổi tuần tự nhanh hơn chỉ gửi thông tin chi tiết về hộp giới hạn, cho phép cập nhật nhanh mà không ảnh hưởng đến các thuộc tính khác. Phương thức này xử lý hiệu quả các thay đổi về hộp giới hạn.
Sửa lỗi cuộn
Giải pháp rất rõ ràng: bao gồm các độ dời cuộn hiện tại bằng cách chuyển đổi tuần tự hộp giới hạn. Điều này đảm bảo rằng các bản cập nhật cuộn được xử lý thông qua đường dẫn nhanh, nâng cao hiệu suất mà không bị chậm trễ không cần thiết. Bằng cách đóng gói độ dời cuộn bằng dữ liệu hộp giới hạn, chúng tôi tối ưu hoá quy trình để cập nhật mượt mà và hiệu quả hơn, tạo ra trải nghiệm ít giật hơn cho người dùng đã bật tính năng hỗ trợ tiếp cận. Mức độ cải thiện sau khi triển khai bản sửa lỗi là lên đến 825% trong các thử nghiệm cuộn.
Đơn giản hoá mã
Trong khoảng thời gian này, tôi tập trung vào chất lượng mã trong một dự án có tên là Onion Soup. Dự án này giúp đơn giản hoá mã bằng cách giảm hoặc xoá mã không cần thiết trên các lớp.
Dự án đầu tiên nhằm đơn giản hoá cách dữ liệu hỗ trợ tiếp cận được chuyển đổi tuần tự từ trình kết xuất sang trình duyệt. Trước đây, dữ liệu phải đi qua một lớp bổ sung trước khi đến đích, điều này làm tăng thêm độ phức tạp không cần thiết. Chúng tôi đã đơn giản hoá quy trình này bằng cách cho phép gửi dữ liệu trực tiếp, loại bỏ bên trung gian.
Ngoài ra, chúng tôi đã xác định và xoá một số sự kiện đã lỗi thời đang gây ra công việc không cần thiết trong hệ thống, chẳng hạn như sự kiện được kích hoạt khi bố cục hoàn tất. Chúng tôi đã thay thế các phương thức này bằng một giải pháp hiệu quả hơn.
Ngoài ra, chúng tôi cũng đã thực hiện một số điểm cải tiến nhỏ khác. Rất tiếc, chúng tôi không ghi lại được mức độ cải thiện về hiệu suất cho những thay đổi này, nhưng chúng tôi rất tự hào được chia sẻ rằng mã này rõ ràng và tự ghi lại nhiều hơn trước. Điều này giúp ích rất nhiều trong việc tạo điều kiện cho việc cải thiện hiệu suất trong tương lai. Bạn có thể kiểm tra các thay đổi thực tế trong hồ sơ gerrit của tôi.
Kết luận
Việc làm việc với Nhóm hỗ trợ tiếp cận của Chromium là một hành trình đầy ý nghĩa. Thông qua việc giải quyết nhiều thách thức, từ việc tối ưu hoá hiệu suất cuộn đến việc đơn giản hoá cơ sở mã, tôi đã hiểu rõ hơn về quá trình phát triển trong một dự án quy mô lớn như vậy, cũng như tìm hiểu các công cụ quan trọng để lập hồ sơ. Ngoài ra, tôi đã tìm hiểu tầm quan trọng của khả năng hỗ trợ tiếp cận trong việc tạo ra một trang web dành cho tất cả mọi người. Những điểm cải tiến mà chúng tôi đã thực hiện không chỉ nâng cao trải nghiệm người dùng đối với những người dựa vào công nghệ hỗ trợ, mà còn góp phần nâng cao hiệu suất và hiệu quả tổng thể của trình duyệt.
Kết quả về hiệu suất rất ấn tượng. Ví dụ: việc chuyển sang sử dụng enum để lên lịch tác vụ đã cải thiện hiệu suất thêm hơn 20%. Ngoài ra, bản sửa lỗi cuộn của chúng tôi đã giúp giảm 825% số lần kiểm thử cuộn. Các thay đổi đơn giản hoá mã không chỉ giúp mã rõ ràng và dễ bảo trì hơn mà còn mở đường cho các điểm cải tiến trong tương lai.
Tôi muốn bày tỏ lòng biết ơn đến Stefan Zager, Chris Harrelson và Mason Freed vì đã hỗ trợ và hướng dẫn tôi trong suốt cả năm, đặc biệt là Aaron Leventhal, người đã giúp tôi có được cơ hội này. Tôi cũng muốn cảm ơn Tab Atkins-Bittner và nhóm GSoC đã hỗ trợ tôi.
Đối với những người muốn đóng góp cho một dự án có ý nghĩa và phát triển kỹ năng của mình, bạn nên tham gia Chromium. Đây là một cách tuyệt vời để học hỏi và các chương trình như Google Summer of Code là một điểm xuất phát tuyệt vời cho hành trình của bạn.