Định cấu hình tính năng theo dõi hệ thống

Bạn có thể định cấu hình tính năng theo dõi hệ thống để chụp hồ sơ CPU và luồng của ứng dụng trong một khoảng thời gian ngắn. Sau đó, bạn có thể sử dụng báo cáo kết quả của tính năng theo dõi hệ thống để cải thiện hiệu suất của trò chơi.

Thiết lập tính năng theo dõi hệ thống dựa trên trò chơi

Công cụ Systrace được cung cấp theo 2 cách:

Systrace là công cụ cấp chi tiết:

  • Cung cấp thông tin thực tế. Systrace thu thập dữ liệu đầu ra trực tiếp từ hạt nhân, do đó chỉ số mà công cụ này thu thập gần giống với chỉ số mà loạt lệnh gọi hệ thống sẽ báo cáo.
  • Tiêu thụ ít tài nguyên. Systrace gây ra mức hao tổn rất thấp trên thiết bị, thường nhỏ hơn 1%, vì công cụ này truyền dữ liệu vào bộ đệm trong bộ nhớ.

Chế độ cài đặt tối ưu

Bạn cần cung cấp cho công cụ này một bộ đối số hợp lý:

  • Danh mục: Bộ danh mục phù hợp nhất để bật tính năng theo dõi hệ thống dựa trên trò chơi là: {sched, freq, idle, am, wm, gfx, view, sync, binder_driver, hal, dalvik}.
  • Dung lượng bộ nhớ đệm: Quy tắc chung là dung lượng bộ nhớ đệm 10 MB trên mỗi lõi CPU cho phép theo dõi dấu vết dài khoảng 20 giây. Ví dụ: Nếu một thiết bị có 2 CPU 4 nhân (tổng cộng 8 nhân), giá trị thích hợp để truyền vào chương trình systrace là 80.000 KB (80 MB).

    Nếu trò chơi đòi hỏi chuyển đổi ngữ cảnh nhiều, hãy tăng bộ đệm lên 15 MB cho mỗi lõi CPU.

  • Sự kiện tuỳ chỉnh: Nếu bạn xác định sự kiện tuỳ chỉnh để thu thập trong trò chơi, hãy bật cờ -a để cho phép Systrace đưa các sự kiện tuỳ chỉnh này vào báo cáo kết quả.

Nếu bạn đang sử dụng chương trình dòng lệnh systrace, hãy sử dụng lệnh sau để thu thập dấu vết hệ thống có áp dụng các phương pháp hay nhất cho bộ danh mục, dung lượng bộ nhớ đệm và sự kiện tuỳ chỉnh:

python systrace.py -a com.example.myapp -b 80000 -o my_systrace_report.html \
  sched freq idle am wm gfx view sync binder_driver hal dalvik

Nếu bạn đang sử dụng ứng dụng hệ thống Systrace trên một thiết bị, hãy hoàn thành các bước sau để thu thập dấu vết hệ thống có áp dụng các phương pháp hay nhất cho bộ danh mục, dung lượng bộ nhớ đệm và sự kiện tuỳ chỉnh:

  1. Bật tuỳ chọn Trace debuggable applications (Truy vết các ứng dụng có thể gỡ lỗi).

    Để sử dụng chế độ cài đặt này, thiết bị phải còn trống 256 MB hoặc 512 MB (tuỳ thuộc vào việc CPU có 4 hay 8 lõi) và mỗi 64 MB của bộ nhớ còn trống phải tạo thành một phân đoạn liền kề.

  2. Chọn Categories (Danh mục), rồi bật các danh mục trong danh sách sau:

    • am: Trình quản lý hoạt động
    • binder_driver: Trình điều khiển hạt nhân Binder
    • dalvik: Máy ảo Dalvik
    • freq: Tần suất của CPU
    • gfx: Đồ hoạ
    • hal: Mô-đun phần cứng
    • idle: CPU rảnh
    • sched: Lập lịch CPU
    • sync: Đồng bộ hoá
    • view: Hệ thống chế độ xem
    • wm: Trình quản lý cửa sổ
  3. Bật tính năng Record tracing (Ghi lại dấu vết).

  4. Tải trò chơi.

  5. Tương tác trong trò chơi theo cách chơi mà bạn muốn đo lường hiệu suất thiết bị.

  6. Ngay sau khi bạn gặp hành vi không mong muốn trong trò chơi, hãy tắt tính năng theo dõi hệ thống.

Bạn đã thu thập được số liệu thống kê cần thiết về hiệu suất để phân tích thêm về vấn đề này.

Để tiết kiệm dung lượng ổ đĩa, hoạt động theo dõi hệ thống trên thiết bị sẽ lưu tệp ở định dạng dấu vết nén (*.ctrace). Để giải nén tệp này khi tạo báo cáo, hãy sử dụng chương trình dòng lệnh và bao gồm tuỳ chọn --from-file:

python systrace.py --from-file=/data/local/traces/my_game_trace.ctrace \
  -o my_systrace_report.html

Cải thiện các lĩnh vực hiệu suất cụ thể

Mục này nêu bật một số mối lo ngại thường gặp về hiệu suất trong trò chơi dành cho thiết bị di động và mô tả cách xác định cũng như cải thiện các khía cạnh này của trò chơi.

Tốc độ tải

Người chơi muốn nhập cuộc chơi nhanh nhất có thể, vì vậy, bạn cần giảm thời gian tải của trò chơi nhiều nhất có thể. Các biện pháp sau thường giúp giảm thời gian tải:

  • Tải từng phần. Nếu bạn sử dụng cùng tài sản cho các cấp độ hoặc cảnh liên tiếp trong trò chơi, hãy chỉ tải các tài sản này một lần.
  • Giảm dung lượng của tài sản. Bằng cách đó, bạn có thể nhóm các phiên bản không nén của các tài sản này với APK của trò chơi.
  • Sử dụng phương thức nén tiết kiệm dung lượng ổ đĩa. Ví dụ về phương thức đó là zlib.
  • Dùng IL2CPPthay vì mono. (Chỉ áp dụng nếu bạn đang sử dụng Unity.) IL2CPP mang lại hiệu suất thực thi tốt hơn cho các tập lệnh C#.
  • Đặt trò chơi ở chế độ đa luồng. Để biết thêm thông tin chi tiết, hãy xem phần tính nhất quán về tốc độ khung hình.

Tính nhất quán về tốc độ khung hình

Một trong những yếu tố quan trọng nhất của trải nghiệm chơi trò chơi là đạt được tốc độ khung hình nhất quán. Để đạt được mục tiêu này một cách dễ dàng hơn, hãy làm theo các kỹ thuật tối ưu hoá được nhắc đến trong mục này.

Đa luồng

Khi phát triển cho nhiều nền tảng, ta thường đặt tất cả hoạt động trong trò chơi vào một luồng duy nhất. Dù là cách thực thi đơn giản trong nhiều công cụ phát triển trò chơi, song đây không phải phương thức tối ưu khi chạy trên các thiết bị Android. Do đó, trò chơi đơn luồng thường tải chậm và không có tốc độ khung hình nhất quán.

Systrace trong Hình 1 hiển thị hành vi điển hình của trò chơi chỉ chạy trên một CPU tại một thời điểm:

Sơ đồ luồng trong dấu vết hệ thống

Hình 1. Báo cáo Systrace cho trò chơi đơn luồng

Để cải thiện hiệu suất của trò chơi, hãy đặt trò chơi ở chế độ đa luồng. Thông thường, mô hình tốt nhất là có 2 luồng:

  • Một luồng trò chơi chứa mô-đun chính của trò chơi và gửi lệnh kết xuất.
  • Một luồng kết xuất nhận lệnh kết xuất và thông dịch thành lệnh đồ hoạ mà GPU của thiết bị có thể sử dụng để hiển thị cảnh.

API Vulkan mở rộng trên mô hình này, dựa trên khả năng đẩy song 2 bộ đệm chung. Bạn có thể sử dụng tính năng này để phân phối nhiều luồng kết xuất trên nhiều CPU, từ đó cải thiện thời gian kết xuất của cảnh.

Bạn cũng có thể thực hiện một số thay đổi cụ thể theo công cụ để nâng cao hiệu suất đa luồng của trò chơi:

  • Nếu bạn đang phát triển trò chơi bằng cách sử dụng công cụ phát triển trò chơi Unity, hãy bật các tuỳ chọn Multithreaded Rendering (Kết xuất đa luồng) và GPU Skinning (Tạo giao diện GPU).
  • Nếu bạn đang sử dụng một công cụ kết xuất tuỳ chỉnh, hãy đảm bảo rằng dòng lệnh kết xuất và dòng lệnh đồ hoạ được căn chỉnh chính xác; nếu không, bạn có thể gây ra độ trễ khi hiển thị cảnh trò chơi.

Sau khi áp dụng các thay đổi này, bạn sẽ thấy trò chơi chiếm đồng thời ít nhất 2 CPU, như trong Hình 2:

Sơ đồ luồng trong dấu vết hệ thống

Hình 2. Báo cáo Systrace cho trò chơi đa luồng

Tải thành phần trên giao diện người dùng

Sơ đồ ngăn xếp khung trong dấu vết hệ thống
Hình 3. Báo cáo Systrace cho trò chơi kết xuất hàng chục thành phần trên giao diện người dùng cùng lúc

Khi tạo một trò chơi có nhiều tính năng, bạn sẽ muốn hiển thị nhiều tuỳ chọn và hành động cho người chơi cùng lúc. Tuy nhiên, để duy trì tốc độ khung hình nhất quán, bạn nên cân nhắc đến kích thước tương đối nhỏ của màn hình di động và giữ cho giao diện người dùng càng đơn giản càng tốt.

Báo cáo Systrace trong Hình 3 là ví dụ về khung giao diện người dùng đang cố gắng kết xuất quá nhiều thành phần so với khả năng của thiết bị di động.

Bạn nên hướng tới việc giảm thời gian cập nhật giao diện người dùng xuống còn 2–3 mili giây. Bạn có thể làm được điều này bằng cách thực hiện các bước tối ưu hoá tương tự như sau:

  • Chỉ cập nhật các thành phần đã di chuyển trên màn hình.
  • Giới hạn số lượng hoạ tiết và lớp giao diện người dùng. Cân nhắc kết hợp các lệnh gọi đồ hoạ, chẳng hạn như chương trình đổ bóng và hoạ tiết sử dụng cùng một vật liệu.
  • Hoãn thao tác động của thành phần cho GPU.
  • Tăng cường lọc các đối tượng nằm ngoài tầm nhìn và bị che khuất.
  • Nếu có thể, thực hiện các thao tác vẽ bằng API Vulkan. Mức hao tổn cho hàm gọi vẽ trên Vulkan là thấp hơn.

Mức tiêu thụ điện năng

Ngay cả sau khi thực hiện các kỹ thuật tối ưu hoá trong mục trước, bạn vẫn có thể thấy tốc độ khung hình của trò chơi giảm trong vòng 45–50 phút đầu tiên khi chơi. Hơn nữa, thiết bị có thể bắt đầu nóng lên và tiêu thụ nhiều pin hơn theo thời gian.

Trong nhiều trường hợp, tổ hợp nhiệt lượng và mức tiêu thụ điện năng không mong muốn này có liên quan đến cách phân phối khối lượng công việc trong trò chơi trên CPU của thiết bị. Để tăng hiệu quả tiêu thụ điện năng của trò chơi, hãy áp dụng các phương pháp hay nhất được trình bày trong các mục sau.

Giữ các luồng tốn bộ nhớ trên một CPU

Trên nhiều thiết bị di động, bộ nhớ đệm L1 nằm trên các CPU cụ thể còn bộ nhớ đệm L2 nằm trên tập hợp CPU dùng chung một đồng hồ. Để tăng tối đa kết quả tìm kiếm trong bộ nhớ đệm L1, tốt nhất bạn nên giữ cho luồng chính và mọi luồng tốn bộ nhớ khác chạy trên một CPU duy nhất.

Hoãn công việc thời lượng ngắn cho CPU công suất thấp

Hầu hết công cụ phát triển trò chơi, bao gồm cả Unity, đều biết cách hoãn thao tác luồng worker lên một CPU khác với luồng chính của trò chơi. Tuy nhiên, công cụ này không nhận biết được cấu trúc cụ thể của thiết bị và không thể dự đoán khối lượng công việc của trò chơi chính xác như bạn.

Hầu hết thiết bị hệ thống trên một chip đều có ít nhất 2 đồng hồ chung, một dành cho CPU nhanh và một dành cho CPU chậm của thiết bị. Hệ quả của cấu trúc này là nếu một CPU nhanh cần hoạt động ở tốc độ tối đa, tất cả CPU nhanh khác cũng hoạt động ở tốc độ tối đa.

Báo cáo ví dụ được minh hoạ trong Hình 4 cho thấy một trò chơi tận dụng CPU nhanh. Tuy nhiên, mức độ hoạt động cao này tạo ra rất nhiều năng lượng và nhanh chóng sinh nhiệt.

Sơ đồ luồng trong dấu vết hệ thống

Hình 4. Báo cáo của Systrace cho thấy hoạt động chỉ định luồng dưới mức tối ưu đối với CPU của thiết bị

Để giảm mức sử dụng điện năng nói chung, tốt nhất bạn nên đề xuất cho trình lập lịch biểu hoãn các công việc có thời lượng ngắn hơn (chẳng hạn như tải âm thanh, chạy các luồng chạy và thực thi (trình biên đạo) choreographer cho bộ CPU chậm trên thiết bị. Bạn có thể chuyển nhiều công việc này nhất có thể cho CPU chậm trong khi vẫn duy trì tốc độ khung hình mong muốn.

Hầu hết thiết bị đều liệt kê CPU chậm trước CPU nhanh, nhưng bạn không thể cho rằng SOC của thiết bị sử dụng thứ tự này. Để kiểm tra, hãy chạy lệnh tương tự như lệnh hiển thị trong mã khám phá cấu trúc liên kết CPU này trên GitHub.

Sau khi biết CPU nào là CPU chậm trên thiết bị, bạn có thể khai báo thông tin về nơi chạy của các luồng thời lượng ngắn mà trình lập lịch biểu của thiết bị sẽ tuân theo. Để làm như vậy, hãy thêm mã sau vào mỗi luồng:

#include <sched.h>
#include <sys/types.h>
#include <unistd.h>

pid_t my_pid; // PID of the process containing your thread.

// Assumes that cpu0, cpu1, cpu2, and cpu3 are the "slow CPUs".
cpu_set_t my_cpu_set;
CPU_ZERO(&my_cpu_set);
CPU_SET(0, &my_cpu_set);
CPU_SET(1, &my_cpu_set);
CPU_SET(2, &my_cpu_set);
CPU_SET(3, &my_cpu_set);
sched_setaffinity(my_pid, sizeof(cpu_set_t), &my_cpu_set);

Ứng suất nhiệt

Khi trở nên quá nóng, thiết bị có thể điều tiết CPU và/hoặc GPU, gây ảnh hưởng không mong muốn đến trò chơi. Các trò chơi tích hợp đồ hoạ phức tạp, tác vụ tính toán nặng hoặc hoạt động mạng được duy trì liên tục có nhiều khả năng gặp phải vấn đề hơn.

Sử dụng API thermal để theo dõi sự thay đổi nhiệt độ trên thiết bị và hành động để duy trì mức sử dụng điện năng thấp hơn cũng như nhiệt độ thiết bị mát hơn. Khi thiết bị báo cáo ứng suất nhiệt, hãy dừng các hoạt động đang diễn ra để giảm mức sử dụng năng lượng. Ví dụ: giảm tốc độ khung hình hoặc lưới đa giác.

Trước tiên, hãy khai báo đối tượng PowerManager và khởi động đối tượng đó trong phương thức onCreate(). Thêm một trình nghe trạng thái nhiệt cho đối tượng.

Kotlin

class MainActivity : AppCompatActivity() {
    lateinit var powerManager: PowerManager

    override fun onCreate(savedInstanceState: Bundle?) {
        powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
        powerManager.addThermalStatusListener(thermalListener)
    }
}

Java

public class MainActivity extends AppCompatActivity {
    PowerManager powerManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
        powerManager.addThermalStatusListener(thermalListener);
    }
}

Xác định các hành động cần thực hiện khi trình nghe phát hiện một thay đổi trạng thái. Nếu trò chơi sử dụng C/C++, hãy thêm mã vào các mức trạng thái nhiệt trong onThermalStatusChanged() để gọi vào mã trò chơi gốc của bạn bằng JNI hoặc sử dụng API Thermal gốc.

Kotlin

val thermalListener = object : PowerManager.OnThermalStatusChangedListener() {
    override fun onThermalStatusChanged(status: Int) {
        when (status) {
            PowerManager.THERMAL_STATUS_NONE -> {
                // No thermal status, so no action necessary
            }

            PowerManager.THERMAL_STATUS_LIGHT -> {
                // Add code to handle light thermal increase
            }

            PowerManager.THERMAL_STATUS_MODERATE -> {
                // Add code to handle moderate thermal increase
            }

            PowerManager.THERMAL_STATUS_SEVERE -> {
                // Add code to handle severe thermal increase
            }

            PowerManager.THERMAL_STATUS_CRITICAL -> {
                // Add code to handle critical thermal increase
            }

            PowerManager.THERMAL_STATUS_EMERGENCY -> {
                // Add code to handle emergency thermal increase
            }

            PowerManager.THERMAL_STATUS_SHUTDOWN -> {
                // Add code to handle immediate shutdown
            }
        }
    }
}

Java

PowerManager.OnThermalStatusChangedListener thermalListener =
    new PowerManager.OnThermalStatusChangedListener () {

    @Override
    public void onThermalStatusChanged(int status) {

        switch (status)
        {
            case PowerManager.THERMAL_STATUS_NONE:
                // No thermal status, so no action necessary
                break;

            case PowerManager.THERMAL_STATUS_LIGHT:
                // Add code to handle light thermal increase
                break;

            case PowerManager.THERMAL_STATUS_MODERATE:
                // Add code to handle moderate thermal increase
                break;

            case PowerManager.THERMAL_STATUS_SEVERE:
                // Add code to handle severe thermal increase
                break;

            case PowerManager.THERMAL_STATUS_CRITICAL:
                // Add code to handle critical thermal increase
                break;

            case PowerManager.THERMAL_STATUS_EMERGENCY:
                // Add code to handle emergency thermal increase
                break;

            case PowerManager.THERMAL_STATUS_SHUTDOWN:
                // Add code to handle immediate shutdown
                break;
        }
    }
};

Độ trễ từ lúc nhấn đến lúc hiển thị

Các trò chơi kết xuất khung hình nhanh nhất có thể sẽ rơi vào tình huống bị ràng buộc bởi GPU, khi đó bộ đệm khung sẽ bị quá tải. CPU cần phải chờ GPU, gây ra độ trễ đáng kể giữa đầu vào của người chơi và hiệu ứng từ đầu vào trên màn hình.

Để xác định xem bạn có thể cải thiện tốc độ khung hình của trò chơi hay không, hãy hoàn thành các bước sau:

  1. Tạo báo cáo Systrace bao gồm các danh mục gfxinput. Các danh mục này bao gồm các phép đo đặc biệt hữu ích để xác định độ trễ từ lúc nhấn đến lúc hiển thị.
  2. Kiểm tra mục SurfaceView trong báo cáo Systrace. Bộ đệm bị quá tải sẽ khiến số lượng bộ đệm đang chờ xử lý dao động trong khoảng từ 1 đến 2, như được hiển thị trong Hình 5:

    Sơ đồ hàng đợi bộ đệm trong dấu vết hệ thống

    Hình 5. Báo cáo Systrace hiển thị một bộ đệm quá tải, thường xuyên quá đầy nên không thể nhận lệnh vẽ

Để giảm thiểu tính không nhất quán về tốc độ khung hình này, hãy làm những việc được mô tả trong các mục sau:

Tích hợp API Android Frame Pacing vào trò chơi

API Android Frame Pacing giúp bạn thực hiện hoán đổi khung hình và xác định khoảng thời gian hoán đổi để trò chơi duy trì tốc độ khung hình nhất quán hơn.

Giảm độ phân giải của các tài sản không phải giao diện người dùng trong trò chơi

Các màn hình trên thiết bị di động hiện đại chứa nhiều pixel hơn so với lượng trình phát có thể xử lý, vì vậy bạn có thể lấy mẫu xuống mức một mảng 5 hoặc thậm chí 10 pixel đều chứa một màu. Với cấu trúc của hầu hết bộ nhớ đệm hiển thị, tốt nhất bạn chỉ nên giảm độ phân giải theo một chiều kích thước.

Tuy nhiên, bạn không được giảm độ phân giải của các thành phần trên giao diện người dùng của trò chơi. Điều quan trọng là duy trì độ dày của đường trên các thành phần này để duy trì kích thước đích nhấn đủ lớn cho tất cả người chơi.

Độ mượt khi kết xuất

Khi SurfaceFlinger kết nối với bộ đệm hiển thị để hiển thị một cảnh trong trò chơi, hoạt động của CPU sẽ tăng lên trong giây lát. Nếu mức tăng đột biến về hoạt động của CPU này xảy ra không đồng đều, bạn có thể thấy hiện tượng kết xuất gián đoạn trong trò chơi. Sơ đồ trong Hình 6 mô tả lý do tại sao điều này xảy ra:

Sơ đồ khung hình bỏ lỡ cửa sổ Vsync vì các khung đó bắt đầu vẽ quá muộn

Hình 6. Báo cáo Systrace cho thấy cách một khung hình có thể bỏ lỡ Vsync

Nếu một khung hình bắt đầu vẽ quá muộn, dù chỉ một vài mili giây, khung đó có thể bỏ lỡ cửa sổ hiển thị tiếp theo. Sau đó, khung hình phải đợi đến khi Vsync tiếp theo được hiển thị (33 mili giây khi chạy trò chơi ở tốc độ 30 FPS), điều này gây ra độ trễ đáng kể từ góc nhìn của người chơi.

Để giải quyết trường hợp này, hãy sử dụng API Android Frame Pacing. API này luôn hiển thị khung mới trên mặt sóng VSync.

Trạng thái bộ nhớ

Khi bạn chạy trò chơi trong một khoảng thời gian dài, thiết bị có thể gặp lỗi hết bộ nhớ.

Trong trường hợp này, hãy kiểm tra hoạt động của CPU trong báo cáo Systrace và xem tần suất hệ thống thực hiện các lệnh gọi tới trình nền kswapd. Nếu có nhiều lệnh gọi trong quá trình thực thi trò chơi, tốt nhất bạn nên xem xét kỹ hơn cách trò chơi đang quản lý và dọn dẹp bộ nhớ.

Để biết thêm thông tin, hãy xem bài viết Quản lý hiệu quả bộ nhớ trong trò chơi.

Trạng thái luồng

Khi di chuyển qua các thành phần điển hình của báo cáo Systrace, bạn có thể xem khoảng thời gian mà một luồng cụ thể dành cho từng trạng thái luồng có thể xảy ra bằng cách chọn luồng trong báo cáo như trong Hình 7:

Sơ đồ báo cáo Systrace

Hình 7. Báo cáo Systrace cho thấy cách thức việc chọn luồng khiến báo cáo hiển thị nội dung tóm tắt trạng thái cho luồng đó

Trong Hình 7, bạn có thể thấy rằng các luồng trò chơi không ở trạng thái "đang chạy" hoặc "có thể chạy" thường xuyên như bình thường. Danh sách sau đây cho thấy một số lý do phổ biến khiến một luồng nhất định có thể định kỳ chuyển đổi sang trạng thái bất thường:

  • Nếu một luồng ngủ trong một khoảng thời gian dài, luồng đó có thể bị tranh chấp khoá hoặc đang chờ hoạt động GPU.
  • Nếu một luồng liên tục bị chặn trên I/O, thì tức là bạn đang đọc quá nhiều dữ liệu từ ổ đĩa cùng một lúc hoặc trò chơi đang bị đơ.

Tài nguyên khác

Để tìm hiểu thêm về cách cải thiện hiệu suất của trò chơi, hãy xem các tài nguyên bổ sung sau:

Video

  • Bài thuyết trình Systrace cho trò chơi từ hội nghị Nhà phát triển trò chơi trên Android năm 2018