API Gợi ý về hiệu suất

Ngày phát hành:

Android 12 (API cấp 31) – API Gợi ý về hiệu suất

Android 13 (API cấp 33) – Trình quản lý gợi ý về hiệu suất trong API NDK

(Xem trước) Android 15 (DP1) – reportActualWorkDuration()

Với các gợi ý về hiệu suất của CPU, trò chơi có thể tác động đến hành vi hiệu suất CPU động để phù hợp hơn với nhu cầu của trò chơi. Trên hầu hết thiết bị, Android tự động điều chỉnh tốc độ xung nhịp CPU và loại nhân cho một khối lượng công việc dựa trên các nhu cầu trước đó. Nếu một mức tải sử dụng nhiều tài nguyên CPU hơn, thì tốc độ xung nhịp sẽ tăng lên và cuối cùng khối lượng công việc sẽ được chuyển sang một lõi lớn hơn. Nếu khối lượng công việc sử dụng ít tài nguyên hơn, thì Android sẽ giảm mức phân bổ tài nguyên. Với ADPF, ứng dụng hoặc trò chơi có thể gửi thêm một tín hiệu về hiệu suất và thời hạn. Điều này giúp hệ thống tăng tốc mạnh mẽ hơn (cải thiện hiệu suất) và giảm xung nhịp nhanh chóng khi hoàn tất khối lượng công việc (tiết kiệm mức sử dụng điện năng).

Tốc độ xung nhịp

Khi các thiết bị Android tự động điều chỉnh tốc độ xung nhịp CPU, tần suất có thể thay đổi hiệu suất của mã. Việc thiết kế mã xử lý tốc độ xung nhịp động là rất quan trọng để tối đa hoá hiệu suất, duy trì trạng thái nhiệt an toàn và sử dụng nguồn điện một cách hiệu quả. Bạn không thể trực tiếp chỉ định tần số của CPU trong mã ứng dụng. Do đó, một cách phổ biến để các ứng dụng cố gắng chạy ở tốc độ xung nhịp CPU cao hơn là chạy một vòng lặp bận trong luồng ở chế độ nền, vì vậy, khối lượng công việc có vẻ đòi hỏi nhiều hơn. Đây là một phương pháp không hợp lệ vì nó làm lãng phí điện năng và làm tăng nhiệt tải trên thiết bị khi ứng dụng không thực sự sử dụng tài nguyên bổ sung. API PerformanceHint của CPU được thiết kế để giải quyết vấn đề này. Bằng cách cho hệ thống biết thời lượng công việc thực tế và thời lượng công việc mục tiêu, Android sẽ có thể nắm được thông tin tổng quan về nhu cầu CPU của ứng dụng và phân bổ tài nguyên một cách hiệu quả. Điều này sẽ mang lại hiệu suất tối ưu ở mức tiêu thụ điện năng hiệu quả.

Các loại nhân

Một yếu tố hiệu suất quan trọng khác là: loại nhân CPU mà trò chơi chạy trên đó. Các thiết bị Android thường thay đổi nhân CPU được chỉ định cho một luồng động dựa trên hành vi của mức tải gần đây. Việc chỉ định nhân CPU thậm chí còn phức tạp hơn trên các hệ thống SoC có nhiều loại nhân. Trên một số thiết bị, bạn chỉ có thể sử dụng các nhân xử lý lớn hơn trong thời gian ngắn trước khi thiết bị rơi vào trạng thái không ổn định về nhiệt.

Trò chơi của bạn không nên tìm cách chỉ định luồng công việc (CPU core affinity) cho các nhân CPU vì những lý do sau:

  • Loại nhân phù hợp nhất cho một mức tải cụ thể còn tuỳ theo kiểu thiết bị.
  • Độ ổn định khi chạy trên các nhân lớn hơn còn tuỳ theo hệ thống SoC và các giải pháp nhiệt do từng mẫu thiết bị cung cấp.
  • Tác động của yếu tố môi trường đối với trạng thái nhiệt có thể khiến việc chọn nhân trở nên phức tạp hơn. Ví dụ: thời tiết hoặc ốp lưng điện thoại có thể thay đổi trạng thái nhiệt của thiết bị.
  • Việc chọn nhân xử lý không thể đáp ứng các thiết bị mới với hiệu suất và năng lực xử lý nhiệt cao hơn. Do đó, các thiết bị thường bỏ qua việc chỉ định đối tượng tương đồng cho nhân xử lý của trò chơi.

Ví dụ về hành vi mặc định của trình lập lịch biểu Linux

Hành vi của trình lập lịch biểu Linux
Hình 1. Bộ điều chỉnh có thể mất khoảng 200 mili giây để tăng hoặc giảm tần số của CPU. ADPF kết hợp với hệ thống Dynamic Điện áp và Điều chỉnh tần số (DVFS) để mang lại hiệu suất tốt nhất trên mỗi watt

PerformanceHint API tóm tắt nhiều hơn độ trễ DVFS

Bản tóm tắt ADPF còn lớn hơn độ trễ của DVFS
Hình 2. ADPF biết cách đưa ra quyết định đúng đắn nhất thay mặt bạn
  • Nếu các tác vụ cần chạy trên một CPU cụ thể, thì API PerformanceHint sẽ biết cách thay mặt bạn đưa ra quyết định đó.
  • Do đó, bạn không cần phải sử dụng đối tượng chung sở thích.
  • Thiết bị có nhiều cấu trúc liên kết khác nhau; các đặc tính nguồn điện và nhiệt quá khác nhau nên nhà phát triển ứng dụng có thể không nhìn thấy được.
  • Bạn không được đưa ra bất kỳ giả định nào về hệ thống cơ bản mà bạn đang chạy.

Giải pháp

ADPF cung cấp lớp PerformanceHintManager để các trò chơi có thể gửi gợi ý hiệu suất đến Android về loại nhân và tốc độ xung nhịp CPU. Sau đó, hệ điều hành có thể quyết định cách sử dụng các gợi ý đó một cách tối ưu dựa trên hệ thống SoC và giải pháp tản nhiệt của thiết bị. Nếu kết hợp API này với tính năng theo dõi trạng thái nhiệt, ứng dụng có thể đưa ra các gợi ý phù hợp hơn cho hệ điều hành thay vì sử dụng các vòng lặp liên tục cũng như kỹ thuật lập trình khác có thể gây ra tình trạng điều tiết.

Sau đây là cách trò chơi sử dụng gợi ý về hiệu suất:

  1. Tạo phiên gợi ý cho các luồng chính hoạt động tương tự. Ví dụ:
  2. Trò chơi nên thực hiện việc này sớm, ít nhất 2 mili giây và tốt nhất là trên 4 mili giây trước khi phiên cần tăng tài nguyên hệ thống.
  3. Trong mỗi phiên gợi ý, hãy dự đoán thời lượng cần thiết mà mỗi phiên sẽ chạy. Thông thường, thời lượng này tương đương với một khoảng thời gian của một khung hình, nhưng ứng dụng có thể sử dụng một khoảng thời gian ngắn hơn nếu khối lượng công việc không thay đổi đáng kể giữa các khung hình.

Sau đây là cách áp dụng lý thuyết vào thực tế:

Khởi chạy PerformanceHintManager và createHintSession

Yêu cầu trình quản lý bằng cách sử dụng dịch vụ hệ thống và tạo một phiên gợi ý cho luồng hoặc nhóm luồng của bạn hoạt động trên cùng một khối lượng công việc.

C++

int32_t tids[1];
tids[0] = gettid();
int64_t target_fps_nanos = getFpsNanos();
APerformanceHintManager* hint_manager = APerformanceHint_getManager();
APerformanceHintSession* hint_session =
  APerformanceHint_createSession(hint_manager, tids, 1, target_fps_nanos);

Java

int[] tids = {
  android.os.Process.myTid()
};
long targetFpsNanos = getFpsNanos();
PerformanceHintManager performanceHintManager =
  (PerformanceHintManager) this.getSystemService(Context.PERFORMANCE_HINT_SERVICE);
PerformanceHintManager.Session hintSession =
  performanceHintManager.createHintSession(tids, targetFpsNanos);

Đặt luồng nếu cần

Ngày phát hành:

Android 11 (API cấp 34)

Sử dụng hàm setThreads của PerformanceHintManager.Session khi bạn có các luồng khác cần được thêm vào sau này. Ví dụ: nếu sau này bạn tạo luồng vật lý và cần thêm luồng đó vào phiên, bạn có thể sử dụng API setThreads này.

C++

auto tids = thread_ids.data();
std::size_t size = thread_ids_.size();
APerformanceHint_setThreads(hint_session, tids, size);

Java

int[] tids = new int[3];

// add all your thread IDs. Remember to use android.os.Process.myTid() as that
// is the linux native thread-id.
// Thread.currentThread().getId() will not work because it is jvm's thread-id.
hintSession.setThreads(tids);

Nếu đang nhắm mục tiêu đến các cấp độ API thấp hơn, bạn sẽ cần huỷ phiên đó và tạo lại một phiên mới mỗi khi cần thay đổi mã nhận dạng luồng.

Báo cáo thời lượng làm việc thực tế

Hãy theo dõi thời lượng thực tế cần thiết để hoàn thành công việc (tính bằng nano giây) và báo cáo cho hệ thống sau khi hoàn thành công việc đó trong mỗi chu kỳ. Ví dụ: nếu đây là luồng kết xuất đồ hoạ, hãy gọi lệnh này trên mọi khung hình.

Để có thời gian thực tế một cách chính xác, hãy sử dụng:

C++

clock_gettime(CLOCK_MONOTONIC, &clock); // if you prefer "C" way from <time.h>
// or
std::chrono::high_resolution_clock::now(); // if you prefer "C++" way from <chrono>

Java

System.nanoTime();

Ví dụ:

C++

// All timings should be from `std::chrono::steady_clock` or `clock_gettime(CLOCK_MONOTONIC, ...)`
auto start_time = std::chrono::high_resolution_clock::now();

// do work

auto end_time = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time).count();
int64_t actual_duration = static_cast<int64_t>(duration);

APerformanceHint_reportActualWorkDuration(hint_session, actual_duration);

Java

long startTime = System.nanoTime();

// do work

long endTime = System.nanoTime();
long duration = endTime - startTime;

hintSession.reportActualWorkDuration(duration);

Cập nhật Thời lượng công việc mục tiêu khi cần

Bất cứ khi nào thời lượng công việc mục tiêu của bạn thay đổi, chẳng hạn như nếu người chơi chọn một khung hình/giây mục tiêu khác, hãy gọi phương thức updateTargetWorkDuration để cho hệ thống biết sao cho hệ điều hành có thể điều chỉnh tài nguyên theo mục tiêu mới. Bạn không phải gọi phương thức này trên mọi khung hình và chỉ cần gọi khi thời lượng mục tiêu thay đổi.

C++

APerformanceHint_updateTargetWorkDuration(hint_session, target_duration);

Java

hintSession.updateTargetWorkDuration(targetDuration);