Tin tức về sản phẩm

Biên dịch nhanh hơn 18%, không ảnh hưởng đến chất lượng

Đọc trong 8 phút

Nhóm Android Runtime (ART) đã giảm 18% thời gian biên dịch mà không ảnh hưởng đến mã đã biên dịch hoặc bất kỳ sự hồi quy bộ nhớ cao điểm nào. Điểm cải tiến này là một phần trong sáng kiến năm 2025 của chúng tôi nhằm cải thiện thời gian biên dịch mà không làm giảm mức sử dụng bộ nhớ hoặc chất lượng của mã đã biên dịch.

Việc tối ưu hoá tốc độ thời gian biên dịch là yếu tố then chốt đối với ART. Ví dụ: khi biên dịch tức thì (JIT), việc này sẽ ảnh hưởng trực tiếp đến hiệu quả của các ứng dụng và hiệu suất tổng thể của thiết bị. Quá trình biên dịch nhanh hơn sẽ giảm thời gian trước khi các hoạt động tối ưu hoá bắt đầu, mang lại trải nghiệm người dùng mượt mà và phản hồi nhanh hơn. Hơn nữa, đối với cả JIT và biên dịch trước thời gian chạy (AOT), việc cải thiện tốc độ thời gian biên dịch sẽ giúp giảm mức tiêu thụ tài nguyên trong quá trình biên dịch, mang lại lợi ích cho thời lượng pin và nhiệt độ thiết bị, đặc biệt là trên các thiết bị cấp thấp.

Một số điểm cải thiện về tốc độ thời gian biên dịch này đã được ra mắt trong bản phát hành Android tháng 6 năm 2025 và phần còn lại sẽ có trong bản phát hành Android vào cuối năm. Hơn nữa, tất cả người dùng Android trên phiên bản 12 trở lên đều đủ điều kiện nhận được những điểm cải tiến này thông qua các bản cập nhật chính.

Tối ưu hoá trình biên dịch tối ưu hoá

Việc tối ưu hoá trình biên dịch luôn là một trò chơi đánh đổi. Bạn không thể chỉ nhận được tốc độ miễn phí; bạn phải từ bỏ một điều gì đó. Chúng tôi đặt ra một mục tiêu rất rõ ràng và đầy thách thức cho chính mình: làm cho trình biên dịch nhanh hơn, nhưng thực hiện việc này mà không gây ra sự hồi quy bộ nhớ và quan trọng là không làm giảm chất lượng của mã mà trình biên dịch tạo ra. Nếu trình biên dịch nhanh hơn nhưng các ứng dụng chạy chậm hơn, thì chúng tôi đã thất bại.

Tài nguyên duy nhất mà chúng tôi sẵn sàng chi tiêu là thời gian phát triển của chính mình để tìm hiểu sâu, điều tra và tìm ra các giải pháp thông minh đáp ứng những tiêu chí nghiêm ngặt này. Hãy xem xét kỹ hơn cách chúng tôi làm việc để tìm ra những lĩnh vực cần cải thiện, cũng như tìm ra các giải pháp phù hợp cho nhiều vấn đề.

Tìm các hoạt động tối ưu hoá có thể thực hiện và đáng giá

Trước khi có thể bắt đầu tối ưu hoá một chỉ số, bạn phải đo lường được chỉ số đó. Nếu không, bạn sẽ không bao giờ chắc chắn liệu mình có cải thiện được chỉ số đó hay không. May mắn là tốc độ thời gian biên dịch khá nhất quán miễn là bạn thực hiện một số biện pháp phòng ngừa như sử dụng cùng một thiết bị mà bạn dùng để đo lường trước và sau khi thay đổi, đồng thời đảm bảo rằng bạn không điều tiết thiết bị. Ngoài ra, chúng tôi cũng có các phép đo mang tính xác định như số liệu thống kê về trình biên dịch giúp chúng tôi hiểu rõ những gì đang diễn ra.

 

Vì tài nguyên mà chúng tôi đang hy sinh cho những điểm cải tiến này là thời gian phát triển, nên chúng tôi muốn có thể lặp lại nhanh nhất có thể. Điều này có nghĩa là chúng tôi đã lấy một số ứng dụng đại diện (kết hợp các ứng dụng bên thứ nhất, ứng dụng bên thứ ba và chính hệ điều hành Android) để tạo mẫu giải pháp. Sau đó, chúng tôi đã xác minh rằng việc triển khai cuối cùng là đáng giá bằng cả phương pháp kiểm thử thủ công và tự động trên diện rộng.

 

Với tập hợp APK được chọn lọc kỹ càng đó, chúng tôi sẽ kích hoạt quá trình biên dịch thủ công cục bộ, lấy hồ sơ của quá trình biên dịch và sử dụng pprof để trực quan hoá thời gian chúng tôi đang dành cho việc gì.

image.png

Ví dụ về biểu đồ ngọn lửa của hồ sơ trong pprof

Công cụ pprof rất mạnh mẽ và cho phép chúng tôi phân chia, lọc và sắp xếp dữ liệu để xem, ví dụ: giai đoạn hoặc phương thức trình biên dịch nào đang chiếm phần lớn thời gian. Chúng tôi sẽ không đi sâu vào chi tiết về pprof; chỉ cần biết rằng nếu thanh lớn hơn thì có nghĩa là quá trình biên dịch mất nhiều thời gian hơn.

Một trong những chế độ xem này là chế độ xem "từ dưới lên" nơi bạn có thể thấy những phương thức nào đang chiếm phần lớn thời gian. Trong hình ảnh bên dưới, chúng ta có thể thấy một phương thức có tên là Kill, chiếm hơn 1% thời gian biên dịch. Một số phương thức hàng đầu khác cũng sẽ được thảo luận sau trong bài đăng trên blog.

image.png

Chế độ xem từ dưới lên của hồ sơ

Trong trình biên dịch tối ưu hoá của chúng tôi, có một giai đoạn gọi là Đánh số giá trị toàn cục (GVN). Bạn không cần phải lo lắng về những gì mà trình biên dịch này thực hiện nói chung, nhưng phần liên quan là phải biết rằng trình biên dịch này có một phương thức có tên là `Kill` sẽ xoá một số nút theo bộ lọc. Việc này tốn thời gian vì phải lặp lại tất cả các nút và kiểm tra từng nút một. Chúng tôi nhận thấy có một số trường hợp mà chúng tôi biết trước rằng quá trình kiểm tra sẽ là sai, bất kể các nút mà chúng tôi có đang hoạt động tại thời điểm đó. Trong những trường hợp này, chúng tôi có thể bỏ qua hoàn toàn việc lặp lại, giảm từ 1,023% xuống còn ~0,3% và cải thiện thời gian chạy của GVN thêm ~15%.

Triển khai các hoạt động tối ưu hoá đáng giá

Chúng tôi đã đề cập đến cách đo lường và cách phát hiện thời gian đang được sử dụng ở đâu, nhưng đây chỉ là bước khởi đầu. Bước tiếp theo là cách tối ưu hoá thời gian biên dịch.

Thông thường, trong trường hợp như `Kill` ở trên, chúng tôi sẽ xem xét cách lặp lại các nút và thực hiện nhanh hơn bằng cách, ví dụ: thực hiện các thao tác song song hoặc cải thiện chính thuật toán. Trên thực tế, đó là những gì chúng tôi đã thử lúc đầu và chỉ khi không tìm thấy việc gì để làm, chúng tôi mới có khoảnh khắc "Chờ một chút..." và nhận thấy rằng giải pháp là (trong một số trường hợp) không lặp lại hoàn toàn! Khi thực hiện các loại hoạt động tối ưu hoá này, bạn rất dễ bỏ qua bức tranh tổng thể.

Trong các trường hợp khác, chúng tôi đã sử dụng một số kỹ thuật khác nhau, bao gồm:

  • sử dụng phương pháp phỏng đoán để quyết định xem một hoạt động tối ưu hoá có tạo ra kết quả đáng giá hay không và do đó có thể bỏ qua
  • sử dụng các cấu trúc dữ liệu bổ sung để lưu vào bộ nhớ đệm dữ liệu đã tính toán
  • thay đổi cấu trúc dữ liệu hiện tại để tăng tốc độ
  • tính toán kết quả một cách trì hoãn để tránh các chu kỳ trong một số trường hợp
  • sử dụng mức độ trừu tượng phù hợp – các tính năng không cần thiết có thể làm chậm mã
  • tránh theo dõi con trỏ thường xuyên sử dụng thông qua nhiều lần tải

Làm cách nào để biết liệu các hoạt động tối ưu hoá có đáng theo đuổi hay không?

Đó là phần thú vị, bạn không cần. Sau khi phát hiện thấy một khu vực đang tiêu tốn nhiều thời gian biên dịch và sau khi dành thời gian phát triển để cố gắng cải thiện khu vực đó, đôi khi bạn không thể tìm thấy giải pháp. Có thể không có gì để làm, việc triển khai sẽ mất quá nhiều thời gian, việc này sẽ làm giảm đáng kể một chỉ số khác, tăng độ phức tạp của cơ sở mã, v.v. Đối với mọi hoạt động tối ưu hoá thành công mà bạn có thể thấy trong bài đăng trên blog này, hãy biết rằng có vô số hoạt động khác không thành công.

Nếu bạn đang ở trong tình huống tương tự, hãy cố gắng ước tính mức độ cải thiện chỉ số bằng cách thực hiện càng ít công việc càng tốt. Điều này có nghĩa là theo thứ tự:

  1. Ước tính bằng các chỉ số mà bạn đã thu thập hoặc chỉ là cảm tính
  2. Ước tính bằng một nguyên mẫu nhanh chóng và sơ sài
  3. Triển khai giải pháp.

Đừng quên cân nhắc việc ước tính những nhược điểm của giải pháp. Ví dụ: nếu bạn định dựa vào các cấu trúc dữ liệu bổ sung, thì bạn sẵn sàng sử dụng bao nhiêu bộ nhớ?

Tìm hiểu sâu hơn

Không cần phải nói thêm, hãy xem một số thay đổi mà chúng tôi đã triển khai.

Chúng tôi đã triển khai một thay đổi để tối ưu hoá một phương thức có tên là FindReferenceInfoOf. Phương thức này đang thực hiện tìm kiếm tuyến tính một vectơ để tìm một mục nhập. Chúng tôi đã cập nhật cấu trúc dữ liệu đó để được lập chỉ mục theo mã của lệnh, sao cho FindReferenceInfoOf sẽ là O(1) thay vì O(n). Ngoài ra, chúng tôi đã phân bổ trước vectơ để tránh thay đổi kích thước. Chúng tôi đã tăng nhẹ bộ nhớ vì phải thêm một trường bổ sung để đếm số mục nhập mà chúng tôi đã chèn vào vectơ, nhưng đó là một sự hy sinh nhỏ vì bộ nhớ cao điểm không tăng. Điều này đã tăng tốc giai đoạn LoadStoreAnalysis của chúng tôi thêm 34-66%, từ đó cải thiện thời gian biên dịch thêm ~0,5-1,8%.

Chúng tôi có một phương thức triển khai tuỳ chỉnh của HashSet mà chúng tôi sử dụng ở một số nơi. Việc tạo cấu trúc dữ liệu này đã tốn một lượng thời gian đáng kể và chúng tôi đã tìm ra lý do. Nhiều năm trước, cấu trúc dữ liệu này chỉ được sử dụng ở một số nơi đang sử dụng HashSet rất lớn và đã được điều chỉnh để tối ưu hoá cho cấu trúc dữ liệu đó. Tuy nhiên, ngày nay, cấu trúc dữ liệu này được sử dụng theo hướng ngược lại với chỉ một vài mục nhập và có thời gian tồn tại ngắn. Điều này có nghĩa là chúng tôi đang lãng phí các chu kỳ bằng cách tạo HashSet khổng lồ này nhưng chỉ sử dụng nó cho một vài mục nhập trước khi loại bỏ. Với thay đổi này, chúng tôi đã cải thiện ~1,3-2% thời gian biên dịch. Ngoài ra, mức sử dụng bộ nhớ đã giảm ~0,5-1% vì chúng tôi không sử dụng các cấu trúc dữ liệu lớn như trước.

Chúng tôi đã cải thiện ~0,5-1% thời gian biên dịch bằng cách truyền cấu trúc dữ liệu theo tham chiếu đến lambda để tránh sao chép chúng. Đây là điều mà chúng tôi đã bỏ lỡ trong lần xem xét ban đầu và đã nằm trong cơ sở mã của chúng tôi trong nhiều năm. Nhờ xem xét các hồ sơ trong pprof, chúng tôi nhận thấy rằng các phương thức này đang tạo và phá huỷ rất nhiều cấu trúc dữ liệu, điều này đã khiến chúng tôi điều tra và tối ưu hoá chúng.

Chúng tôi đã tăng tốc giai đoạn ghi đầu ra đã biên dịch bằng cách lưu vào bộ nhớ đệm các giá trị đã tính toán, điều này đã giúp cải thiện ~1,3-2,8% tổng thời gian biên dịch. Rất tiếc, việc ghi sổ sách bổ sung là quá nhiều và quá trình kiểm thử tự động của chúng tôi đã cảnh báo về sự hồi quy bộ nhớ. Sau đó, chúng tôi đã xem xét lại cùng một mã và triển khai một phiên bản mới không chỉ xử lý sự hồi quy bộ nhớ mà còn cải thiện thêm ~0,5-1,8% thời gian biên dịch! Trong thay đổi thứ hai này, chúng tôi phải tái cấu trúc và hình dung lại cách giai đoạn này hoạt động để loại bỏ một trong hai cấu trúc dữ liệu.

Chúng tôi có một giai đoạn trong trình biên dịch tối ưu hoá để nội tuyến các lệnh gọi hàm nhằm đạt được hiệu suất tốt hơn. Để chọn phương thức nội tuyến, chúng tôi sử dụng cả phương pháp phỏng đoán trước khi thực hiện bất kỳ tính toán nào và kiểm tra cuối cùng sau khi thực hiện công việc nhưng ngay trước khi chúng tôi hoàn tất việc nội tuyến. Nếu bất kỳ phương thức nào trong số đó phát hiện thấy việc nội tuyến không đáng giá (ví dụ: sẽ có quá nhiều lệnh mới được thêm vào), thì chúng tôi sẽ không nội tuyến lệnh gọi phương thức.

Chúng tôi đã chuyển hai lần kiểm tra từ danh mục "kiểm tra cuối cùng" sang danh mục "phương pháp phỏng đoán" để ước tính xem việc nội tuyến có thành công hay không trước khi thực hiện bất kỳ tính toán tốn thời gian nào. Vì đây là ước tính nên không hoàn hảo, nhưng chúng tôi đã xác minh rằng phương pháp phỏng đoán mới của chúng tôi bao gồm 99,9% những gì đã được nội tuyến trước đó mà không ảnh hưởng đến hiệu suất. Một trong những phương pháp phỏng đoán mới này là về các thanh ghi DEX cần thiết (cải thiện ~0,2-1,3%) và phương pháp phỏng đoán còn lại là về số lượng lệnh (cải thiện ~2%).

Chúng tôi có một phương thức triển khai tuỳ chỉnh của BitVector mà chúng tôi sử dụng ở một số nơi. Chúng tôi đã thay thế lớp BitVector có thể thay đổi kích thước bằng một BitVectorView đơn giản hơn cho một số vectơ bit có kích thước cố định. Điều này loại bỏ một số gián tiếp và kiểm tra phạm vi thời gian chạy, đồng thời tăng tốc quá trình xây dựng các đối tượng vectơ bit.

Hơn nữa, lớp BitVectorView đã được tạo mẫu trên loại bộ nhớ cơ bản (thay vì luôn sử dụng uint32_t làm BitVector cũ). Điều này cho phép một số thao tác, ví dụ: Union(), xử lý số lượng bit gấp đôi cùng nhau trên các nền tảng 64 bit. Các mẫu của các hàm bị ảnh hưởng đã giảm hơn 1% tổng cộng khi biên dịch hệ điều hành Android. Việc này được thực hiện thông qua một số thay đổi [123456]

Nếu chúng tôi nói chi tiết về tất cả các hoạt động tối ưu hoá, thì chúng tôi sẽ ở đây cả ngày! Nếu bạn quan tâm đến một số hoạt động tối ưu hoá khác, hãy xem một số thay đổi khác mà chúng tôi đã triển khai:

Kết luận

Việc chúng tôi nỗ lực cải thiện tốc độ thời gian biên dịch của ART đã mang lại những cải tiến đáng kể, giúp Android mượt mà và hiệu quả hơn, đồng thời góp phần cải thiện thời lượng pin và nhiệt độ thiết bị. Bằng cách xác định và triển khai các hoạt động tối ưu hoá một cách siêng năng, chúng tôi đã chứng minh rằng có thể đạt được những lợi ích đáng kể về thời gian biên dịch mà không ảnh hưởng đến mức sử dụng bộ nhớ hoặc chất lượng mã.

Hành trình của chúng tôi bao gồm việc lập hồ sơ bằng các công cụ như pprof, sẵn sàng lặp lại và đôi khi thậm chí từ bỏ những con đường ít hiệu quả hơn. Những nỗ lực tập thể của nhóm ART không chỉ giảm thời gian biên dịch theo một tỷ lệ đáng chú ý mà còn đặt nền tảng cho những tiến bộ trong tương lai.

Tất cả những điểm cải tiến này đều có trong bản cập nhật Android cuối năm 2025 và cho Android 12 trở lên thông qua các bản cập nhật chính. Chúng tôi hy vọng rằng việc nghiên cứu chuyên sâu về quy trình tối ưu hoá của chúng tôi sẽ cung cấp thông tin chi tiết có giá trị về sự phức tạp và phần thưởng của kỹ thuật trình biên dịch!

Tiếp tục đọc