Tổng quan về cách đo lường hiệu suất của ứng dụng

Chủ đề này giúp bạn xác định và khắc phục các vấn đề chính về hiệu suất của ứng dụng.

Các vấn đề chính về hiệu suất

Có nhiều vấn đề có thể góp phần gây ra hiệu suất kém trong một ứng dụng. Tuy nhiên, bạn nên lưu ý một số trường hợp sau đây trong ứng dụng của mình:

  • Scroll Jank (Giật trong khi cuộn)
    • "Jank" (Giật) là thuật ngữ dùng để mô tả hiện tượng hình ảnh xảy ra khi hệ thống không thể xây dựng và cung cấp khung hình kịp thời để vẽ lên màn hình theo nhịp độ yêu cầu (60hz trở lên). Jank thể hiện rõ nhất trong khi cuộn, khi một luồng hoạt ảnh đang chạy mượt mà bị giật, khi chuyển động tạm dừng dọc theo một hoặc nhiều khung hình do ứng dụng mất nhiều thời gian hơn để hiển thị nội dung so với thời lượng của một khung hình trên hệ thống.
    • Ứng dụng phải nhắm mục tiêu tốc độ làm mới 90Hz. Tốc độ kết xuất truyền thống là 60Hz, nhưng nhiều thiết bị mới hơn hoạt động ở chế độ 90Hz trong các tương tác của người dùng, chẳng hạn như cuộn, và một số thiết bị còn hỗ trợ tốc độ cao hơn, lên tới 120Hz.
      • Để xem tốc độ làm mới mà thiết bị đang sử dụng tại một thời điểm nhất định, hãy bật lớp phủ bằng cách vào Developer Options (Tùy chọn cho nhà phát triển) > Show refresh rate (Hiển thị tốc độ làm mới) trong phần Debugging (Gỡ lỗi).
  • Độ trễ khi khởi động

    • Độ trễ khi khởi động là khoảng thời gian cần thiết kể từ khi bạn nhấn vào biểu tượng ứng dụng, thông báo hoặc điểm nhập khác đến khi dữ liệu của người dùng hiển thị trên màn hình.
    • Bạn nên nhắm đến hai mục tiêu khởi động này trong ứng dụng:

      • Khởi động nguội < 500 mili giây: "Khởi động nguội" xảy ra khi ứng dụng đang chạy không có mặt trong bộ nhớ của hệ thống. Hiện tượng này xảy ra khi đây là lần khởi chạy đầu tiên của ứng dụng kể từ khi khởi động lại hoặc người dùng hoặc hệ thống buộc dừng quy trình hệ thống.

        Ngược lại, "khởi động ấm" xảy ra khi ứng dụng đang chạy dưới nền. Khởi động nguội yêu cầu khối lượng công việc nhiều nhất từ hệ thống do hệ thống phải tải lại mọi thứ từ bộ nhớ và khởi chạy ứng dụng. Hãy đặt mục tiêu khởi động nguội là 500 mili giây trở xuống.

      • Để các độ trễ P95/P99 gần với độ trễ trung bình. Nếu phải chờ rất lâu để ứng dụng khởi động, người dùng sẽ bị mất niềm tin. IPC và I/O không cần thiết trong quá trình khởi động ứng dụng quan trọng có thể gặp phải tình trạng khóa nội dung và dẫn đến những xung đột này.

  • Quá trình chuyển đổi không suôn sẻ

    • Những vấn đề này phát sinh trong quá trình tương tác, chẳng hạn như chuyển đổi giữa các thẻ hoặc tải một hoạt động mới. Các loại chuyển đổi này phải đảm bảo ảnh động động mượt mà và không có độ trễ hoặc nhấp nháy hình ảnh.
  • Nguồn điện không hiệu quả

    • Tất nhiên làm việc sẽ tốn pin, nhưng thực hiện những tác vụ không cần thiết lại cũng làm giảm thời lượng pin.
    • Việc phân bổ bộ nhớ từ việc tạo các đối tượng mới trong mã, có thể là nguyên nhân dẫn đến hệ thống phải thực hiện khối lượng công việc lớn. Bởi vì không chỉ có việc phân bổ yêu cầu chạy Android Runtime, mà việc giải phóng các đối tượng đó sau này ("gom rác") cũng đòi hỏi thời gian và công sức. Cả quá trình phân bổ và thu gom đều nhanh và hiệu quả hơn nhiều so với trước đây, đặc biệt là với các đối tượng tạm thời. Do đó, hướng dẫn trước đây là tránh phân bổ các đối tượng bất cứ khi nào có thể, còn đề xuất hiện tại là hãy thực hiện điều gì có lợi nhất cho ứng dụng và kiến trúc của bạn. Việc tiết kiệm khối lượng phân bổ không phải là lựa chọn phù hợp vì có thể gây ra nguy cơ không thể duy trì mã căn cứ vào các khả năng của ART.

      Tuy nhiên, hệ thống vẫn còn phải xử lý rất nhiều việc, vì vậy bạn cần lưu ý rằng việc phân bổ nhiều đối tượng trong vòng lặp nội bộ có thể góp phần gây ra các vấn đề về hiệu suất.

Xác định vấn đề

Quy trình công việc đề xuất để xác định và khắc phục các vấn đề về hiệu suất như sau:

  • Xác định các hành trình trọng yếu của người dùng mà bạn cần kiểm tra. Các hành trình này có thể bao gồm:
    • Các quy trình khởi động phổ biến, bao gồm cả từ trình chạy và thông báo.
    • Màn hình bất kỳ mà người dùng cuộn xem dữ liệu.
    • Chuyển đổi giữa các màn hình.
    • Các quy trình chạy lâu dài, chẳng hạn như điều hướng hoặc phát nhạc.
  • Kiểm tra xem điều gì sẽ xảy ra trong các quy trình đó bằng các công cụ gỡ lỗi:
    • Systrace hoặc Perfetto: Cho phép bạn xem chính xác những gì đang xảy ra trên toàn bộ thiết bị với dữ liệu thời gian chính xác.
    • Trình phân tích bộ nhớ: Cho phép bạn xem hoạt động phân bổ bộ nhớ nào đang diễn ra trên vùng nhớ heap.
    • Simpleperf: Xem biểu đồ về các lệnh gọi hàm đang chiếm nhiều CPU nhất trong một khoảng thời gian nhất định. Khi bạn xác định được tác vụ nào đang chạy nhiều thời gian trong systrace, nhưng không biết lý do tại sao, Simpleperf có thể cung cấp thêm thông tin.

Việc gỡ lỗi thủ công từng lần chạy thử nghiệm riêng lẻ rất quan trọng để giúp bạn hiểu và gỡ lỗi các vấn đề hiệu suất này. Bạn không thể thay thế các bước ở trên bằng cách phân tích dữ liệu tổng hợp. Tuy nhiên, việc thiết lập tính năng thu thập chỉ số trong chạy thử nghiệm tự động cũng như trong thực tế cũng rất quan trọng để hiểu rõ những nội dung mà người dùng thực sự nhìn thấy và xác định thời điểm có thể lùi hệ thống:

  • Quy trình khởi động
  • Giật
    • Chỉ số trường
      • Các chỉ số khung của Play Console: Lưu ý rằng trong Play Console, không thể thu hẹp các chỉ số cho một hành trình cụ thể của người dùng, vì tất cả chỉ số báo cáo là độ giật tổng thể trong toàn bộ ứng dụng.
      • Đo lường tùy chỉnh bằng FrameMetricsAggregator: Bạn có thể sử dụng FrameMetricsAggregator để ghi lại các chỉ số độ giật trong một quy trình công việc cụ thể.
    • Phòng thử nghiệm
      • Jetpack Macrobenchmark: Cuộn
      • Macrobenchmark thu thập thời gian kết xuất khung hình bằng cách sử dụng các lệnh dumpsys gfxinfo đóng một hành trình người dùng. Đây là một cách hợp lý để tìm hiểu về thay đổi độ giật trong một hành trình người dùng cụ thể. Chỉ số RenderTime, trong đó nêu rõ khoảng thời gian cần thiết để vẽ các khung hình, có ý nghĩa quan trọng hơn số lượng khung hình bị giật trong việc xác định lùi hệ thống hay cải thiện hệ thống.

Thiết lập ứng dụng để phân tích hiệu suất

Bạn cần thiết lập hệ thống đúng cách để có được các điểm chuẩn chính xác, hiện hành và có thể lặp lại từ một ứng dụng. Thử nghiệm trên một hệ thống gần với quá trình sản xuất nhất có thể, đồng thời ngăn chặn các nguồn gây nhiễu. Trong các phần sau, bạn có thể thực hiện một số bước cho APK và theo hệ thống cụ thể để chuẩn bị thiết lập thử nghiệm, trong đó có thể thiết lập một số bước cụ thể theo từng trường hợp.

Các điểm theo dõi

Các ứng dụng có thể đo lường mã bằng các sự kiện theo dõi tùy chỉnh.

Mặc dù đang thu thập dấu vết hệ thống, nhưng tính năng theo dõi có mức hao tổn nhỏ (khoảng 5μ) mỗi phần, vì vậy, đừng sử dụng tính năng này cho mọi phương pháp. Chỉ cần theo dõi các phần công việc lớn hơn (>0,1 mili giây) là bạn có thể hiểu rõ hơn về vấn đề trọng điểm.

Những điểm cần lưu ý về APK

Thận trọng: Không đo lường hiệu suất trên bản dựng gỡ lỗi.

Các biến thể gỡ lỗi có thể hữu ích cho việc khắc phục sự cố và thay thế các biểu tượng mẫu ngăn xếp. Tuy nhiên, các biến thể đó có ảnh hưởng phi tuyến tính nghiêm trọng đến hiệu suất. Các thiết bị chạy Android 10 (API cấp 29) trở lên có thể sử dụng profileable android:shell="true" trong tệp kê khai để cho phép lập hồ sơ trong các bản phát hành.

Sử dụng cấu hình rút gọn mã ở cấp sản xuất. Tùy theo tài nguyên được sử dụng cho ứng dụng của bạn, việc này có thể ảnh hưởng đáng kể đến hiệu suất. Lưu ý rằng một số cấu hình ProGuard xóa các dấu vết, vì vậy, hãy cân nhắc xóa các quy tắc đó cho cấu hình mà bạn đang chạy thử nghiệm.

Biên dịch

Biên dịch ứng dụng của bạn trên thiết bị sang một trạng thái đã biết (thường là tốc độ hoặc hồ sơ tốc độ). Hoạt động JIT trong nền có thể gây ra mức hao tổn hiệu suất đáng kể và bạn sẽ thường xuyên đạt đến mức này nếu đang cài đặt lại APK giữa các lần chạy thử nghiệm. Lệnh thực hiện hoạt động này là:

adb shell cmd package compile -m speed -f com.google.packagename

Chế độ biên dịch 'tốc độ' sẽ biên dịch ứng dụng hoàn toàn; chế độ 'lập hồ sơ tốc độ' sẽ biên dịch ứng dụng theo hồ sơ của các đường dẫn mã đã sử dụng được thu thập trong quá trình sử dụng ứng dụng. Bạn có thể gặp khó khăn khi thu thập hồ sơ một cách chính xác và nhất quán. Vì vậy, nếu quyết định sử dụng hồ sơ, hãy xác nhận rằng các hồ sơ đó đang thu thập những dữ liệu bạn muốn. Các hồ sơ nằm ở đây:

/data/misc/profiles/ref/[package-name]/primary.prof

Hãy lưu ý rằng Macrobenchmark cho phép bạn trực tiếp chỉ định chế độ biên dịch.

Những điểm cần lưu ý về hệ thống

Để đo lường độ chân thực cao và thấp, hãy điều chỉnh thiết bị của bạn. Chạy so sánh A/B trên cùng một thiết bị và cùng một phiên bản hệ điều hành. Hiệu suất có thể thay đổi đáng kể, thậm chí ngay trên cùng một loại thiết bị.

Trên các thiết bị đã bị can thiệp hệ thống, hãy cân nhắc sử dụng tập lệnh lockClocks cho các điểm chuẩn nhỏ. Ngoài ra, các tập lệnh này thực hiện những việc sau:

  • Đặt CPU ở tần suất cố định,
  • Vô hiệu hóa các lõi nhỏ định cấu hình GPU.
  • Tắt chế độ điều tiết nhiệt.

Bạn không nên sử dụng tùy chọn này cho các thử nghiệm tập trung vào trải nghiệm người dùng (chẳng hạn như khởi chạy ứng dụng, thử nghiệm DoU và thử nghiệm độ giật) nhưng tùy chọn này có thể là yếu tố cần thiết để giảm nhiễu trong các thử nghiệm điểm chuẩn nhỏ.

Nếu có thể, hãy cân nhắc sử dụng một khung thử nghiệm như Macrobenchmark để có thể giảm nhiễu trong kết quả đo lường và ngăn chặn quá trình đo lường không chính xác.

Khởi động ứng dụng chậm: hoạt động đàn hồi không cần thiết

Một hoạt động đàn hồi có thể kéo dài thời gian khởi động ứng dụng theo cách không cần thiết, và điều quan trọng là bạn phải biết ứng dụng của mình có đang chạy như vậy hay không. Như bạn có thể thấy trong dấu vết mẫu sau đây, một activityStart xuất hiện, ngay sau đó là một activityStart khác mà không có khung hình nào được vẽ sau hoạt động đầu tiên.

alt_text

Hiện tượng này có thể xảy ra ở cả điểm truy cập thông báo và điểm truy cập khởi động ứng dụng thông thường và thường có thể được giải quyết bằng cách tái cấu trúc. Ví dụ: nếu bạn đang sử dụng hoạt động đó để thực hiện thiết lập trước khi chạy một hoạt động khác thì yếu tố này sẽ mã hóa thành phần hoặc thư viện có thể tái sử dụng.

Các trường hợp phân bổ không cần thiết sẽ kích hoạt GC thường xuyên

Bạn có thể nhận thấy rằng việc thu gom rác (GC) đang diễn ra thường xuyên hơn so với dự kiến của bạn trong một hoạt động truy vết hệ thống.

Trong trường hợp này, một chỉ báo sẽ xuất hiện sau mỗi 10 giây trong một hoạt động dài cho biết ứng dụng của bạn có thể đang phân bổ một cách không cần thiết nhưng nhất quán theo thời gian:

alt_text

Hoặc bạn có thể nhận thấy rằng ngăn xếp lệnh gọi cụ thể đang chiếm phần lớn các vị trí phân bổ khi sử dụng Trình phân tích bộ nhớ. Bạn không cần phải buộc loại bỏ tất cả tùy chọn phân bổ, vì điều này có thể khiến mã khó duy trì hơn. Hãy bắt đầu bằng cách xử lý các điểm phân bổ.

Khung hình bị giật

Quy trình đồ họa tương đối phức tạp và có thể xảy ra một số vấn đề liên quan đến việc xác định xem có phải người dùng đã thấy một số khung hình bị bỏ qua hay không. Trong một số trường hợp, nền tảng đó có thể "giải cứu" khung bằng cách sử dụng bộ đệm. Tuy nhiên, bạn có thể bỏ qua hầu hết các vấn đề đó để dễ dàng xác định khung hình có vấn đề từ chính ứng dụng của bạn.

Khi khung hình đang được vẽ và khối lượng công việc đòi hỏi từ ứng dụng không nhiều, điểm truy vết Choreographer.doFrame() xảy ra trên nhịp 16,7ms (giả sử thiết bị 60 khung hình/giây):

alt_text

Nếu bạn thu nhỏ và điều hướng xem qua dấu vết, đôi khi bạn sẽ thấy một số khung hình mất nhiều thời gian hơn để hoàn thành, nhưng vẫn có thể chấp nhận được vì không quá thời gian 16,7 mili giây đã được phân bổ:

alt_text

Khi bạn nhận thấy thực sự có gián đoạn ở nhịp độ định kỳ thì đó là hiện tượng khung hình bị giật:

alt_text

Bạn chỉ cần thực hành một chút và sẽ có thể dễ dàng nhìn thấy những dấu vết này.

alt_text

Trong một số trường hợp, bạn sẽ cần phóng to điểm theo dõi đó để tìm hiểu thêm thông tin về chế độ xem nào đang được tăng cường hoặc về hoạt động của RecyclerView. Trong các trường hợp khác, bạn có thể cần phải kiểm tra thêm.

Để biết thêm thông tin về cách xác định khung hình bị giật và cách gỡ lỗi, hãy xem phần Kết xuất chậm.

Các lỗi RecyclerView thường gặp

  • Vô hiệu hóa toàn bộ dữ liệu sao lưu của RecyclerView một cách không cần thiết. Điều này có thể dẫn đến thời gian kết xuất khung hình lâu và do đó, video bị giật. Thay vào đó, bạn chỉ nên vô hiệu hóa dữ liệu đã thay đổi để giảm thiểu số lượt xem cần cập nhật.
    • Hãy xem bài viết Trình bày dữ liệu động để biết các cách tránh các lệnh gọi notifyDatasetChanged() tốn kém. Các lệnh này sẽ cập nhật nội dung thay vì thay thế hoàn toàn nội dung.
  • Việc không hỗ trợ các RecyclerView lồng ghép phù hợp sẽ khiến nội dung trên RecyclerView được tạo lại hoàn toàn mỗi lần.
    • Mỗi RecyclerView nội bộ lồng nhau phải có một RecycledViewPool được thiết lập để đảm bảo có thể sử dụng lại các chế độ xem giữa các RecyclerView nội bộ.
  • Không tìm nạp trước đủ dữ liệu hoặc không tìm nạp trước kịp thời. Khung hình sẽ bị trễ nếu nhanh chóng cuộn đến cuối danh sách cuộn và cần phải đợi thêm dữ liệu từ máy chủ. Mặc dù về mặt kỹ thuật thì đây không phải là "giật", vì không quá thời hạn kết xuất khung hình, nhưng trải nghiệm người dùng sẽ được cải tiến đáng kể nếu bạn có thể sửa đổi thời gian và số lượng tìm nạp trước sao cho người dùng không phải chờ dữ liệu.