Nhóm Android Runtime (ART) đã giảm thời gian biên dịch xuống 18% mà không ảnh hưởng đến mã đã biên dịch hoặc bất kỳ mức giảm bộ nhớ cao nhất 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 độ 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 ứ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 đến trải nghiệm mượt mà và phản hồi nhanh hơn cho người dùng. 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 độ 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 độ của 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 độ 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 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 quá trình đánh đổi. Bạn không thể có đượ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 bản thân: làm cho trình biên dịch nhanh hơn, nhưng phải thực hiện mà không làm giảm 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 ứng dụng chạy chậm hơn, thì chúng tôi đã thất bại.
Nguồn 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 điểm 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 nhiệ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ì nguồn 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 nguyên 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ả hoạt động 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ơ biên dịch và sử dụng pprof để hình dung nơi chúng tôi đang dành thời gian.
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 tích, lọc và sắp xếp dữ liệu để xem, ví dụ: giai đoạn hoặc phương thức biên dịch nào đang chiếm nhiều thời gian nhất. 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 phương thức nào đang chiếm nhiều thời gian nhất. 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.
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 được 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ì nó thực hiện nói chung, nhưng phần liên quan là biết rằng nó 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 ~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à thấy rằng giải pháp là (trong một số trường hợp) không lặp lại! 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, bao gồm:
- sử dụng phương pháp phỏng đoán để quyết định xem 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ấ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, đôi khi bạn không thể tìm thấy giải pháp. Có thể không có gì để làm, sẽ mất quá nhiều thời gian để triển khai, 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ự:
- Ước tính bằng các chỉ số mà bạn đã thu thập hoặc chỉ là cảm tính
- Ước tính bằng nguyên mẫu nhanh và sơ sài
- 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ấu trúc dữ liệu bổ sung, 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 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 hướng dẫn, 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 nhất 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 khá nhiều thời gian 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 việc đó. 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, chỉ có một vài mục nhập và 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í chu kỳ bằng cách tạo HashSet khổng lồ này nhưng chỉ sử dụng 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ấ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 quá trình xem xét ban đầu và đã tồn tại 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ỷ 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à hoạt động kiểm thử tự động của chúng tôi đã cảnh báo về việc giảm 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ý việc giảm 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ỳ phép tính 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 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 hướng dẫn 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ỳ phép tính 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% nội dung đã đượ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 còn lại là về số lượng hướng dẫn (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 BitVectorView đơn giản hơn cho một số vectơ bit có kích thước cố định. Điều này giúp 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 [1, 2, 3, 4, 5, 6]
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:
- Thêm sổ sách để cải thiện thời gian biên dịch thêm ~0,6–1,6%.
- Tính toán dữ liệu một cách trì hoãn để tránh các chu kỳ, nếu có thể.
- Tái cấu trúc mã của chúng tôi để bỏ qua công việc tính toán trước khi mã không được sử dụng.
- Tránh một số chuỗi tải phụ thuộc khi có thể dễ dàng lấy trình phân bổ từ những nơi khác.
- Một trường hợp khác là thêm một lần kiểm tra để tránh công việc không cần thiết.
- Tránh phân nhánh thường xuyên trên loại thanh ghi (lõi/FP) trong trình phân bổ thanh ghi.
- Đảm bảo một số mảng được khởi chạy tại thời gian biên dịch. Đừng dựa vào clang để thực hiện việc này.
- Dọn dẹp một số vòng lặp. Sử dụng các vòng lặp phạm vi mà clang có thể tối ưu hoá tốt hơn vì không cần tải lại con trỏ nội bộ của vùng chứa do các hiệu ứng phụ của vòng lặp. Tránh gọi hàm ảo `HInstruction::GetInputRecords()` trong vòng lặp thông qua `InputAt(.)` được nội tuyến cho mỗi đầu vào.
- Tránh các hàm Accept() cho mẫu khách truy cập bằng cách khai thác hoạt động tối ưu hoá trình biên dịch.
Kết luận
Sự tận tâm của chúng tôi trong việc cải thiện tốc độ 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 độ của 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 xuống 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
-
Tin tức về sản phẩm
Quy trình làm việc và nhu cầu về AI của mỗi nhà phát triển là riêng biệt. Vì vậy, bạn cần có thể chọn cách AI hỗ trợ quá trình phát triển của mình. Vào tháng 1, chúng tôi đã giới thiệu khả năng chọn bất kỳ mô hình AI cục bộ hoặc từ xa nào để cung cấp chức năng AI trong Android Studio
Matthew Warner • Đọc trong 2 phút
-
Tin tức về sản phẩm
Android Studio Panda 3 hiện đã ổn định và sẵn sàng để bạn sử dụng trong quá trình phát hành chính thức. Bản phát hành này giúp bạn có thêm quyền kiểm soát và tuỳ chỉnh đối với quy trình làm việc dựa trên AI, giúp bạn dễ dàng hơn bao giờ hết trong việc xây dựng các ứng dụng Android chất lượng cao.
Matt Dyor • Đọc trong 3 phút
-
Tin tức về sản phẩm
Tại Google, chúng tôi cam kết mang những mô hình AI mạnh nhất trực tiếp đến các thiết bị Android trong túi của bạn. Hôm nay, chúng tôi rất vui mừng thông báo về việc phát hành mô hình mở hiện đại nhất của mình: Gemma 4.
Caren Chang, David Chou • Đọc trong 3 phút
Nhận thông tin cập nhật
Nhận thông tin chi tiết mới nhất về quá trình phát triển Android được gửi đến hộp thư đến của bạn hằng tuần.