Phân tích bằng tính năng phân tích trực quan kết xuất GPU

Công cụ phân tích trực quan kết xuất GPU cho biết thời gian tương đối mà mỗi giai đoạn của quy trình kết xuất cần để hiển thị khung trước. Kiến thức này có thể giúp bạn xác định nút thắt cổ chai trong quy trình để bạn có thể tối ưu hoá nhằm cải thiện hiệu suất hiển thị của ứng dụng.

Trang này giải thích ngắn gọn những gì xảy ra trong từng giai đoạn của quy trình, đồng thời trình bày các vấn đề có thể gây nút thắt cổ chai. Trước khi đọc trang này, bạn nên làm quen với thông tin trình bày trong phần Tốc độ kết xuất phân tích GPU. Ngoài ra, để hiểu cách tất cả các giai đoạn kết hợp với nhau, bạn nên xem lại cách hoạt động của quy trình kết xuất.

Minh họa bằng hình ảnh

Công cụ phân tích trực quan kết xuất GPU hiển thị các giai đoạn và thời gian tương đối ở dạng biểu đồ: biểu đồ được mã hóa màu. Hình 1 cho thấy một ví dụ có phần hiển thị như vậy.

Biểu đồ phân tích trực quan kết xuất GPU
Hình 1. Biểu đồ phân tích trực quan kết xuất GPU

Mỗi phân đoạn của mỗi thanh dọc hiển thị trong biểu đồ phân tích trực quan kết xuất GPU đại diện cho một giai đoạn trong quy trình, và được đánh dấu bằng một màu cụ thể trong biểu đồ thanh. Hình 2 cho thấy ý nghĩa của từng màu hiển thị.

Chú giải biểu đồ phân tích trực quan kết xuất GPU
Hình 2. Chú giải biểu đồ phân tích trực quan kết xuất GPU

Sau khi hiểu rõ từng đặc điểm màu sắc, bạn có thể nhắm mục tiêu các khía cạnh cụ thể của ứng dụng nhằm cố gắng tối ưu hoá hiệu suất hiển thị của ứng dụng.

Các giai đoạn và ý nghĩa của các giai đoạn

Phần này giải thích những gì xảy ra trong mỗi giai đoạn cũng như các nguyên nhân gây điểm tắc nghẽn cần chú ý.

Xử lý dữ liệu đầu vào

Giai đoạn xử lý đầu vào của quy trình đo lường thời gian ứng dụng xử lý sự kiện đầu vào. Chỉ số này cho biết lượng thời gian ứng dụng thực thi mã được gọi do các lệnh gọi lại sự kiện đầu vào.

Khi phân đoạn này lớn

Các giá trị cao trong khu vực này thường là do có quá nhiều công việc hoặc công việc phức tạp xảy ra bên trong lệnh gọi lại sự kiện xử lý đầu vào. Vì các lệnh gọi lại này luôn xảy ra trên luồng chính, nên các giải pháp cho vấn đề này sẽ tập trung vào việc tối ưu hoá tác vụ trực tiếp hoặc chuyển tác vụ sang một luồng khác.

Thao tác cuộn qua LazyColumn hoặc LazyRow cũng có thể xuất hiện trong giai đoạn này. Sau khi thao tác chạm của người dùng đủ điều kiện là thao tác cuộn, danh sách lười biếng sẽ sử dụng các sự kiện chạm để tạo và bố trí các mục một cách linh động. Nếu ứng dụng của bạn thực hiện công việc tuỳ chỉnh phản hồi các thay đổi về vị trí cuộn, thì bạn cần phải thực hiện thao tác này càng nhanh càng tốt để ngăn tình trạng giảm khung hình. Các công cụ phân tích hiệu suất như Trình phân tích CPU trong Android Studio hoặc Perfetto có thể giúp bạn điều tra thêm. Hãy xem bài viết Tổng quan về tính năng theo dõi hệ thống để biết thêm thông tin.

Ảnh động

Giai đoạn ảnh động cho bạn biết thời gian cần thiết để đánh giá tất cả các trạng thái ảnh động đã chạy trong khung đó. Một số API ảnh động phổ biến trong Compose là animate*AsState, TransitionAnimatable. Ngoài ra, Recomposer sẽ chạy trong giai đoạn này để xử lý các thay đổi về trạng thái ảnh chụp nhanh và cập nhật thành phần. Điều này có nghĩa là mức hao tổn khi kết hợp lại thường xuất hiện ngay trong giai đoạn ảnh động.

Đối với giao diện người dùng Jetpack Compose, hãy thêm thư viện Theo dõi thời gian chạy Compose để xem dấu vết thành phần chi tiết cùng với các sự kiện hệ thống.

Khi phân đoạn này lớn

Các giá trị cao trong khu vực này thường là kết quả của việc thực thi do có các thay đổi về trạng thái do ảnh động điều khiển. Ví dụ: một ảnh động từ cử chỉ hất, cuộn LazyColumn hoặc LazyRow của bạn sẽ dẫn đến việc tạo thành phần, đo lường và phân bổ nhanh các mục mới trong danh sách.

Đo lường

Để vẽ các thành phần kết hợp trên màn hình, Android sẽ thực thi 3 giai đoạn trên các nút bố cục trong cây giao diện người dùng.

Trước tiên, hệ thống đo lường các nút bố cục. Mỗi thành phần kết hợp đều có các ràng buộc và đối tượng sửa đổi cụ thể mô tả giới hạn kích thước của đối tượng trên màn hình. Một số thành phần kết hợp có thể có kích thước cố định cụ thể; một số khác có kích thước thích ứng với các điều kiện ràng buộc do vùng chứa bố cục mẹ truyền xuống.

Thứ hai, hệ thống sẽ đặt các nút bố cục. Sau khi tính toán kích thước của các nút con trong giai đoạn đo lường, Compose có thể tiến hành giai đoạn đặt vị trí, trong đó Compose định cỡ và đặt vị trí các nút bố cục trên màn hình.

Hệ thống luôn thực hiện bố cục một lượt này để đạt được hiệu quả. Khi một bố cục có khả năng kết hợp không hợp lệ, Compose sẽ đo lường nút cụ thể đó và chỉ truyền các nội dung cập nhật bố cục lên hệ thống phân cấp mẹ nếu thành phần con thay đổi kích thước hoặc các ràng buộc.

Khi phân đoạn này lớn

Một phân đoạn lớn trong khu vực này có nghĩa là ứng dụng đang dành quá nhiều thời gian trong giai đoạn bố cục, bao gồm việc định vị và xác định kích thước của các nút bố cục. Các thao tác này bao gồm việc thực thi các phép đo và bộ sửa đổi vị trí cho thành phần kết hợp. Các thao tác này có thể trì hoãn quá trình chuẩn bị khung hình nếu cây bố cục quá phức tạp. Trong những trường hợp này, việc giải quyết vấn đề hiệu suất bao gồm việc đo điểm chuẩn cho ứng dụng Compose và làm theo các phương pháp hay nhất về hiệu suất.

Sử dụng Trình phân tích CPU trong Android Studio hoặc Perfetto để kiểm tra các lượt bố trí và xác định nút thắt cổ chai. Hãy xem bài viết Tổng quan về tính năng theo dõi hệ thống để biết thêm thông tin.

Vẽ

Giai đoạn vẽ sẽ dịch các hoạt động kết xuất, chẳng hạn như vẽ nền, hình dạng hoặc văn bản, thành một chuỗi các lệnh vẽ gốc. Hệ thống sẽ ghi lại các lệnh này vào danh sách hiển thị để GPU thực thi.

Thanh Vẽ ghi lại khoảng thời gian cần thiết để hoàn thành việc ghi các lệnh vào danh sách hiển thị, cho tất cả các nút bố cục cần được cập nhật trên màn hình cho khung hình này. Thời gian đo được cũng áp dụng cho mọi logic vẽ tuỳ chỉnh mà bạn có thể có trong các đối tượng sửa đổi vẽ hoặc một thành phần kết hợp Canvas.

Khi phân đoạn này lớn

Nói một cách ngắn gọn, bạn có thể hiểu chỉ số này là chỉ số thời gian để chạy tất cả các lệnh vẽ cho mỗi nút bố cục không hợp lệ. Quá trình đo lường này bao gồm bất kỳ thời gian nào dành cho việc gửi các lệnh này đến các nút con và đối tượng vẽ vectơ. Do đó, khi bạn thấy thanh này tăng đột biến, nguyên nhân có thể là do nhiều thành phần kết hợp đột nhiên bị vô hiệu hoá. Việc vô hiệu hoá khiến bạn cần phải thực thi lại các lệnh vẽ và tạo lại danh sách hiển thị của các nút bố cục. Ngoài ra, thời gian dài có thể là kết quả của một vài thành phần kết hợp tuỳ chỉnh hoặc canvas có một số logic cực kỳ phức tạp trong quá trình triển khai DrawScope.

Ngoài ra, Compose thường xử lý các đường chuyền bố cục và đo lường nội bộ trong giai đoạn Vẽ mà nền tảng coi là giai đoạn Vẽ. Do đó, thanh Draw (Vẽ) tăng cao có thể là do các thao tác đo lường/bố cục nội bộ tốn kém hoặc quá mức chứ không chỉ do các lệnh vẽ. Nếu nghi ngờ, hãy ghi lại dấu vết Perfetto để xem liệu chi phí phát sinh từ các quy trình vẽ hay các lượt đo lường và bố trí Compose.

Tải lên

Chỉ số tải lên thể hiện thời gian cần thiết để chuyển các đối tượng bitmap từ bộ nhớ CPU sang bộ nhớ GPU trong khung hiện tại.

Là các bộ xử lý khác nhau, CPU và GPU có các vùng RAM khác nhau dành riêng cho quá trình xử lý. Khi bạn vẽ một bitmap trên Android, hệ thống sẽ chuyển bitmap vào bộ nhớ GPU trước khi GPU có thể kết xuất bitmap vào màn hình. Sau đó, GPU lưu bitmap vào bộ nhớ đệm để hệ thống không cần phải chuyển lại dữ liệu trừ phi hoạ tiết bị loại khỏi bộ nhớ đệm hoạ tiết GPU.

Lưu ý: Trên các thiết bị Lollipop, giai đoạn này có màu tím.

Khi phân đoạn này lớn

Tất cả tài nguyên cho một khung cần phải nằm trong bộ nhớ GPU trước khi có thể dùng để vẽ khung. Điều đó có nghĩa là giá trị cao của chỉ số này đại diện cho một lượng lớn lượt tải tài nguyên nhỏ hoặc một lượng nhỏ lượt tải các tài nguyên rất lớn. Trường hợp phổ biến là khi ứng dụng hiển thị một bitmap gần với kích thước của màn hình. Một trường hợp khác là khi ứng dụng hiển thị một lượng lớn các hình thu nhỏ.

Để thu nhỏ thanh này, bạn có thể sử dụng các kỹ thuật như bên dưới:

  • Đảm bảo độ phân giải bitmap của bạn không lớn hơn kích thước mà chúng sẽ hiển thị. Ví dụ: tránh hiển thị hình ảnh có kích thước 1024x1024 dưới dạng hình ảnh có kích thước 48x48.
  • Tận dụng các thư viện hiện đại như Coil để tải bitmap lên một cách không đồng bộ trước giai đoạn đồng bộ hoá tiếp theo.

Lệnh thực thi

Phân đoạn lệnh thực thi cho biết thời gian đưa ra tất cả các lệnh cần thiết để vẽ danh sách hiển thị lên màn hình.

Để vẽ các danh sách hiển thị lên màn hình, hệ thống sẽ gửi các lệnh cần thiết đến GPU. Thông thường, công cụ sẽ thực hiện hành động này thông qua API OpenGL ES.

Quá trình này sẽ mất một chút thời gian, vì hệ thống thực hiện chuyển đổi lần cuối và cắt nội dung cho mỗi lệnh trước khi gửi lệnh đến GPU. Chi phí bổ sung lúc này phát sinh ở phía GPU, đây là nơi tính toán các lệnh cuối cùng. Các lệnh này bao gồm các phép biến đổi cuối cùng và các thao tác cắt bổ sung.

Khi phân đoạn này lớn

Thời gian dành cho giai đoạn này là thước đo trực tiếp về độ phức tạp và số lượng của danh sách hiển thị mà hệ thống kết xuất trong một khung hình nhất định. Chẳng hạn như việc có nhiều thao tác vẽ, đặc biệt là trong trường hợp có một khoản chi phí cố định nhỏ cho mỗi lần vẽ nguyên bản, có thể làm tăng thời gian này. Ví dụ:

for (i in 0 until 1000) {
    canvas.drawPoint()
}

chi phí phát hành đắt hơn rất nhiều so với:

canvas.drawPoints(thousandPointArray)

Không phải lúc nào cũng có mối tương quan 1:1 giữa việc ra lệnh và thực sự vẽ danh sách hiển thị. Khác với thanh lệnh thực thi, chỉ số vẽ không ghi lại thời gian cần thiết để gửi lệnh vẽ tới GPU mà sẽ đại diện cho thời gian cần thiết để ghi lại các lệnh đã đưa ra vào danh sách hiển thị.

Sự khác biệt này phát sinh vì danh sách hiển thị sẽ được hệ thống lưu vào bộ nhớ đệm bất cứ khi nào có thể. Kết quả là trong một số trường hợp khi cuộn, chuyển đổi hoặc thực hiện các thao tác ảnh động, hệ thống sẽ được yêu cầu phải gửi lại danh sách hiển thị, nhưng không thực sự phải tạo lại danh sách đó - lấy lại lệnh vẽ từ đầu. Kết quả là bạn có thể thấy thanh lệnh phát hành cao mà không thấy thanh lệnh vẽ cao.

Hoán đổi vùng đệm

Sau khi Android hoàn tất việc gửi danh sách hiển thị tới GPU, hệ thống sẽ đưa ra một lệnh cuối cùng để báo cho trình điều khiển đồ hoạ biết hệ thống đã thực hiện lệnh đó với khung hình hiện tại. Tại thời điểm này, trình điều khiển cuối cùng có thể hiển thị hình ảnh cập nhật cho màn hình.

Khi phân đoạn này lớn

Điều quan trọng là bạn cần phải biết GPU thực thi hoạt động song song với CPU. Hệ thống Android sẽ đưa ra các lệnh vẽ cho GPU, sau đó chuyển sang thao tác tiếp theo. GPU sẽ đọc các lệnh vẽ đó từ hàng đợi và xử lý các lệnh đó.

Trong những tình huống CPU đưa ra các lệnh nhanh hơn mức GPU tiêu thụ chúng, hàng đợi giao tiếp giữa các bộ xử lý có thể trở nên đầy. Khi điều này xảy ra, CPU sẽ chặn và đợi cho đến khi có khoảng trống trong hàng đợi để đặt lệnh tiếp theo. Trạng thái nghẽn hàng đợi này thường phát sinh trong giai đoạn trao đổi vùng đệm, vì vào thời điểm đó, hệ thống đã gửi một lệnh là toàn bộ khung hình.

Mấu chốt để giảm thiểu vấn đề này là giảm bớt sự phức tạp của công việc hoạt động trên GPU, tương tự như cách bạn sẽ làm đối với giai đoạn lệnh thực thi.

Khác

Ngoài thời gian hệ thống kết xuất thực hiện công việc, còn có một nhóm công việc khác diễn ra trên luồng chính và không liên quan đến quá trình kết xuất. Thời gian mà công việc này dùng được báo cáo là thời gian khác. Thời gian khác thường thể hiện công việc có thể diễn ra trên luồng giao diện người dùng giữa hai khung hình kết xuất liên tiếp.

Khi phân đoạn này lớn

Nếu giá trị này cao, thì có thể ứng dụng của bạn có các lệnh gọi lại, ý định hoặc hoạt động khác sẽ diễn ra trên một chuỗi khác. Các công cụ như Trình phân tích CPU trong Android Studio hoặc Perfetto có thể cho phép hiển thị các tác vụ đang chạy trên luồng chính. Thông tin này có thể giúp bạn nhắm mục tiêu các cải thiện về hiệu suất. Hãy xem bài viết Tổng quan về tính năng theo dõi hệ thống để biết thêm thông tin.