API คำแนะนำด้านประสิทธิภาพ

เผยแพร่

Android 12 (API ระดับ 31) - Performance Hint API

Android 13 (API ระดับ 33) - Performance Hint Manager ใน NDK API

(ตัวอย่าง) Android 15 (DP1) - reportActualWorkDuration()

คำแนะนำด้านประสิทธิภาพของ CPU ช่วยให้เกมมีอิทธิพลต่อลักษณะการทำงานของประสิทธิภาพ CPU แบบไดนามิก เพื่อให้ตรงกับความต้องการของเกมได้ดียิ่งขึ้น ในอุปกรณ์ส่วนใหญ่ Android จะปรับความเร็วสัญญาณนาฬิกาของ CPU และประเภทคอร์แบบไดนามิกสำหรับภาระงานตามความต้องการก่อนหน้า หากภาระงานใช้ทรัพยากร CPU มากขึ้น ระบบจะเพิ่มความเร็วสัญญาณนาฬิกาและย้ายภาระงานไปยังคอร์ที่ใหญ่ขึ้นในที่สุด หากปริมาณงานใช้ทรัพยากรน้อยกว่า Android จะลดการจัดสรรทรัพยากร ADPF ช่วยให้แอปพลิเคชัน หรือเกมส่งสัญญาณเพิ่มเติมเกี่ยวกับประสิทธิภาพและกำหนดเวลาได้ ซึ่งจะช่วยให้ระบบเพิ่มประสิทธิภาพได้ดียิ่งขึ้น (ปรับปรุงประสิทธิภาพ) และลดความเร็วสัญญาณนาฬิกาได้อย่างรวดเร็วเมื่อปริมาณงานเสร็จสมบูรณ์ (ประหยัดการใช้พลังงาน)

ความเร็วนาฬิกา

เมื่ออุปกรณ์ Android ปรับความเร็วสัญญาณนาฬิกาของ CPU แบบไดนามิก ความถี่อาจ เปลี่ยนประสิทธิภาพของโค้ด การออกแบบโค้ดที่จัดการความเร็วสัญญาณนาฬิกาแบบไดนามิก เป็นสิ่งสำคัญในการเพิ่มประสิทธิภาพสูงสุด รักษา สถานะความร้อนที่ปลอดภัย และใช้พลังงานอย่างมีประสิทธิภาพ คุณไม่สามารถกำหนดความถี่ CPU โดยตรง ในโค้ดแอป ด้วยเหตุนี้ วิธีทั่วไปที่แอปใช้เพื่อพยายามเรียกใช้ที่ความเร็วสัญญาณนาฬิกาของ CPU ที่สูงขึ้น คือการเรียกใช้ลูปที่ทำงานหนักในเธรดเบื้องหลังเพื่อให้ปริมาณงาน ดูเหมือนจะต้องการทรัพยากรมากขึ้น ซึ่งเป็นแนวทางที่ไม่ดีเนื่องจากเป็นการสิ้นเปลืองพลังงานและเพิ่ม ภาระด้านความร้อนในอุปกรณ์เมื่อแอปไม่ได้ใช้ทรัพยากรเพิ่มเติม จริงๆ PerformanceHint API ของ CPU ออกแบบมาเพื่อแก้ปัญหานี้ การแจ้งให้ระบบทราบระยะเวลาการทำงานจริงและระยะเวลาการทำงานเป้าหมาย จะช่วยให้ Android ทราบภาพรวมความต้องการ CPU ของแอปและจัดสรร ทรัพยากรได้อย่างมีประสิทธิภาพ ซึ่งจะช่วยให้ประสิทธิภาพการทำงานดีที่สุดในระดับการใช้พลังงานที่มีประสิทธิภาพ

ประเภทหลัก

ประเภทแกน CPU ที่เกมของคุณทำงานเป็นอีกปัจจัยสำคัญด้านประสิทธิภาพ อุปกรณ์ Android มักจะเปลี่ยนคอร์ CPU ที่กำหนดให้กับเธรด แบบไดนามิกตามลักษณะการทำงานของเวิร์กโหลดล่าสุด การกำหนดแกน CPU จะยิ่งซับซ้อนมากขึ้น ใน SoC ที่มีแกนหลายประเภท ในอุปกรณ์บางรุ่น แกนขนาดใหญ่จะใช้ได้เพียงชั่วครู่เท่านั้นโดยไม่เข้าสู่สถานะ ที่ความร้อนไม่สามารถระบายออกได้

เกมไม่ควรพยายามตั้งค่าความสัมพันธ์ของคอร์ CPU ด้วยเหตุผลต่อไปนี้

  • ประเภทคอร์ที่ดีที่สุดสำหรับภาระงานจะแตกต่างกันไปตามรุ่นของอุปกรณ์
  • ความยั่งยืนของการเรียกใช้คอร์ขนาดใหญ่จะแตกต่างกันไปตาม SoC และโซลูชันระบายความร้อนต่างๆ ที่มีให้ในอุปกรณ์แต่ละรุ่น
  • ผลกระทบต่อสิ่งแวดล้อมที่มีต่อสถานะความร้อนอาจทำให้การเลือกคอร์ซับซ้อนยิ่งขึ้น เช่น สภาพอากาศหรือเคสโทรศัพท์อาจเปลี่ยนสถานะความร้อนของอุปกรณ์ได้
  • การเลือกหลักไม่รองรับอุปกรณ์ใหม่ที่มีประสิทธิภาพและความสามารถด้านความร้อนเพิ่มเติม ด้วยเหตุนี้ อุปกรณ์จึงมักไม่สนใจความสัมพันธ์ของโปรเซสเซอร์ของเกม

ตัวอย่างลักษณะการทำงานของตัวจัดกำหนดการ Linux เริ่มต้น

ลักษณะการทำงานของตัวกำหนดเวลา Linux
รูปที่ 1 Governor อาจใช้เวลาประมาณ 200 มิลลิวินาทีในการเพิ่มหรือลดความถี่ CPU ADPF ทำงานร่วมกับระบบการปรับขนาดแรงดันไฟฟ้าและความถี่แบบไดนามิก (DVFS) เพื่อให้ประสิทธิภาพต่อวัตต์ที่ดีที่สุด

PerformanceHint API จะแยกความหน่วงของ DVFS ออกมามากกว่า

ADPF Abstracts more than DVFS Latencies
รูปที่ 2 ADPF รู้ว่าจะตัดสินใจในนามของคุณอย่างไรให้ดีที่สุด
  • หากงานต้องทำงานใน CPU ที่เฉพาะเจาะจง PerformanceHint API จะทราบวิธี ตัดสินใจในนามของคุณ
  • ดังนั้น คุณจึงไม่จำเป็นต้องใช้ความสัมพันธ์
  • อุปกรณ์มีโทโพโลยีที่หลากหลาย ลักษณะด้านพลังงานและความร้อนจึงแตกต่างกันมากเกินกว่าที่จะเปิดเผยต่อนักพัฒนาแอป
  • คุณไม่สามารถคาดเดาเกี่ยวกับระบบพื้นฐานที่คุณใช้งานอยู่ได้

โซลูชัน

ADPF มีคลาส PerformanceHintManager เพื่อให้เกมส่งคำแนะนำด้านประสิทธิภาพไปยัง Android สำหรับความเร็วสัญญาณนาฬิกาของ CPU และ ประเภทคอร์ จากนั้นระบบปฏิบัติการจะตัดสินใจได้ว่าจะใช้คำแนะนำอย่างไรให้ดีที่สุดโดยอิงตาม SoC และ โซลูชันระบายความร้อนของอุปกรณ์ หากแอปใช้ API นี้ร่วมกับการตรวจสอบ สถานะความร้อน แอปจะให้คำแนะนำที่แม่นยำยิ่งขึ้นแก่ระบบปฏิบัติการได้แทนการใช้ ลูปที่ทำงานหนักและเทคนิคการเขียนโค้ดอื่นๆ ที่อาจทำให้เกิดการควบคุมปริมาณ

เกมจะใช้คำแนะนำด้านประสิทธิภาพดังนี้

  1. สร้างเซสชันคำแนะนำสำหรับเธรดสำคัญที่มีลักษณะการทำงานคล้ายกัน เช่น
  2. เกมควรดำเนินการนี้ตั้งแต่เนิ่นๆ อย่างน้อย 2 มิลลิวินาที และควรมากกว่า 4 มิลลิวินาที ก่อนที่เซสชันจะต้องใช้ทรัพยากรของระบบเพิ่มขึ้น
  3. ในแต่ละเซสชันคำใบ้ ให้คาดการณ์ระยะเวลาที่จำเป็นสำหรับแต่ละเซสชันในการเรียกใช้ โดยปกติแล้ว ระยะเวลาจะเทียบเท่ากับช่วงเฟรม แต่แอปสามารถใช้ช่วงที่สั้นกว่าได้หากปริมาณงานไม่แตกต่างกันมากนักในเฟรมต่างๆ

วิธีนำทฤษฎีไปใช้จริงมีดังนี้

เริ่มต้น PerformanceHintManager และสร้าง HintSession

รับเครื่องมือจัดการโดยใช้บริการของระบบและสร้างเซสชันคำแนะนำสำหรับเธรด หรือกลุ่มเธรดที่ทำงานในภาระงานเดียวกัน

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);

ตั้งค่าเธรดหากจำเป็น

เผยแพร่

Android 11 (API ระดับ 34)

ใช้ฟังก์ชันsetThreads ของ PerformanceHintManager.Session เมื่อมีเธรดอื่นๆ ที่ต้องเพิ่มในภายหลัง เช่น หากคุณสร้างเธรดฟิสิกส์ในภายหลังและต้องการเพิ่มลงในเซสชัน คุณสามารถใช้ setThreads API นี้ได้

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);

หากกำหนดเป้าหมายเป็น API ระดับต่ำกว่า คุณจะต้องทำลายเซสชันและ สร้างเซสชันใหม่ทุกครั้งที่ต้องการเปลี่ยนรหัสเธรด

รายงานระยะเวลาการทำงานจริง

ติดตามระยะเวลาจริงที่ต้องใช้ในการทำงานให้เสร็จสมบูรณ์ในหน่วยนาโนวินาทีและรายงาน ต่อระบบเมื่อทำงานเสร็จสมบูรณ์ในทุกรอบ เช่น หาก นี่คือสำหรับเธรดการแสดงผล ให้เรียกใช้ในทุกเฟรม

หากต้องการรับเวลาจริงอย่างน่าเชื่อถือ ให้ใช้

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();

เช่น

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);

อัปเดตระยะเวลาทำงานเป้าหมายเมื่อจำเป็น

เมื่อใดก็ตามที่ระยะเวลาการทำงานเป้าหมายเปลี่ยนแปลง เช่น หากผู้เล่นเลือก เป้าหมาย FPS ที่แตกต่างกัน ให้เรียกใช้เมธอด updateTargetWorkDuration เพื่อให้ระบบทราบเพื่อให้ระบบปฏิบัติการปรับทรัพยากรตาม เป้าหมายใหม่ คุณไม่จำเป็นต้องเรียกใช้ในทุกเฟรม และเรียกใช้เฉพาะเมื่อระยะเวลาเป้าหมายเปลี่ยนแปลงเท่านั้น

C++

APerformanceHint_updateTargetWorkDuration(hint_session, target_duration);

Java

hintSession.updateTargetWorkDuration(targetDuration);