Cải tiến NGHỆ THUẬT Android 8.0

Thời gian chạy Android (ART) đã được cải thiện đáng kể trong bản phát hành Android 8.0. Danh sách dưới đây tóm tắt những cải tiến mà nhà sản xuất thiết bị có thể mong đợi ở ART.

Máy thu gom rác nén đồng thời

Như đã thông báo tại Google I/O, ART có tính năng thu gom rác nén đồng thời (GC) mới trong Android 8.0. Trình thu thập này nén vùng heap mỗi khi GC chạy và trong khi ứng dụng đang chạy, chỉ có một lần tạm dừng ngắn để xử lý các gốc luồng. Dưới đây là những lợi ích của nó:

  • GC luôn nén vùng heap: trung bình kích thước vùng heap nhỏ hơn 32% so với Android 7.0.
  • Tính năng nén cho phép phân bổ đối tượng con trỏ cục bộ của luồng: Phân bổ nhanh hơn 70% so với Android 7.0.
  • Cung cấp thời gian tạm dừng nhỏ hơn 85% cho điểm chuẩn H2 so với Android 7.0 GC.
  • Thời gian tạm dừng không còn tăng theo kích thước heap; các ứng dụng sẽ có thể sử dụng đống lớn mà không phải lo lắng về hiện tượng giật.
  • Chi tiết triển khai GC - Rào cản đọc:
    • Rào cản đọc là một lượng nhỏ công việc được thực hiện cho từng trường đối tượng được đọc.
    • Chúng được tối ưu hóa trong trình biên dịch nhưng có thể làm chậm một số trường hợp sử dụng.

Tối ưu hóa vòng lặp

Nhiều cách tối ưu hóa vòng lặp khác nhau được ART sử dụng trong bản phát hành Android 8.0:

  • Loại bỏ kiểm tra giới hạn
    • Tĩnh: phạm vi được chứng minh là nằm trong giới hạn tại thời gian biên dịch
    • Động: kiểm tra thời gian chạy đảm bảo các vòng lặp nằm trong giới hạn (nếu không thì deopt)
  • Loại bỏ biến cảm ứng
    • Loại bỏ cảm ứng chết
    • Thay thế quy nạp chỉ được sử dụng sau vòng lặp bằng các biểu thức dạng đóng
  • Loại bỏ mã chết bên trong thân vòng lặp, loại bỏ toàn bộ các vòng lặp đã chết
  • Giảm sức mạnh
  • Các phép biến đổi vòng lặp: đảo ngược, hoán đổi, tách, hủy cuộn, đơn mô đun, v.v.
  • SIMDization (còn gọi là vector hóa)

Trình tối ưu hóa vòng lặp nằm trong thẻ tối ưu hóa riêng của nó trong trình biên dịch ART. Hầu hết các tối ưu hóa vòng lặp đều tương tự như tối ưu hóa và đơn giản hóa ở những nơi khác. Các thách thức nảy sinh với một số tối ưu hóa viết lại CFG theo cách phức tạp hơn bình thường, bởi vì hầu hết các tiện ích CFG (xem node.h) đều tập trung vào việc xây dựng CFG chứ không phải viết lại CFG.

Phân tích phân cấp lớp

ART trong Android 8.0 sử dụng Phân tích phân cấp lớp (CHA), một trình tối ưu hóa trình biên dịch giúp ảo hóa các cuộc gọi ảo thành cuộc gọi trực tiếp dựa trên thông tin được tạo bằng cách phân tích hệ thống phân cấp lớp. Các cuộc gọi ảo rất tốn kém vì chúng được triển khai xung quanh việc tra cứu vtable và chúng chịu một số tải phụ thuộc. Ngoài ra, các cuộc gọi ảo không thể được nội tuyến.

Dưới đây là bản tóm tắt các cải tiến liên quan:

  • Cập nhật trạng thái phương thức triển khai đơn động - Vào cuối thời gian liên kết lớp, khi vtable đã được điền, ART tiến hành so sánh từng mục nhập với vtable của siêu lớp.
  • Tối ưu hóa trình biên dịch - Trình biên dịch sẽ tận dụng thông tin triển khai đơn lẻ của một phương thức. Nếu một phương thức A.foo có cờ triển khai đơn được đặt, trình biên dịch sẽ biến cuộc gọi ảo thành cuộc gọi trực tiếp và kết quả là cố gắng nội tuyến cuộc gọi trực tiếp.
  • Vô hiệu hóa mã biên dịch - Cũng vào cuối thời gian liên kết lớp khi thông tin triển khai một lần được cập nhật, nếu phương thức A.foo trước đây có triển khai một lần nhưng trạng thái đó hiện bị vô hiệu, tất cả mã được biên dịch phụ thuộc vào giả định rằng phương thức A. foo có nhu cầu triển khai đơn lẻ để mã được biên dịch của chúng bị vô hiệu hóa.
  • Hủy tối ưu hóa - Đối với mã được biên dịch trực tiếp nằm trên ngăn xếp, quá trình hủy tối ưu hóa sẽ được bắt đầu để buộc mã được biên dịch không hợp lệ sang chế độ trình thông dịch nhằm đảm bảo tính chính xác. Một cơ chế khử tối ưu hóa mới là sự kết hợp giữa khử tối ưu hóa đồng bộ và không đồng bộ sẽ được sử dụng.

Bộ nhớ đệm nội tuyến trong tệp .oat

ART hiện sử dụng bộ nhớ đệm nội tuyến và tối ưu hóa các trang web cuộc gọi có đủ dữ liệu. Tính năng bộ đệm nội tuyến ghi lại thông tin thời gian chạy bổ sung vào cấu hình và sử dụng thông tin đó để thêm các tối ưu hóa động vào quá trình biên dịch trước thời hạn.

Dexlayout

Dexlayout là một thư viện được giới thiệu trong Android 8.0 để phân tích các tệp dex và sắp xếp lại chúng theo hồ sơ. Dexlayout nhằm mục đích sử dụng thông tin lược tả thời gian chạy để sắp xếp lại các phần của tệp dex trong quá trình biên dịch bảo trì không hoạt động trên thiết bị. Bằng cách nhóm các phần của tệp dex thường được truy cập lại với nhau, các chương trình có thể có kiểu truy cập bộ nhớ tốt hơn nhờ vị trí được cải thiện, tiết kiệm RAM và rút ngắn thời gian khởi động.

Vì thông tin hồ sơ hiện chỉ khả dụng sau khi chạy ứng dụng nên dexlayout được tích hợp trong quá trình biên dịch trên thiết bị của dex2oat trong thời gian bảo trì không hoạt động.

Xóa bộ đệm Dex

Lên đến Android 7.0, đối tượng DexCache sở hữu bốn mảng lớn, tỷ lệ thuận với số phần tử nhất định trong DexFile, cụ thể là:

  • chuỗi (một tham chiếu cho mỗi DexFile::StringId),
  • các loại (một tham chiếu cho mỗi DexFile::TypeId),
  • các phương thức (một con trỏ gốc cho mỗi DexFile::MethodId),
  • các trường (một con trỏ gốc cho mỗi DexFile::FieldId).

Các mảng này được sử dụng để truy xuất nhanh các đối tượng mà chúng tôi đã giải quyết trước đó. Trong Android 8.0, tất cả các mảng đã bị xóa ngoại trừ mảng phương thức.

Hiệu suất phiên dịch

Hiệu suất của trình thông dịch được cải thiện đáng kể trong bản phát hành Android 7.0 với sự ra mắt của "mterp" - một trình thông dịch có cơ chế tìm nạp/giải mã/thông dịch cốt lõi được viết bằng hợp ngữ. Mterp được mô phỏng theo trình thông dịch Dalvik nhanh và hỗ trợ arm, arm64, x86, x86_64, mips và mips64. Về mã tính toán, mterp của Art gần như tương đương với trình thông dịch nhanh của Dalvik. Tuy nhiên, trong một số trường hợp, nó có thể chậm hơn đáng kể - và thậm chí đáng kể -:

  1. Gọi hiệu suất.
  2. Thao tác chuỗi và những người sử dụng nhiều phương thức khác được công nhận là nội tại trong Dalvik.
  3. Mức sử dụng bộ nhớ ngăn xếp cao hơn.

Android 8.0 giải quyết những vấn đề này.

Thêm nội tuyến

Kể từ Android 6.0, ART có thể nội tuyến bất kỳ cuộc gọi nào trong cùng một tệp dex, nhưng chỉ có thể nội tuyến các phương thức lá từ các tệp dex khác nhau. Có hai lý do cho hạn chế này:

  1. Nội tuyến từ một tệp dex khác yêu cầu sử dụng bộ đệm dex của tệp dex khác đó, không giống như nội tuyến tệp dex tương tự, chỉ có thể sử dụng lại bộ đệm dex của người gọi. Bộ đệm dex cần thiết trong mã được biên dịch cho một số lệnh như lệnh gọi tĩnh, tải chuỗi hoặc tải lớp.
  2. Bản đồ ngăn xếp chỉ mã hóa chỉ mục phương thức trong tệp dex hiện tại.

Để giải quyết những hạn chế này, Android 8.0:

  1. Xóa quyền truy cập bộ đệm dex khỏi mã được biên dịch (xem thêm phần "Xóa bộ đệm dex")
  2. Mở rộng mã hóa bản đồ ngăn xếp.

Cải tiến đồng bộ hóa

Nhóm ART đã điều chỉnh các đường dẫn mã MonitorEnter/MonitorExit và giảm sự phụ thuộc của chúng tôi vào các rào cản bộ nhớ truyền thống trên ARMv8, thay thế chúng bằng các hướng dẫn (thu thập/phát hành) mới hơn nếu có thể.

Phương pháp gốc nhanh hơn

Các lệnh gọi gốc nhanh hơn đến Giao diện gốc Java (JNI) có sẵn bằng cách sử dụng các chú thích @FastNative@CriticalNative . Các tính năng tối ưu hóa thời gian chạy ART tích hợp này giúp tăng tốc quá trình chuyển đổi JNI và thay thế ký hiệu !bang JNI hiện không còn được dùng nữa. Các chú thích không có tác dụng đối với các phương thức không phải gốc và chỉ khả dụng cho mã Ngôn ngữ Java nền tảng trên bootclasspath (không có bản cập nhật Cửa hàng Play).

Chú thích @FastNative hỗ trợ các phương thức không tĩnh. Sử dụng điều này nếu một phương thức truy cập một jobject dưới dạng tham số hoặc giá trị trả về.

Chú thích @CriticalNative cung cấp một cách thậm chí còn nhanh hơn để chạy các phương thức gốc với các hạn chế sau:

  • Các phương thức phải ở dạng tĩnh—không có đối tượng cho tham số, giá trị trả về hoặc ẩn this .
  • Chỉ các kiểu nguyên thủy mới được truyền cho phương thức gốc.
  • Phương thức gốc không sử dụng tham số JNIEnvjclass trong định nghĩa hàm của nó.
  • Phương thức này phải được đăng ký với RegisterNatives thay vì dựa vào liên kết JNI động.

@FastNative có thể cải thiện hiệu suất của phương thức gốc lên tới 3 lần và @CriticalNative lên tới 5 lần. Ví dụ: quá trình chuyển đổi JNI được đo trên thiết bị Nexus 6P:

Lệnh gọi Giao diện gốc Java (JNI) Thời gian thực hiện (tính bằng nano giây)
JNI thường xuyên 115
!bang JNI 60
@FastNative 35
@CriticalNative 25