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ỳ sự hồi quy nào về bộ nhớ cao điểm. Đ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 đúng lúc (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 điểm tối ưu hoá có hiệu lực, giúp mang lại trải nghiệm mượt mà và phản hồi nhanh hơn cho người dùng. Ngoài ra, đố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 đã đượ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. Ngoài ra, tất cả người dùng Android từ phiên bản 12 trở lên đều đủ điều kiện nhận 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 thứ 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 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 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 đ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 điểm tối ưu hoá có thể mang lại lợi ích
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ải thiện được chỉ số đó hay chưa. May mắn cho chúng tôi là tốc độ 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 chỉnh nhiệt độ thiết bị của mình. 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 bên trong.
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 của mình, 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à xứng đáng bằng cả phương thức 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 đó, 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 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.
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ó làm 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ì nó 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 rằng 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ể nút nào chúng tôi có tại thời điểm đó. Trong những trường hợp này, chúng ta có thể bỏ qua hoàn toàn việc lặp lại, giảm từ 1,023% xuống còn khoảng 0,3% và cải thiện thời gian chạy của GVN khoảng 15%.
Triển khai các điểm tối ưu hoá có lợi
Chúng tôi đã đề cập đến cách đo lường và cách phát hiện nơi thời gian đang được sử dụng, 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 chúng tôi lặp lại các nút và thực hiện nhanh hơn, ví dụ: thực hiện 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 được 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 chút nào! Khi thực hiện các loại 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 việc 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 lười biếng để tránh các chu kỳ trong một số trường hợp
- sử dụng tính 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 đuổi con trỏ thường xuyên được sử dụng thông qua nhiều lần tải
Làm cách nào để biết liệu việc 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 ra 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 toàn bộ mã nguồn, v.v. Đối với mọi điểm 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ố điểm tối ưu hoá khác không thành hiện thực.
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 làm ít việc nhất có thể. Đ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 xem xét việc ước tính những nhược điểm của giải pháp. Ví dụ: nếu bạn sẽ dựa vào các 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 nhiều nữa, 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. 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 để 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 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 từ 34 đến 66%, từ đó giúp cải thiện thời gian biên dịch khoảng 0,5 đến 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, nó được sử dụng theo hướng ngược lại với chỉ một vài mục và có tuổi thọ 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úng tôi chỉ sử dụng nó cho một vài mục trước khi loại bỏ. Với thay đổi này, chúng tôi đã cải thiện khoảng 1,3 đến 2% thời gian biên dịch. Ngoài ra, mức sử dụng bộ nhớ đã giảm khoảng 0,5 đến 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 khoảng 0,5 đến 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 toàn bộ mã nguồn 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 tổng thời gian biên dịch khoảng 1,3 đến 2,8%. Rất tiếc, việc ghi sổ bổ sung quá nhiều và quá trình kiểm thử tự động đã cảnh báo chúng tôi 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 thời gian biên dịch khoảng 0,5 đến 1,8%! 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 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ẽ thêm quá nhiều hướng dẫn mới), 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 đ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 khoảng 0,2 đến 1,3%) và phương pháp còn lại là về số lượng hướng dẫn (cải thiện khoảng 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 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 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 điểm 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ố điểm 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 việc ghi sổ để cải thiện thời gian biên dịch khoảng 0,6 đến 1,6%.
- Tính toán dữ liệu một cách lười biếng để 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 về việc 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 các con trỏ nội bộ của vùng chứa do các tác dụ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 điểm tối ưu hoá trình biên dịch.
Kết luận
Việc chúng tôi nỗ lực cải thiện tốc độ biên dịch của ART đã mang lại những điểm 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 điểm 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 hướng đi í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 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 bài nghiên cứu chuyên sâu này về quy trình tối ưu hóa 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à lợi ích của kỹ thuật trình biên dịch!
Tiếp tục đọc
-
năm 2026năm 2026
Tin tức về sản phẩm
Năm ngoái, chúng tôi đã ra mắt quy trình xác minh nhà phát triển Android để tăng cường bảo mật hệ sinh thái và ngăn chặn các tác nhân độc hại ẩn danh để phát hành các ứng dụng gây hại.
Matthew Forsythe • Đọc trong 2 phút
-
năm 2026năm 2026
Tin tức về sản phẩm
Từ lớp phủ tăng cường đến môi trường hoàn toàn nhập vai, hệ sinh thái Android XR đang phát triển nhanh chóng, với Samsung Galaxy XR đã có mặt trên thị trường.
Stevan Silva, Vinny DaSilva • Đọc trong 3 phút
-
Tin tức về sản phẩm
Mỗi năm, Google I/O đều mang đến những thông báo và tài nguyên mới trên các hệ sinh thái và sản phẩm, bao gồm cả quá trình phát triển Android. Khi quá trình phát triển chuyển sang AI và công cụ hỗ trợ tác nhân, chúng tôi đã mở rộng các dịch vụ của mình để hỗ trợ bạn tốt hơn, bất kể bạn quyết định xây dựng cho Android như thế nào.
Simona Milanovic • Đọc trong 2 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 vào hộp thư đến của bạn mỗi tuần.