บทนำ
ในช่วงไม่กี่ปีที่ผ่านมา เว็บแอปพลิเคชันมีความเร็วเพิ่มขึ้นอย่างมาก ปัจจุบันแอปพลิเคชันจำนวนมากทำงานได้เร็วพอจนฉันได้ยินนักพัฒนาซอฟต์แวร์บางรายสงสัยว่า "เว็บเร็วพอหรือยัง" ซึ่งอาจเพียงพอสำหรับบางแอปพลิเคชัน แต่สำหรับนักพัฒนาแอปที่ทำงานกับแอปพลิเคชันประสิทธิภาพสูง เราทราบดีว่ายังเร็วไม่พอ แม้ว่าเทคโนโลยีเครื่องเสมือน JavaScript จะมีความก้าวหน้าอย่างน่าทึ่ง แต่การศึกษาล่าสุดแสดงให้เห็นว่าแอปพลิเคชัน Google ใช้เวลา 50% ถึง 70% ภายใน V8 แอปพลิเคชันของคุณมีเวลาจำกัด การลดรอบการทำงานของระบบหนึ่งหมายความว่าอีกระบบหนึ่งจะทำงานได้มากขึ้น โปรดทราบว่าแอปพลิเคชันที่ทำงานที่ 60 FPS จะมีเวลาเพียง 16 มิลลิวินาทีต่อเฟรมเท่านั้น ไม่เช่นนั้นจะเกิดอาการกระตุก อ่านต่อเพื่อดูข้อมูลเกี่ยวกับการเพิ่มประสิทธิภาพ JavaScript และแอปพลิเคชัน JavaScript ของโปรไฟล์ในเรื่องราวจากสนามรบของนักสืบประสิทธิภาพในทีม V8 ที่ติดตามปัญหาด้านประสิทธิภาพที่คลุมเครือใน Find Your Way to Oz
เซสชัน Google I/O 2013
ฉันนำเสนอเนื้อหานี้ในงาน Google I/O 2013 ดูวิดีโอด้านล่าง
เหตุใดประสิทธิภาพจึงมีความสำคัญ
รอบ CPU เป็นเกมที่ผลรวมเป็นศูนย์ การทำให้ส่วนหนึ่งของระบบใช้ทรัพยากรน้อยลงจะช่วยให้คุณใช้ทรัพยากรในส่วนอื่นได้มากขึ้นหรือทำงานได้ราบรื่นขึ้นโดยรวม การทำงานเร็วขึ้นและการทำสิ่งต่างๆ ได้มากขึ้นมักเป็นเป้าหมายที่ขัดแย้งกัน ผู้ใช้ต้องการฟีเจอร์ใหม่ๆ ขณะเดียวกันก็คาดหวังให้แอปพลิเคชันทำงานได้ราบรื่นขึ้นด้วย เครื่องเสมือน JavaScript ทำงานเร็วขึ้นเรื่อยๆ แต่นั่นไม่ใช่เหตุผลที่จะเพิกเฉยต่อปัญหาด้านประสิทธิภาพที่คุณแก้ไขได้ในวันนี้ เนื่องจากนักพัฒนาซอฟต์แวร์จำนวนมากที่จัดการกับปัญหาด้านประสิทธิภาพในเว็บแอปพลิเคชันทราบอยู่แล้ว ในแอปพลิเคชันแบบเรียลไทม์ที่มีอัตราเฟรมสูง ความกดดันที่จะทำให้แอปพลิเคชันทำงานได้อย่างราบรื่นนั้นเป็นสิ่งสำคัญที่สุด Insomniac Games ได้จัดทำการศึกษาซึ่งแสดงให้เห็นว่าอัตราเฟรมที่เสถียรและสม่ำเสมอเป็นสิ่งสำคัญต่อความสำเร็จของเกม โดยระบุว่า "อัตราเฟรมที่เสถียรยังคงเป็นสัญญาณของผลิตภัณฑ์ที่สร้างขึ้นมาอย่างดีและเป็นมืออาชีพ" นักพัฒนาเว็บโปรดทราบ
การแก้ปัญหาด้านประสิทธิภาพ
การแก้ปัญหาด้านประสิทธิภาพก็เหมือนกับการไขคดีอาชญากรรม คุณต้องตรวจสอบหลักฐานอย่างละเอียด ตรวจสอบสาเหตุที่น่าสงสัย และทดลองใช้วิธีแก้ปัญหาต่างๆ คุณต้องบันทึกการวัดผลตลอดกระบวนการเพื่อให้แน่ใจว่าได้แก้ไขปัญหาแล้วจริงๆ วิธีการนี้แทบไม่ต่างจากวิธีที่นักสืบคดีอาชญากรรมใช้ไขคดีเลย นักสืบตรวจสอบหลักฐาน สอบปากคำผู้ต้องสงสัย และทำการทดลองโดยหวังว่าจะพบหลักฐานที่มัดตัวคนร้ายได้
V8 CSI: ออสเตรเลีย
นักพัฒนาที่น่าทึ่งซึ่งสร้าง Find Your Way to Oz ได้ติดต่อทีม V8 เกี่ยวกับปัญหาด้านประสิทธิภาพที่พวกเขาแก้เองไม่ได้ บางครั้ง Oz จะค้าง ทำให้เกิดอาการกระตุก นักพัฒนาแอป Oz ได้ทำการตรวจสอบเบื้องต้นโดยใช้แผงไทม์ไลน์ในเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome เมื่อดูการใช้งานหน่วยความจำ พวกเขาพบกราฟฟันเลื่อยที่น่ากลัว ทุกๆ 1 วินาที เครื่องมือเก็บขยะจะเก็บขยะ 10 MB และการหยุดชั่วคราวของเครื่องมือเก็บขยะจะสอดคล้องกับอาการกระตุก คล้ายกับภาพหน้าจอต่อไปนี้จากไทม์ไลน์ในเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome
Jakob และ Yang นักสืบ V8 ได้รับมอบหมายให้ทำคดีนี้ สิ่งที่เกิดขึ้นคือการโต้ตอบกันไปมาเป็นเวลานานระหว่าง Jakob และ Yang จากทีม V8 กับทีม Oz เราได้สรุปการสนทนานี้เป็นเหตุการณ์สำคัญที่ช่วยติดตามปัญหานี้
หลักฐาน
ขั้นตอนแรกคือการรวบรวมและศึกษาหลักฐานเบื้องต้น
เรากำลังดูแอปพลิเคชันประเภทใดอยู่
เดโม Oz เป็นแอปพลิเคชัน 3 มิติแบบอินเทอร์แอกทีฟ ด้วยเหตุนี้ จึงมีความไวต่อการหยุดชั่วคราวที่เกิดจากการเก็บขยะอย่างมาก โปรดทราบว่าแอปพลิเคชันแบบอินเทอร์แอกทีฟที่ทำงานที่ 60fps มีเวลา 16 มิลลิวินาทีในการทำงาน JavaScript ทั้งหมด และต้องเหลือเวลาส่วนหนึ่งไว้ให้ Chrome ประมวลผลการเรียกใช้กราฟิกและวาดหน้าจอ
Oz ทำการคำนวณทางคณิตศาสตร์จำนวนมากเกี่ยวกับค่า Double และเรียกใช้ WebAudio และ WebGL บ่อยครั้ง
ปัญหาด้านประสิทธิภาพที่พบคืออะไร
เราพบการหยุดชั่วคราวหรือที่เรียกว่าเฟรมดร็อปหรืออาการกระตุก การหยุดชั่วคราวเหล่านี้สัมพันธ์กับการเรียกใช้การเก็บขยะ
นักพัฒนาแอปทำตามแนวทางปฏิบัติแนะนำหรือไม่
ใช่ นักพัฒนา Oz มีความเชี่ยวชาญด้านประสิทธิภาพของ JavaScript VM และเทคนิคการเพิ่มประสิทธิภาพ โปรดทราบว่านักพัฒนา Oz ใช้ CoffeeScript เป็นภาษาต้นทางและสร้างโค้ด JavaScript ผ่านคอมไพเลอร์ CoffeeScript ซึ่งทำให้การตรวจสอบบางอย่างทำได้ยากขึ้นเนื่องจากโค้ดที่นักพัฒนา Oz เขียนและโค้ดที่ V8 ใช้งานไม่เชื่อมโยงกัน ตอนนี้เครื่องมือสำหรับนักพัฒนาเว็บใน Chrome รองรับซอร์สแมป ซึ่งจะช่วยให้การดำเนินการนี้ง่ายขึ้น
เหตุใดจึงมีการเรียกใช้ Garbage Collector
VM จะจัดการหน่วยความจำใน JavaScript ให้กับนักพัฒนาซอฟต์แวร์โดยอัตโนมัติ V8 ใช้ระบบจัดการหน่วยความจำที่ไม่ใช้แล้วทั่วไป โดยจะแบ่งหน่วยความจำออกเป็น 2 (หรือมากกว่า) รุ่น Young Generation จะเก็บออบเจ็กต์ที่เพิ่งได้รับการจัดสรร หากออบเจ็กต์อยู่รอดนานพอ ระบบจะย้ายออบเจ็กต์ไปยังรุ่นเก่า
ระบบจะรวบรวมข้อมูลรุ่นใหม่ด้วยความถี่ที่สูงกว่ารุ่นเก่ามาก ซึ่งเป็นไปตามที่ออกแบบไว้ เนื่องจากคอลเล็กชันรุ่นใหม่มีราคาถูกกว่ามาก โดยทั่วไปแล้ว การหยุดชั่วคราวของ GC ที่เกิดขึ้นบ่อยครั้งมักเกิดจากการรวบรวมข้อมูลของ Young Generation
ใน V8 พื้นที่หน่วยความจำใหม่จะแบ่งออกเป็นบล็อกหน่วยความจำที่อยู่ติดกัน 2 บล็อกซึ่งมีขนาดเท่ากัน ระบบจะใช้หน่วยความจำเพียงบล็อกเดียวจาก 2 บล็อกนี้ในเวลาใดก็ตาม และเรียกว่า To Space แม้ว่าจะมีหน่วยความจำเหลืออยู่ในพื้นที่ปลายทาง แต่การจัดสรรออบเจ็กต์ใหม่ก็ใช้ทรัพยากรน้อย เคอร์เซอร์ในพื้นที่เป้าหมายจะเลื่อนไปข้างหน้าตามจำนวนไบต์ที่จำเป็นสำหรับออบเจ็กต์ใหม่ โดยจะดำเนินการต่อไปจนกว่าพื้นที่ปลายทางจะเต็ม เมื่อถึงจุดนี้ โปรแกรมจะหยุดทำงานและเริ่มการเก็บรวบรวมข้อมูล
ณ จุดนี้ พื้นที่ต้นทางและพื้นที่ปลายทางจะสลับกัน ระบบจะสแกนพื้นที่ที่เคยเป็นพื้นที่ปลายทางและตอนนี้เป็นพื้นที่ต้นทางตั้งแต่ต้นจนจบ และคัดลอกออบเจ็กต์ที่ยังคงทำงานอยู่ไปยังพื้นที่ปลายทางหรือเลื่อนไปยังฮีปเจเนอเรชันเก่า หากต้องการรายละเอียด เราขอแนะนำให้อ่านเกี่ยวกับอัลกอริทึมของ Cheney
คุณควรเข้าใจโดยสัญชาตญาณว่าทุกครั้งที่มีการจัดสรรออบเจ็กต์ ไม่ว่าจะโดยนัยหรือโดยชัดแจ้ง (ผ่านการเรียกใช้ new, [] หรือ {}) แอปพลิเคชันของคุณจะเข้าใกล้การเก็บขยะและการหยุดแอปพลิเคชันชั่วคราวที่น่ากลัวมากขึ้นเรื่อยๆ
แอปพลิเคชันนี้ควรมีขยะ 10 MB/วินาทีใช่ไหม
สรุปสั้นๆ คือไม่ นักพัฒนาแอปไม่ได้ทำอะไรที่คาดว่าจะทำให้เกิดขยะ 10 MB/วินาที
ผู้ต้องสงสัย
ขั้นตอนถัดไปของการตรวจสอบคือการระบุผู้ต้องสงสัยที่อาจเป็นไปได้ แล้วคัดออกให้เหลือน้อยลง
ผู้ต้องสงสัย #1
การเรียกใช้ "new" ระหว่างเฟรม โปรดทราบว่าออบเจ็กต์แต่ละรายการที่ได้รับการจัดสรรจะทำให้คุณเข้าใกล้การหยุดชั่วคราวของ GC มากขึ้น โดยเฉพาะอย่างยิ่งแอปพลิเคชันที่ทำงานด้วยอัตราเฟรมสูงควรพยายามไม่ให้มีการจัดสรรต่อเฟรม โดยปกติแล้ว การดำเนินการนี้ต้องใช้ระบบรีไซเคิลออบเจ็กต์เฉพาะแอปพลิเคชันที่ผ่านการคิดมาอย่างรอบคอบ นักสืบ V8 ตรวจสอบกับทีม Oz แล้ว และทีม Oz ไม่ได้เรียกใช้ "new" อันที่จริง ทีม Oz ทราบถึงข้อกำหนดนี้เป็นอย่างดีอยู่แล้ว และกล่าวว่า "นั่นคงน่าอายมาก" ขีดฆ่ารายการนี้ออกจากลิสต์ได้เลย
ผู้ต้องสงสัย #2
การแก้ไข "รูปร่าง" ของออบเจ็กต์นอกคอนสตรัคเตอร์ กรณีนี้จะเกิดขึ้นเมื่อมีการเพิ่มพร็อพเพอร์ตี้ใหม่ลงในออบเจ็กต์นอกคอนสตรัคเตอร์ ซึ่งจะสร้างคลาสที่ซ่อนอยู่ใหม่สำหรับออบเจ็กต์ เมื่อโค้ดที่เพิ่มประสิทธิภาพเห็นคลาสที่ซ่อนอยู่ใหม่นี้ ระบบจะทริกเกอร์การยกเลิกการเพิ่มประสิทธิภาพ โค้ดที่ไม่ได้เพิ่มประสิทธิภาพจะทำงานจนกว่าจะได้รับการจัดประเภทเป็นโค้ดที่ทำงานบ่อยและเพิ่มประสิทธิภาพอีกครั้ง การเลิกเพิ่มประสิทธิภาพและการเพิ่มประสิทธิภาพซ้ำนี้จะส่งผลให้เกิดความล่าช้า แต่ไม่ได้เกี่ยวข้องกับการสร้างขยะมากเกินไปอย่างเคร่งครัด หลังจากตรวจสอบโค้ดอย่างละเอียดแล้ว เรายืนยันได้ว่ารูปร่างของออบเจ็กต์เป็นแบบคงที่ จึงตัดผู้ต้องสงสัยรายที่ 2 ออกไปได้
ผู้ต้องสงสัย #3
เลขคณิตในโค้ดที่ไม่ได้เพิ่มประสิทธิภาพ ในโค้ดที่ไม่ได้เพิ่มประสิทธิภาพ ผลการคำนวณทั้งหมดจะส่งผลให้มีการจัดสรรออบเจ็กต์จริง ตัวอย่างเช่น ข้อมูลโค้ดนี้
var a = p * d;
var b = c + 3;
var c = 3.3 * dt;
point.x = a * b * c;
ส่งผลให้มีการสร้างออบเจ็กต์ HeapNumber 5 รายการ 3 รายการแรกมีไว้สำหรับตัวแปร a, b และ c รายการที่ 4 มีไว้สำหรับค่าที่ไม่ระบุตัวตน (a * b) และรายการที่ 5 มาจาก #4 * c โดยรายการที่ 5 จะกำหนดให้กับ point.x ในท้ายที่สุด
Oz ทำการดำเนินการเหล่านี้หลายพันครั้งต่อเฟรม หากการคำนวณเหล่านี้เกิดขึ้นในฟังก์ชันที่ไม่เคยได้รับการเพิ่มประสิทธิภาพ ก็อาจเป็นสาเหตุของขยะได้ เนื่องจากการคำนวณใน allocate ที่ไม่ได้เพิ่มประสิทธิภาพจะจัดสรรหน่วยความจำแม้สำหรับผลลัพธ์ชั่วคราว
ผู้ต้องสงสัย #4
การจัดเก็บตัวเลขความแม่นยำแบบ Double ลงในพร็อพเพอร์ตี้ ต้องสร้างออบเจ็กต์ HeapNumber เพื่อจัดเก็บตัวเลขและพร็อพเพอร์ตี้ที่แก้ไขให้ชี้ไปยังออบเจ็กต์ใหม่นี้ การแก้ไขพร็อพเพอร์ตี้ให้ชี้ไปที่ HeapNumber จะไม่สร้างขยะ อย่างไรก็ตาม อาจมีตัวเลขความแม่นยำแบบ Double จำนวนมากที่จัดเก็บเป็นพร็อพเพอร์ตี้ออบเจ็กต์ โค้ดเต็มไปด้วยคำสั่งต่างๆ เช่น
sprite.position.x += 0.5 * (dt);
ในโค้ดที่เพิ่มประสิทธิภาพ ทุกครั้งที่มีการกำหนดค่าที่คำนวณใหม่ให้กับ x ซึ่งเป็นคำสั่งที่ดูเหมือนไม่มีอันตราย ระบบจะจัดสรรออบเจ็กต์ HeapNumber ใหม่โดยนัย ซึ่งทำให้เราเข้าใกล้การหยุดชั่วคราวเพื่อรวบรวมขยะมากขึ้น
โปรดทราบว่าการใช้อาร์เรย์ที่พิมพ์ (หรืออาร์เรย์ปกติที่มีเฉพาะค่า Double) จะช่วยหลีกเลี่ยงปัญหานี้ได้ทั้งหมด เนื่องจากระบบจะจัดสรรพื้นที่เก็บข้อมูลสำหรับตัวเลขความแม่นยำระดับ Double เพียงครั้งเดียว และการเปลี่ยนค่าซ้ำๆ ไม่จำเป็นต้องจัดสรรพื้นที่เก็บข้อมูลใหม่
ผู้ต้องสงสัยคนที่ 4 มีความเป็นไปได้
การสืบค้นเบาะแสเกี่ยวกับนิติวิทยาศาสตร์
ณ จุดนี้ นักสืบมีผู้ต้องสงสัย 2 ราย ได้แก่ การจัดเก็บตัวเลขฮีปเป็นพร็อพเพอร์ตี้ออบเจ็กต์และการคำนวณทางคณิตศาสตร์ที่เกิดขึ้นภายในฟังก์ชันที่ไม่ได้เพิ่มประสิทธิภาพ ถึงเวลาไปที่ห้องแล็บและระบุให้แน่ชัดว่าผู้ต้องสงสัยคนใดเป็นผู้กระทำความผิด หมายเหตุ: ในส่วนนี้ ฉันจะใช้การจำลองปัญหาที่พบในซอร์สโค้ด Oz จริง การจำลองนี้มีขนาดเล็กกว่าโค้ดต้นฉบับมาก จึงทำให้เข้าใจได้ง่ายขึ้น
การทดสอบ #1
กำลังตรวจสอบผู้ต้องสงสัยรายที่ 3 (การคำนวณทางคณิตศาสตร์ภายในฟังก์ชันที่ไม่ได้เพิ่มประสิทธิภาพ) เครื่องมือ V8 JavaScript มีระบบการบันทึกในตัวซึ่งให้ข้อมูลเชิงลึกที่ยอดเยี่ยมเกี่ยวกับสิ่งที่เกิดขึ้นเบื้องหลัง
เริ่มจาก Chrome ไม่ทำงานเลย ให้เปิด Chrome ด้วยแฟล็กต่อไปนี้
--no-sandbox --js-flags="--prof --noprof-lazy --log-timer-events"
จากนั้นการออกจาก Chrome โดยสมบูรณ์จะทำให้เกิดไฟล์บันทึก v8.log ในไดเรกทอรีปัจจุบัน
หากต้องการตีความเนื้อหาของ v8.log คุณต้องดาวน์โหลด v8 เวอร์ชันเดียวกับที่ Chrome ใช้อยู่ (ตรวจสอบ about:version) และสร้าง
หลังจากสร้าง v8 สำเร็จแล้ว คุณจะประมวลผลบันทึกโดยใช้ Tick Processor ได้ดังนี้
$ tools/linux-tick-processor /path/to/v8.log
(แทนที่ mac หรือ windows ด้วย linux โดยขึ้นอยู่กับแพลตฟอร์มของคุณ) (ต้องเรียกใช้เครื่องมือนี้จากไดเรกทอรีต้นทางระดับบนสุดใน v8)
เครื่องมือประมวลผลขีดจะแสดงตารางแบบข้อความของฟังก์ชัน JavaScript ที่มีขีดมากที่สุด
[JavaScript]:
ticks total nonlib name
167 61.2% 61.2% LazyCompile: *opt demo.js:12
40 14.7% 14.7% LazyCompile: unopt demo.js:20
15 5.5% 5.5% Stub: KeyedLoadElementStub
13 4.8% 4.8% Stub: BinaryOpStub_MUL_Alloc_Number+Smi
6 2.2% 2.2% Stub: BinaryOpStub_ADD_OverwriteRight_Number+Number
4 1.5% 1.5% Stub: KeyedStoreElementStub
4 1.5% 1.5% KeyedLoadIC: {12}
2 0.7% 0.7% KeyedStoreIC: {13}
1 0.4% 0.4% LazyCompile: ~main demo.js:30
คุณจะเห็นว่า demo.js มีฟังก์ชัน 3 รายการ ได้แก่ opt, unopt และ main ฟังก์ชันที่เพิ่มประสิทธิภาพจะมีเครื่องหมายดอกจัน (*) อยู่ข้างชื่อ สังเกตว่าฟังก์ชัน opt ได้รับการเพิ่มประสิทธิภาพแล้ว และ unopt ยังไม่ได้รับการเพิ่มประสิทธิภาพ
เครื่องมือสำคัญอีกอย่างหนึ่งในกระเป๋าเครื่องมือของนักสืบ V8 คือ plot-timer-event ซึ่งสามารถดำเนินการได้ดังนี้
$ tools/plot-timer-event /path/to/v8.log
หลังจากเรียกใช้แล้ว ไฟล์ png ชื่อ timer-events.png จะอยู่ในไดเรกทอรีปัจจุบัน เมื่อเปิดขึ้นมา คุณจะเห็นหน้าตาประมาณนี้
นอกจากกราฟที่ด้านล่างแล้ว ข้อมูลจะแสดงเป็นแถว แกน X คือเวลา (มิลลิวินาที) ด้านซ้ายมือจะมีป้ายกำกับสำหรับแต่ละแถว ดังนี้
แถว V8.Execute มีเส้นแนวตั้งสีดำที่ขีดไว้ที่ขีดโปรไฟล์แต่ละขีดที่ V8 เรียกใช้โค้ด JavaScript V8.GCScavenger มีเส้นแนวตั้งสีน้ำเงินที่วาดไว้ที่ขีดโปรไฟล์แต่ละขีดที่ V8 ทำการรวบรวมข้อมูลรุ่นใหม่ และสถานะ V8 ที่เหลือก็เป็นไปในลักษณะเดียวกัน
แถวที่สำคัญที่สุดแถวหนึ่งคือ "ประเภทโค้ดที่กำลังดำเนินการ" โดยจะเป็นสีเขียวเมื่อมีการเรียกใช้โค้ดที่เพิ่มประสิทธิภาพ และเป็นสีแดงและสีน้ำเงินผสมกันเมื่อมีการเรียกใช้โค้ดที่ไม่ได้เพิ่มประสิทธิภาพ ภาพหน้าจอต่อไปนี้แสดงการเปลี่ยนจากโค้ดที่เพิ่มประสิทธิภาพแล้วเป็นโค้ดที่ไม่ได้เพิ่มประสิทธิภาพ แล้วกลับไปเป็นโค้ดที่เพิ่มประสิทธิภาพแล้ว
ตามหลักการแล้ว เส้นนี้ควรเป็นสีเขียวทึบ แต่จะไม่เกิดขึ้นทันที ซึ่งหมายความว่าโปรแกรมของคุณได้เปลี่ยนไปสู่สถานะคงที่ที่เพิ่มประสิทธิภาพแล้ว โค้ดที่ไม่ได้เพิ่มประสิทธิภาพจะทำงานช้ากว่าโค้ดที่เพิ่มประสิทธิภาพเสมอ
หากคุณทำตามขั้นตอนเหล่านี้แล้ว โปรดทราบว่าคุณจะทำงานได้เร็วขึ้นมากด้วยการเปลี่ยนโครงสร้างภายในโค้ดของแอปพลิเคชันเพื่อให้ทำงานใน Shell การแก้ไขข้อบกพร่อง v8: d8 ได้ การใช้ d8 ช่วยให้คุณทำซ้ำได้เร็วขึ้นด้วยเครื่องมือ tick-processor และ plot-timer-event ผลข้างเคียงอีกอย่างหนึ่งของการใช้ d8 คือช่วยให้แยกปัญหาที่แท้จริงได้ง่ายขึ้น ซึ่งจะช่วยลดปริมาณข้อมูลรบกวนในข้อมูล
เมื่อดูพล็อตเหตุการณ์ตัวจับเวลาจากซอร์สโค้ด Oz จะเห็นการเปลี่ยนจากโค้ดที่เพิ่มประสิทธิภาพเป็นโค้ดที่ไม่ได้เพิ่มประสิทธิภาพ และขณะที่เรียกใช้โค้ดที่ไม่ได้เพิ่มประสิทธิภาพ ระบบจะทริกเกอร์คอลเล็กชันรุ่นใหม่จำนวนมาก ดังภาพหน้าจอต่อไปนี้ (โปรดทราบว่าระบบนำเวลาตรงกลางออกแล้ว)
หากดูอย่างละเอียด คุณจะเห็นว่าเส้นสีดำที่ระบุว่า V8 กำลังเรียกใช้โค้ด JavaScript นั้นหายไปในเวลาที่ตรงกันกับเครื่องหมายขีดของโปรไฟล์ที่ระบุการเก็บรวบรวมข้อมูลของรุ่นใหม่ (เส้นสีน้ำเงิน) ซึ่งแสดงให้เห็นอย่างชัดเจนว่าขณะที่ระบบกำลังรวบรวมขยะ สคริปต์จะหยุดชั่วคราว
เมื่อดูเอาต์พุตของตัวประมวลผลขีดจากซอร์สโค้ด Oz ฟังก์ชันด้านบน (updateSprites) ไม่ได้รับการเพิ่มประสิทธิภาพ กล่าวคือ ฟังก์ชันที่โปรแกรมใช้เวลามากที่สุดก็ยังไม่ได้รับการเพิ่มประสิทธิภาพด้วย ซึ่งบ่งชี้อย่างชัดเจนว่าผู้ต้องสงสัยคนที่ 3 คือคนร้าย แหล่งที่มาของ updateSprites มีลูปที่มีลักษณะดังนี้
function updateSprites(dt) {
for (var sprite in sprites) {
sprite.position.x += 0.5 * dt;
// 20 more lines of arithmetic computation.
}
}
ด้วยความที่รู้จัก V8 เป็นอย่างดี พวกเขาจึงทราบทันทีว่าบางครั้ง V8 ไม่ได้เพิ่มประสิทธิภาพโครงสร้างการวนซ้ำ for-i-in กล่าวคือ หากฟังก์ชันมีโครงสร้างการวนซ้ำ for-i-in ระบบอาจไม่เพิ่มประสิทธิภาพฟังก์ชันดังกล่าว กรณีนี้เป็นกรณีพิเศษในปัจจุบัน และมีแนวโน้มที่จะเปลี่ยนแปลงในอนาคต กล่าวคือ V8 อาจเพิ่มประสิทธิภาพโครงสร้างการวนซ้ำนี้ได้ในวันหนึ่ง เนื่องจากเราไม่ใช่นักสืบ V8 และไม่รู้จัก V8 อย่างทะลุปรุโปร่ง เราจะระบุได้อย่างไรว่าเหตุใด updateSprites จึงไม่ได้รับการเพิ่มประสิทธิภาพ
การทดสอบ #2
การเรียกใช้ Chrome ด้วย Flag นี้
--js-flags="--trace-deopt --trace-opt-verbose"
แสดงบันทึกแบบละเอียดของข้อมูลการเพิ่มประสิทธิภาพและการยกเลิกการเพิ่มประสิทธิภาพ เมื่อค้นหาข้อมูล updateSprites เราจะพบข้อมูลต่อไปนี้
[disabled optimization for updateSprites, reason: ForInStatement is not fast case]
โครงสร้างการวนซ้ำ for-i-in เป็นสาเหตุของปัญหาตามที่นักสืบตั้งสมมติฐานไว้จริงๆ
ปิดเคสแล้ว
หลังจากค้นพบสาเหตุที่ updateSprites ไม่ได้รับการเพิ่มประสิทธิภาพ การแก้ไขก็ง่ายมาก เพียงย้ายการคำนวณไปยังฟังก์ชันของตัวเอง ดังนี้
function updateSprite(sprite, dt) {
sprite.position.x += 0.5 * dt;
// 20 more lines of arithmetic computation.
}
function updateSprites(dt) {
for (var sprite in sprites) {
updateSprite(sprite, dt);
}
}
updateSprite จะได้รับการเพิ่มประสิทธิภาพ ส่งผลให้มีออบเจ็กต์ HeapNumber น้อยลงมาก ซึ่งส่งผลให้การหยุดชั่วคราวของ GC เกิดขึ้นน้อยลง คุณสามารถยืนยันเรื่องนี้ได้ง่ายๆ โดยทำการทดสอบแบบเดียวกันด้วยโค้ดใหม่ ผู้อ่านที่สังเกตอย่างละเอียดจะเห็นว่าระบบยังคงจัดเก็บตัวเลข Double เป็นพร็อพเพอร์ตี้ หากการทำโปรไฟล์ระบุว่าคุ้มค่า การเปลี่ยนตำแหน่งเป็นอาร์เรย์ของ Double หรืออาร์เรย์ข้อมูลที่พิมพ์จะช่วยลดจำนวนออบเจ็กต์ที่สร้างขึ้นได้อีก
บทส่งท้าย
นักพัฒนา Oz ไม่ได้หยุดอยู่แค่นั้น เมื่อมีเครื่องมือและเทคนิคที่นักสืบ V8 แชร์ให้ พวกเขาก็สามารถค้นหาฟังก์ชันอื่นๆ อีก 2-3 ฟังก์ชันที่ติดอยู่ในวงจรการลดประสิทธิภาพ และแยกโค้ดการคำนวณออกเป็นฟังก์ชัน Leaf ที่ได้รับการเพิ่มประสิทธิภาพ ซึ่งส่งผลให้มีประสิทธิภาพดียิ่งขึ้น
ออกไปไขคดีอาชญากรรมด้านประสิทธิภาพกันเลย