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

Tài liệu 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ố vấn đề thường gặp sau đây trong ứng dụng của mình:

Giật trong khi cuộn

Giật là thuật ngữ mô tả tình trạng hình ảnh bị giật khi hệ thống không thể xây dựng và cung cấp các khung hình kịp thời để vẽ chúng lên màn hình ở tần số 60 Hz trở lên. Hiện tượng giật thể hiện rõ nhất lúc cuộn, khi luồng ảnh động không chạy trơn tru mà lại có tình trạng giật. Hiện tượng giật xuất hiện khi chuyển động tạm dừng trong một hoặc nhiều khung hình, vì ứ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 đến tốc độ làm mới 90 Hz. Tốc độ kết xuất thông thường là 60 Hz, nhưng nhiều thiết bị mới hoạt động ở chế độ 90 Hz trong các hoạt động tương tác của người dùng, chẳng hạn như cuộn. Một số thiết bị hỗ trợ tốc độ thậm chí còn cao hơn, lên tới 120 Hz.

Để xem tốc độ làm mới mà một 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 sử dụng Tuỳ chọn cho nhà phát triển > Hiện tốc độ làm mới trong phần 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 người dùng nhấn vào biểu tượng ứng dụng, thông báo hoặc điểm vào khác đến khi dữ liệu của người dùng hiện trên màn hình.

Hãy cố gắng đạt được các mục tiêu khởi động sau trong ứng dụng của bạn:

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

    Ngược lại, quá trình khởi động ấm xảy ra khi ứng dụng đã chạy trong nền. Quá trình khởi động nguội yêu cầu hệ thống phải xử lý nhiều việc nhất, vì hệ thống phải tải mọi thứ từ bộ nhớ và khởi động ứng dụng. Hãy cố gắng làm cho thời gian khởi động nguội chỉ mất tối đa 500 mili giây.

  • Các độ trễ P95 và P99 rất gần với độ trễ trung bình. Khi mất quá nhiều thời gian để khởi động, ứng dụng sẽ mang lại trải nghiệm khó chịu cho người dùng. Hoạt động giao tiếp liên quy trình (IPC) và I/O không cần thiết trong quá trình quan trọng khi khởi động ứng dụng có thể gặp phải tình trạng cạnh tranh khoá và dẫn đến sự không nhất quán.

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

Vấn đề này rất dễ thấy trong các hoạt động tương tác 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ượt mà và không có độ trễ hoặc hình ảnh nhấp nháy.

Sử dụng pin 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 khiế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 (ART), 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, đặc biệt là với các đối tượng tạm thời. Mặc dù trước đây phương pháp hay nhất là tránh phân bổ các đối tượng bất cứ khi nào có thể, nhưng bạn nên làm điều phù hợp nhất với ứng dụng và kiến trúc của mình. Dựa trên khả năng của ART, phương pháp hay nhất là tiết kiệm được các mức phân bổ trước nguy cơ mã không thể duy trì.

Tuy nhiên, việc này đòi hỏi nhiều công sức, vì vậy, hãy lưu ý rằng nó có thể ảnh hưởng đến hiệu suất nếu bạn đang phân bổ nhiều đối tượng trong vòng lặp nội bộ.

Xác định vấn đề

Chúng tôi đề xuất quy trình công việc sau đây để xác định và khắc phục các vấn đề về hiệu suất:

  1. Xác định và kiểm tra các hành trình trọng yếu sau đây của người dùng:
    • 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 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.
  2. Kiểm tra xem điều gì đang xảy ra trong các quy trình trước đó bằng các công cụ gỡ lỗi sau:
    • Perfetto: cho phép bạn xem điều gì đang diễn ra trên toàn bộ thiết bị nhờ dữ liệu thời gian chính xác.
    • Trình phân tích bộ nhớ: cho phép bạn xem quá trình phân bổ bộ nhớ đang diễn ra trên vùng nhớ khối xếp.
    • Simpleperf: hiển thị biểu đồ về các lệnh gọi hàm đang sử dụng 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.

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

  • Quy trình khởi động
  • Giật
    • Chỉ số trường
      • Các chỉ số khung của Play Console: trong Play Console, bạn 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. Loại chỉ số này chỉ báo cáo tình trạng giật tổng thể trong toàn bộ ứng dụng.
      • Đo lường tuỳ 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ể.
    • Kiểm thử trong phòng thí nghiệm
      • Cuộn bằng Macrobenchmark.
      • 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 của người dùng. Đây là một cách để tìm hiểu về sự thay đổi độ giật trong một hành trình cụ thể của người dùng. Chỉ số RenderTime (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 sự hồi quy hay cải thiện.

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

Bạn cần phải thiết lập đúng cách để có được các điểm chuẩn chính xác, có thể lặp lại và hữu ích từ một ứng dụng. Hãy kiểm thử trên một hệ thống gần với bản phát hành công khai nhất có thể, đồng thời loại bỏ 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 cụ thể tuỳ theo APK và hệ thống để chuẩn bị thiết lập việc kiểm thử, trong đó một số bước có thể thay đổi tuỳ từng trường hợp sử dụng.

Các điểm theo dõi

Các ứng dụng có thể đo lường mã của chúng bằng các sự kiện theo dõi tuỳ chỉnh.

Mặc dù đang thu thập dấu vết, 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 thức. Việc theo dõi các phần công việc lớn hơn 0,1 mili giây có thể cung cấp thông tin chi tiết đáng kể về các điểm tắc nghẽn.

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

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 phát hành công khai. Tuỳ theo tài nguyên 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 xoá các dấu vết, vì vậy, hãy cân nhắc xoá các quy tắc đó cho cấu hình mà bạn đang chạy chương trình kiểm thử.

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à speed hoặc speed-profile. Hoạt động đúng thời điểm (JIT) ở chế độ nền có thể có mức hao tổn hiệu suất đáng kể. 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 chương trình kiểm thử. Sau đây là lệnh để thực hiện việc này:

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

Chế độ biên dịch speed sẽ biên dịch ứng dụng hoàn toàn. Chế độ speed-profile 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. Có thể bạn sẽ khó thu thập được 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ày nằm ở vị trí sau:

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

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 quy trình 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 Microbenchmark. 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 hoá các lõi nhỏ và đị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ập lệnh lockClocks cho các hoạt động kiểm thử 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, kiểm thử DoU và kiểm thử độ giật. Tuy nhiên, tập lệnh này có thể cần thiết để giảm độ nhiễu trong các hoạt động kiểm thử Microbenchmark.

Nếu có thể, hãy cân nhắc sử dụng một khung kiểm thử như Macrobenchmark để có thể giảm độ nhiễu trong kết quả đo lường và ngăn tình trạng đ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. Đ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ư minh hoạ trong dấu vết mẫu sau, một activityStart đứng ngay trước một activityStart khác mà không có bất kỳ khung nào do hoạt động đầu tiên vẽ.

alt_text

Hình 1. Dấu vết cho thấy hoạt động đàn hồi.

Điều 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. Bạn thường có thể giải quyết vấn đề này bằng cách tái cấu trúc. Ví dụ: nếu bạn đang sử dụng hoạt động này để thực hiện việc thiết lập trước khi chạy một hoạt động khác, hãy đưa mã này vào một 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ể thấy hoạt động thu gom rác (GC) diễn ra thường xuyên hơn dự kiến trong Systrace.

Trong ví dụ sau, cứ 10 giây trong một hoạt động kéo dài, sẽ có một chỉ báo xuất hiện cho biết ứng dụng 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

Hình 2. Dấu vết cho thấy có khoảng cách giữa các sự kiện GC.

Bạn cũng có thể nhận thấy rằng một ngăn xếp lệnh gọi cụ thể đang thực hiện phần lớn quá trình 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ả tuỳ chọn phân bổ, vì điều này có thể khiến mã khó duy trì hơn. Thay vào đó, 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 đồ hoạ tương đối phức tạp và có thể xảy ra một số vấn đề trong việc xác định xem liệu người dùng có thấy 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 hình 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 theo tần suất 16,7 mili giây trên thiết bị 60 khung hình/giây:

alt_text

Hình 3. Một dấu vết cho thấy các khung hình nhanh thường xuyên xuất hiện.

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

Hình 4. Dấu vết cho thấy các khung hình nhanh, thường xuyên xảy ra cụm công việc định kỳ.

Khi bạn thấy có sự gián đoạn ở tần suất thông thường này, thì đó là hiện tượng khung hình bị giật như minh hoạ trong Hình 5:

alt_text

Hình 5. Dấu vết cho thấy một khung hình bị giật.

Bạn có thể thực hành xác định những khung hình này.

alt_text

Hình 6. Một dấu vết cho thấy nhiều khung hình bị giật hơn.

Trong một số trường hợp, bạn cần phóng to một điểm theo dõi để biết thêm thông tin về khung hiển thị nào đang được tăng cường hoặc 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 thường gặp về RecyclerView

Việc vô hiệu hoá toàn bộ dữ liệu sao lưu của RecyclerView một cách không cần thiết có thể dẫn đến thời gian kết xuất khung hình dài và hiện tượng giật. Để giảm thiểu số lượng khung hiển thị cần cập nhật, bạn chỉ nên vô hiệu hoá những dữ liệu thay đổi.

Hãy xem phần 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 vì những lệnh này khiến nội dung cập nhật thay vì thay thế hoàn toàn nội dung.

Nếu bạn không hỗ trợ đúng cách cho mọi RecyclerView được lồng, thì việc này có thể khiến RecyclerView nội bộ luôn được tạo lại hoàn toàn. Mọi RecyclerView bên trong được lồng phải có một RecycledViewPool được thiết lập để giúp đảm bảo có thể sử dụng lại các khung hiển thị giữa mọi RecyclerView bên trong.

Việc 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 có thể gây khó chịu trong quá trình người dùng truy cập đến cuối danh sách cuộn, vì người dùng 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à hiện tượng giật vì không quá thời hạn kết xuất khung hình, nhưng bạn có thể cải thiện đáng kể trải nghiệm người dùng bằng cách sửa đổi thời gian và số lượng tìm nạp trước để người dùng không phải chờ dữ liệu của chúng tôi.