หากวัดไม่ได้ ก็ปรับปรุงไม่ได้
Lord Kelvin
หากต้องการให้เกม HTML5 ทำงานเร็วขึ้น คุณต้องระบุจุดคอขวดด้านประสิทธิภาพก่อน ซึ่งอาจทำได้ยาก การประเมินข้อมูลเฟรมต่อวินาที (FPS) เป็นเพียงจุดเริ่มต้นเท่านั้น แต่คุณต้องเข้าใจความแตกต่างของกิจกรรมใน Chrome เพื่อให้เห็นภาพรวมทั้งหมด
เครื่องมือ about:tracing
ให้ข้อมูลเชิงลึกที่ช่วยให้คุณหลีกเลี่ยงการแก้ปัญหาชั่วคราวที่มุ่งเน้นการปรับปรุงประสิทธิภาพ แต่จริงๆ แล้วเป็นการคาดเดาที่มีเจตนาดี คุณจะช่วยประหยัดเวลาและแรงได้มาก รวมถึงเห็นภาพชัดเจนขึ้นว่า Chrome กำลังทำอะไรกับแต่ละเฟรม และนำข้อมูลนี้ไปใช้เพิ่มประสิทธิภาพเกม
สวัสดี about:tracing
เครื่องมือ about:tracing
ของ Chrome ช่วยให้คุณเห็นภาพรวมของกิจกรรมทั้งหมดของ Chrome ในช่วงเวลาหนึ่งๆ โดยละเอียดมากจนคุณอาจรู้สึกสับสนในตอนแรก ฟังก์ชันหลายอย่างใน Chrome มีเครื่องมือสําหรับการติดตามอยู่แล้วตั้งแต่แกะกล่อง คุณจึงใช้ about:tracing
เพื่อติดตามประสิทธิภาพได้โดยไม่ต้องทำเครื่องมือวัดด้วยตนเอง (ดูส่วนถัดไปเกี่ยวกับการติดแท็ก JS ด้วยตนเอง)
หากต้องการดูมุมมองการติดตาม ให้พิมพ์ "about:tracing" ลงในแถบอเนกประสงค์ (แถบที่อยู่) ของ Chrome
จากเครื่องมือการติดตาม คุณสามารถเริ่มบันทึก เรียกใช้เกมเป็นเวลา 2-3 วินาที แล้วดูข้อมูลการติดตามได้ ต่อไปนี้คือตัวอย่างลักษณะที่ข้อมูลอาจปรากฏ
ใช่ เป็นเรื่องที่สับสน มาพูดถึงวิธีอ่านกัน
แต่ละแถวแสดงถึงโปรไฟล์ของกระบวนการ แกนซ้าย-ขวาแสดงเวลา และกล่องสีแต่ละช่องคือการเรียกฟังก์ชันที่มีเครื่องมือวัด แถวต่างๆ แสดงทรัพยากรหลายประเภท รายการที่น่าสนใจที่สุดสำหรับการจัดโปรไฟล์เกมคือ CrGpuMain ซึ่งแสดงสิ่งที่หน่วยประมวลผลกราฟิก (GPU) กำลังทํา และ CrRendererMain การติดตามแต่ละรายการจะมีบรรทัด CrRendererMain สําหรับแท็บที่เปิดอยู่แต่ละแท็บในช่วงการติดตาม (รวมถึงแท็บ about:tracing
เอง)
เมื่ออ่านข้อมูลการติดตาม งานแรกของคุณคือต้องระบุว่าแถว CrRendererMain ใดสอดคล้องกับเกมของคุณ
ในตัวอย่างนี้ 2 รายการที่ตรงกันคือ 2216 และ 6516 ขออภัย ปัจจุบันยังไม่มีวิธีที่ดีในการเลือกแอปพลิเคชัน ยกเว้นการมองหาบรรทัดที่ทำการอัปเดตเป็นระยะๆ จำนวนมาก (หรือหากคุณได้ติดตั้งเครื่องมือวัดโค้ดด้วยจุดติดตามด้วยตนเอง ให้มองหาบรรทัดที่เก็บข้อมูลการติดตาม) ในตัวอย่างนี้ ดูเหมือนว่า 6516 จะเรียกใช้ลูปหลักจากความถี่ของการอัปเดต หากคุณปิดแท็บอื่นๆ ทั้งหมดก่อนเริ่มการติดตาม การค้นหา CrRendererMain ที่ถูกต้องจะง่ายขึ้น แต่อาจยังมีแถว CrRendererMain สำหรับกระบวนการอื่นๆ นอกเหนือจากเกมของคุณ
การค้นหาเฟรม
เมื่อพบแถวที่ถูกต้องในเครื่องมือการติดตามสำหรับเกมแล้ว ขั้นตอนถัดไปคือการค้นหาลูปหลัก ลูปหลักมีลักษณะเป็นรูปแบบที่ซ้ำกันในข้อมูลการติดตาม คุณสามารถไปยังส่วนต่างๆ ของข้อมูลการติดตามได้โดยใช้แป้น W, A, S, D โดยแป้น A และ D ใช้เพื่อเลื่อนไปทางซ้ายหรือขวา (ย้อนกลับและไปข้างหน้าตามลำดับเวลา) ส่วนแป้น W และ S ใช้เพื่อซูมเข้าและออก คุณควรคาดหวังว่าลูปหลักจะเป็นรูปแบบที่ซ้ำกันทุก 16 มิลลิวินาทีหากเกมทำงานที่ 60Hz
เมื่อพบจุดที่หัวใจเต้นของเกมแล้ว คุณจะเจาะลึกสิ่งที่โค้ดทําในแต่ละเฟรมได้ ใช้แป้น W, A, S, D เพื่อซูมเข้าจนกว่าคุณจะอ่านข้อความในช่องฟังก์ชันได้
คอลเล็กชันกล่องนี้แสดงชุดการเรียกฟังก์ชัน โดยแต่ละการเรียกจะแสดงด้วยกล่องสี แต่ละฟังก์ชันเรียกโดยช่องด้านบน ดังนั้นในกรณีนี้ คุณจะเห็นได้ว่า MessageLoop::RunTask เรียก RenderWidget::OnSwapBuffersComplete ซึ่งเรียก RenderWidget::DoDeferredUpdate และอื่นๆ ต่อๆ ไป การอ่านข้อมูลนี้จะช่วยให้คุณเห็นภาพรวมทั้งหมดของสิ่งที่เรียกใช้อะไรและระยะเวลาในการเรียกใช้แต่ละรายการ
แต่ปัญหาอยู่ตรงที่ว่า ข้อมูลที่ about:tracing
แสดงคือฟังก์ชันการเรียกแบบดิบจากซอร์สโค้ดของ Chrome คุณอาจเดาได้ว่าฟังก์ชันแต่ละรายการทําอะไรจากชื่อ แต่ข้อมูลนั้นไม่ค่อยเป็นมิตรกับผู้ใช้ การดูภาพรวมของเฟรมมีประโยชน์ แต่คุณก็ต้องใช้ภาพที่สามารถอ่านได้ง่ายกว่านี้เพื่อดูว่าเกิดอะไรขึ้น
การเพิ่มแท็กการติดตาม
แต่โชคดีที่เรามีวิธีง่ายๆ ในการเพิ่มเครื่องมือวัดผลด้วยตนเองลงในโค้ดเพื่อสร้างข้อมูลการติดตาม นั่นคือ console.time
และ console.timeEnd
console.time("update");
update();
console.timeEnd("update");
console.time("render");
update();
console.timeEnd("render");
โค้ดด้านบนจะสร้างช่องใหม่ในชื่อมุมมองการติดตามพร้อมแท็กที่ระบุ ดังนั้นหากคุณเรียกใช้แอปอีกครั้ง คุณจะเห็นช่อง "อัปเดต" และ "แสดงผล" ที่แสดงเวลาผ่านไประหว่างการเรียกใช้เริ่มต้นและสิ้นสุดสําหรับแต่ละแท็ก
ซึ่งจะช่วยให้คุณสร้างข้อมูลการติดตามที่มนุษย์อ่านได้เพื่อติดตามจุดร้อนในโค้ดได้
GPU หรือ CPU
เมื่อใช้กราฟิกที่เร่งด้วยฮาร์ดแวร์ คำถามที่สําคัญที่สุดอย่างหนึ่งที่คุณถามได้ในระหว่างการโปรไฟล์คือ โค้ดนี้ใช้ GPU หรือ CPU ในแต่ละเฟรม คุณจะต้องทำการเรนเดอร์บางอย่างใน GPU และตรรกะบางอย่างใน CPU หากต้องการทำความเข้าใจว่าอะไรทำให้เกมช้าลง คุณจะต้องดูว่างานมีความสมดุลกันอย่างไรใน 2 ทรัพยากรนี้
ก่อนอื่น ให้ค้นหาบรรทัดในมุมมองการติดตามชื่อ CrGPUMain ซึ่งระบุว่า GPU ไม่ว่างในช่วงเวลาหนึ่งๆ หรือไม่
คุณจะเห็นได้ว่าเฟรมทุกเฟรมของเกมทําให้ CPU ทํางานใน CrRendererMain และ GPU การติดตามด้านบนแสดง Use Case ที่ง่ายมากซึ่งทั้ง CPU และ GPU ไม่ได้ใช้งานเกือบตลอดเฟรม 16 มิลลิวินาทีแต่ละเฟรม
มุมมองการติดตามจะมีประโยชน์อย่างยิ่งเมื่อเกมทำงานช้าและคุณไม่แน่ใจว่าทรัพยากรใดใช้เกินขีดจำกัด การดูความเชื่อมโยงของบรรทัด GPU และ CPU คือกุญแจสำคัญในการแก้ไขข้อบกพร่อง ใช้ตัวอย่างเดิม แต่เพิ่มการทำงานอีกเล็กน้อยในลูปการอัปเดต
console.time("update");
doExtraWork();
update(Math.min(50, now - time));
console.timeEnd("update");
console.time("render");
render();
console.timeEnd("render");
ตอนนี้คุณจะเห็นการติดตามที่มีลักษณะดังนี้
การติดตามนี้บอกอะไรเราบ้าง เราเห็นว่าเฟรมในภาพใช้เวลาประมาณ 2270 มิลลิวินาทีถึง 2320 มิลลิวินาที ซึ่งหมายความว่าแต่ละเฟรมใช้เวลาประมาณ 50 มิลลิวินาที (อัตราเฟรม 20 Hz) คุณจะเห็นกล่องสีเล็กๆ ที่แสดงถึงฟังก์ชันการแสดงผลข้างช่องอัปเดต แต่เฟรมนั้นเต็มไปด้วยการอัปเดต
คุณจะเห็นว่า GPU ยังคงทำงานอยู่เฉยๆ เกือบทุกเฟรม ซึ่งตรงข้ามกับสิ่งที่เกิดขึ้นใน CPU หากต้องการเพิ่มประสิทธิภาพโค้ดนี้ ให้มองหาการดำเนินการที่ทำได้โดยใช้โค้ด Shader แล้วย้ายไปยัง GPU เพื่อใช้ทรัพยากรอย่างคุ้มค่าที่สุด
จะเกิดอะไรขึ้นเมื่อโค้ด Shader ทำงานช้าและ GPU ทำงานหนักเกินไป จะเกิดอะไรขึ้นหากเรานำงานที่ไม่จำเป็นต้องทำออกจาก CPU และเพิ่มงานบางอย่างในโค้ด Shader ของเศษ นี่คือตัวอย่าง Shader ระดับเศษส่วนที่สิ้นเปลืองทรัพยากรโดยไม่จำเป็น
#ifdef GL_ES
precision highp float;
#endif
void main(void) {
for(int i=0; i<9999; i++) {
gl_FragColor = vec4(1.0, 0, 0, 1.0);
}
}
ร่องรอยของโค้ดที่ใช้โปรแกรมเปลี่ยนสีนั้นมีลักษณะเป็นอย่างไร
โปรดจดบันทึกระยะเวลาของเฟรมอีกครั้ง รูปแบบที่ซ้ำกันนี้อยู่ในช่วงประมาณ 2750-2950 มิลลิวินาที โดยมีระยะเวลา 200 มิลลิวินาที (อัตราเฟรมประมาณ 5 Hz) บรรทัด CrRendererMain ว่างเปล่าเกือบทั้งหมด ซึ่งหมายความว่า CPU ไม่ได้ทำงานเกือบตลอดเวลา ขณะที่ GPU ทำงานหนักเกินไป นี่เป็นสัญญาณที่ชัดเจนว่าชิเดอร์ของคุณมีน้ำหนักมากเกินไป
หากไม่ทราบสาเหตุที่ทำให้เกิดอัตราเฟรมต่ำ คุณอาจสังเกตเห็นการอัปเดต 5 Hz และอาจอยากเข้าไปแก้ไขโค้ดเกมและเริ่มพยายามเพิ่มประสิทธิภาพหรือนำตรรกะเกมออก ในกรณีนี้ การดำเนินการดังกล่าวจะไร้ประโยชน์อย่างยิ่ง เนื่องจากตรรกะในลูปเกมไม่ใช่สิ่งที่ทำให้เสียเวลา อันที่จริง สิ่งที่การติดตามนี้บ่งชี้คือการทำงานกับ CPU มากขึ้นในแต่ละเฟรมจะ "ฟรี" อยู่เสมอเนื่องจาก CPU ทำงานอยู่เฉยๆ ดังนั้นการให้งานเพิ่มเติมจะไม่ส่งผลต่อระยะเวลาของเฟรม
ตัวอย่างจริง
มาดูกันว่าข้อมูลการติดตามจากเกมจริงมีลักษณะเป็นอย่างไร สิ่งที่น่าสนใจอย่างหนึ่งเกี่ยวกับเกมที่สร้างด้วยเทคโนโลยีเว็บแบบเปิดคือคุณสามารถดูสิ่งที่เกิดขึ้นในผลิตภัณฑ์โปรดของคุณ หากต้องการทดสอบเครื่องมือโปรไฟล์ คุณสามารถเลือกเกม WebGL ที่ชอบจาก Chrome เว็บสโตร์และโปรไฟล์ด้วย about:tracing
นี่คือตัวอย่างการติดตามที่มาจากเกม Skid Racer บน WebGL ที่ยอดเยี่ยม
ดูเหมือนว่าแต่ละเฟรมจะใช้เวลาประมาณ 20 มิลลิวินาที ซึ่งหมายความว่าอัตราเฟรมจะอยู่ที่ประมาณ 50 FPS คุณจะเห็นได้ว่างานมีความสมดุลระหว่าง CPU และ GPU แต่ GPU เป็นทรัพยากรที่มีความต้องการมากที่สุด หากต้องการดูตัวอย่างจริงของเกม WebGL ให้ลองเล่นกับเกมบางเกมใน Chrome เว็บสโตร์ที่สร้างขึ้นด้วย WebGL ซึ่งรวมถึงเกมต่อไปนี้
บทสรุป
หากต้องการให้เกมทำงานที่ 60Hz การดำเนินการทั้งหมดสำหรับเฟรมแต่ละเฟรมต้องใช้เวลาไม่เกิน 16 มิลลิวินาทีของ CPU และ 16 มิลลิวินาทีของ GPU คุณมีทรัพยากร 2 รายการที่ใช้ได้พร้อมกัน และสามารถสลับงานระหว่างทรัพยากรเพื่อเพิ่มประสิทธิภาพสูงสุด มุมมอง about:tracing
ของ Chrome เป็นเครื่องมือที่มีประโยชน์อย่างยิ่งในการรับข้อมูลเชิงลึกเกี่ยวกับสิ่งที่โค้ดของคุณทําอยู่จริง และจะช่วยให้คุณใช้เวลาในการพัฒนาได้คุ้มค่าที่สุดด้วยการแก้ปัญหาที่เหมาะสม
ขั้นตอนถัดไปคือ
นอกจาก GPU แล้ว คุณยังติดตามส่วนอื่นๆ ของรันไทม์ Chrome ได้ด้วย Chrome Canary ซึ่งเป็นเวอร์ชันเริ่มต้นของ Chrome มีเครื่องมือในการติดตาม IO, IndexedDB และกิจกรรมอื่นๆ อีกหลายอย่าง คุณควรอ่านบทความ Chromium นี้เพื่อให้เข้าใจสถานะปัจจุบันของเหตุการณ์การติดตามได้ดีขึ้น
หากคุณเป็นนักพัฒนาเกมบนเว็บ โปรดดูวิดีโอด้านล่าง นี่เป็นงานนำเสนอจากทีมผู้สนับสนุนนักพัฒนาเกมของ Google ที่ GDC 2012 เกี่ยวกับการเพิ่มประสิทธิภาพเกมใน Chrome