เผยแพร่
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 เริ่มต้น

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

- หากงานต้องทำงานใน CPU ที่เฉพาะเจาะจง PerformanceHint API จะทราบวิธี ตัดสินใจในนามของคุณ
- ดังนั้น คุณจึงไม่จำเป็นต้องใช้ความสัมพันธ์
- อุปกรณ์มีโทโพโลยีที่หลากหลาย ลักษณะด้านพลังงานและความร้อนจึงแตกต่างกันมากเกินกว่าที่จะเปิดเผยต่อนักพัฒนาแอป
- คุณไม่สามารถคาดเดาเกี่ยวกับระบบพื้นฐานที่คุณใช้งานอยู่ได้
โซลูชัน
ADPF มีคลาส PerformanceHintManager
เพื่อให้เกมส่งคำแนะนำด้านประสิทธิภาพไปยัง Android สำหรับความเร็วสัญญาณนาฬิกาของ CPU และ
ประเภทคอร์ จากนั้นระบบปฏิบัติการจะตัดสินใจได้ว่าจะใช้คำแนะนำอย่างไรให้ดีที่สุดโดยอิงตาม SoC และ
โซลูชันระบายความร้อนของอุปกรณ์ หากแอปใช้ API นี้ร่วมกับการตรวจสอบ
สถานะความร้อน แอปจะให้คำแนะนำที่แม่นยำยิ่งขึ้นแก่ระบบปฏิบัติการได้แทนการใช้
ลูปที่ทำงานหนักและเทคนิคการเขียนโค้ดอื่นๆ ที่อาจทำให้เกิดการควบคุมปริมาณ
เกมจะใช้คำแนะนำด้านประสิทธิภาพดังนี้
- สร้างเซสชันคำแนะนำสำหรับเธรดสำคัญที่มีลักษณะการทำงานคล้ายกัน เช่น
- เธรดการแสดงผลและทรัพยากร Dependency จะได้รับการเข้าชม 1 ครั้ง
- ใน Cocos เทรดเครื่องมือหลักและเทรดการแสดงผลจะได้รับ 1 เซสชัน
- ใน Unity ให้ผสานรวมปลั๊กอินผู้ให้บริการ Android ของ Adaptive Performance
- ใน Unreal ให้ผสานรวมปลั๊กอิน Unreal Adaptive Performance และใช้ตัวเลือกความสามารถในการปรับขนาดเพื่อรองรับระดับคุณภาพหลายระดับ
- IO threads get another session
- เธรดเสียงจะได้รับเซสชันที่ 3
- เธรดการแสดงผลและทรัพยากร Dependency จะได้รับการเข้าชม 1 ครั้ง
- เกมควรดำเนินการนี้ตั้งแต่เนิ่นๆ อย่างน้อย 2 มิลลิวินาที และควรมากกว่า 4 มิลลิวินาที ก่อนที่เซสชันจะต้องใช้ทรัพยากรของระบบเพิ่มขึ้น
- ในแต่ละเซสชันคำใบ้ ให้คาดการณ์ระยะเวลาที่จำเป็นสำหรับแต่ละเซสชันในการเรียกใช้ โดยปกติแล้ว ระยะเวลาจะเทียบเท่ากับช่วงเฟรม แต่แอปสามารถใช้ช่วงที่สั้นกว่าได้หากปริมาณงานไม่แตกต่างกันมากนักในเฟรมต่างๆ
วิธีนำทฤษฎีไปใช้จริงมีดังนี้
เริ่มต้น 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);