บทนำ
ในช่วงไม่กี่ปีที่ผ่านมา เว็บแอปพลิเคชันทำงานได้เร็วขึ้นมาก ปัจจุบันแอปพลิเคชันจำนวนมากทำงานได้เร็วพอ เราได้ยินว่านักพัฒนาแอปบางรายสงสัยว่า "เว็บเร็วพอไหม" สำหรับแอปพลิเคชันบางรายการก็อาจเร็ว แต่สำหรับนักพัฒนาแอปพลิเคชันประสิทธิภาพสูง เราทราบดีว่าความเร็วนี้ไม่เพียงพอ แม้เทคโนโลยีเครื่องเสมือน 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 เป็นเกมที่มีผลรวมเป็น 0 การทำให้ระบบส่วนหนึ่งใช้พื้นที่เก็บข้อมูลน้อยลงจะช่วยให้คุณใช้พื้นที่เก็บข้อมูลในส่วนอื่นได้มากขึ้นหรือระบบทำงานได้ราบรื่นขึ้นโดยรวม การทำงานเร็วขึ้นและทำงานได้มากขึ้นมักเป็นเป้าหมายที่ขัดแย้งกัน ผู้ใช้ต้องการฟีเจอร์ใหม่ๆ ในขณะเดียวกันก็คาดหวังว่าแอปพลิเคชันจะทำงานได้ราบรื่นขึ้น เครื่องเสมือน JavaScript ทำงานได้เร็วขึ้นเรื่อยๆ แต่นั่นไม่ใช่เหตุผลที่จะละเลยปัญหาด้านประสิทธิภาพที่คุณแก้ไขได้ในวันนี้ ดังที่นักพัฒนาแอปจำนวนมากที่จัดการกับปัญหาด้านประสิทธิภาพในเว็บแอปพลิเคชันของตนทราบดี ในแอปพลิเคชันอัตราเฟรมสูงแบบเรียลไทม์ สิ่งสำคัญที่สุดคือต้องไม่มีภาพกระตุก Insomniac Games ได้จัดทำการศึกษาซึ่งแสดงให้เห็นว่าอัตราเฟรมที่ราบรื่นและสม่ำเสมอมีความสำคัญต่อความสำเร็จของเกม "อัตราเฟรมที่ราบรื่นยังคงเป็นสัญญาณของผลิตภัณฑ์ที่มีคุณภาพและสร้างมาอย่างมืออาชีพ" หมายเหตุสำหรับนักพัฒนาเว็บ
การแก้ปัญหาด้านประสิทธิภาพ
การแก้ปัญหาด้านประสิทธิภาพก็เหมือนกับการไขคดี คุณต้องตรวจสอบหลักฐานอย่างละเอียด ตรวจสอบสาเหตุที่สงสัย และทดสอบวิธีแก้ปัญหาต่างๆ ตลอดกระบวนการนี้ คุณต้องบันทึกการวัดผลเพื่อให้มั่นใจว่าคุณได้แก้ไขปัญหาแล้ว วิธีนี้มีความคล้ายคลึงกับวิธีที่นักสืบไขคดีเป็นอย่างมาก นักสืบจะตรวจสอบหลักฐาน สอบปากคำผู้ต้องสงสัย และทำการทดสอบเพื่อหวังว่าจะพบหลักฐานที่ชี้ชัด
V8 CSI: Oz
ทีมวิซาร์ดที่ยอดเยี่ยมซึ่งสร้างแอป Find Your Way to Oz ติดต่อทีม V8 เพื่อขอความช่วยเหลือเกี่ยวกับปัญหาด้านประสิทธิภาพที่พวกเขาแก้ไขด้วยตนเองไม่ได้ บางครั้ง Oz จะค้าง ทำให้แอปทำงานขัดข้อง นักพัฒนาซอฟต์แวร์ของ Oz ได้ดำเนินการตรวจสอบเบื้องต้นโดยใช้แผงไทม์ไลน์ใน เครื่องมือสำหรับนักพัฒนาเว็บใน Chrome เมื่อดูที่การใช้งานหน่วยความจํา พวกเขาพบกราฟฟันเลื่อยที่น่ากลัว โปรแกรมเก็บขยะรวบรวมขยะ 10 MB ทุกๆ วินาที และหยุดรวบรวมขยะชั่วคราวเมื่อเกิดปัญหาการกระตุก คล้ายกับภาพหน้าจอต่อไปนี้จากไทม์ไลน์ในเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome
นักสืบ V8 อย่าง Jakob และ Yang เป็นผู้ดูแลเคสนี้ สิ่งที่เกิดขึ้นคือ Jakob และ Yang จากทีม V8 และทีม Oz พูดคุยกันเป็นเวลานาน เราได้สรุปการสนทนานี้ให้เหลือเฉพาะเหตุการณ์สำคัญซึ่งช่วยติดตามปัญหานี้
หลักฐาน
ขั้นตอนแรกคือการรวบรวมและศึกษาหลักฐานเบื้องต้น
เรากําลังดูใบสมัครประเภทใด
การสาธิต Oz เป็นแอปพลิเคชัน 3 มิติแบบอินเทอร์แอกทีฟ ด้วยเหตุนี้ ข้อมูลจึงไวต่อการหยุดชั่วคราวที่เกิดจากการรวบรวมขยะ โปรดทราบว่าแอปพลิเคชันแบบอินเทอร์แอกทีฟที่ทำงานด้วยอัตราเฟรม 60 fps มีเวลา 16 มิลลิวินาทีในการทํางาน JavaScript ทั้งหมด และต้องเหลือเวลาบางส่วนให้ Chrome ประมวลผลการเรียกใช้กราฟิกและการวาดหน้าจอ
Oz ดำเนินการคํานวณทางคณิตศาสตร์จํานวนมากกับค่า Double และเรียกใช้ WebAudio และ WebGL บ่อยครั้ง
เราพบปัญหาด้านประสิทธิภาพประเภทใด
เราเห็นการหยุดชั่วคราวหรือเฟรมตกหรือกระตุก การหยุดชั่วคราวเหล่านี้เกี่ยวข้องกับการทำงานของการเก็บขยะ
นักพัฒนาแอปทําตามแนวทางปฏิบัติแนะนําหรือไม่
ใช่ นักพัฒนาซอฟต์แวร์ของ Oz เชี่ยวชาญด้านเทคนิคการเพิ่มประสิทธิภาพและประสิทธิภาพของ VM JavaScript โปรดทราบว่านักพัฒนาซอฟต์แวร์ Oz ใช้ CoffeeScript เป็นภาษาต้นทางและสร้างโค้ด JavaScript ผ่านคอมไพเลอร์ CoffeeScript การตรวจสอบจึงทําได้ยากขึ้นเนื่องจากโค้ดที่นักพัฒนาซอฟต์แวร์ Oz เขียนขึ้นไม่ตรงกับโค้ดที่ V8 ใช้ ตอนนี้เครื่องมือสำหรับนักพัฒนาเว็บใน Chrome รองรับการแมปแหล่งที่มา ซึ่งจะช่วยให้ดำเนินการได้ง่ายขึ้น
เหตุใดจึงมีการเรียกใช้โปรแกรมเก็บขยะ
VM จะจัดการหน่วยความจําใน JavaScript ให้นักพัฒนาแอปโดยอัตโนมัติ V8 ใช้ระบบจัดการหน่วยความจำที่ไม่ใช้แล้วแบบทั่วไปที่แบ่งหน่วยความจำออกเป็น 2 (หรือมากกว่า) รุ่น รุ่นใหม่จะเก็บออบเจ็กต์ที่เพิ่งจัดสรร หากออบเจ็กต์มีอายุการใช้งานนานพอ ระบบจะย้ายออบเจ็กต์ดังกล่าวไปยังรุ่นเก่า
ระบบจะรวบรวมข้อมูลรุ่นที่อายุน้อยด้วยความถี่สูงกว่าการเก็บรวบรวมข้อมูลรุ่นที่มีอายุมาก การดำเนินการนี้เป็นไปตามการออกแบบ เนื่องจากคอลเล็กชันรุ่นที่อายุน้อยกว่านั้นมีราคาถูกกว่ามาก บ่อยครั้งที่การหยุด GC บ่อยๆ เกิดจากการเก็บรวบรวมรุ่นเยาว์
ใน V8 พื้นที่หน่วยความจำใหม่จะแบ่งออกเป็น 2 บล็อกหน่วยความจำขนาดเท่าๆ กันที่อยู่ติดกัน หน่วยความจํา 2 บล็อกนี้จะใช้เพียงบล็อกเดียวในแต่ละช่วงเวลา และบล็อกดังกล่าวเรียกว่าพื้นที่ "ไปยัง" เมื่อมีหน่วยความจำเหลืออยู่ในพื้นที่เป้าหมาย การจัดสรรออบเจ็กต์ใหม่จะเสียค่าใช้จ่ายน้อย เคอร์เซอร์ในพื้นที่ "ไปยัง" จะเลื่อนไปข้างหน้าตามจำนวนไบต์ที่จําเป็นสําหรับออบเจ็กต์ใหม่ ซึ่งจะดำเนินต่อไปจนกว่าพื้นที่ในช่อง "ถึง" จะเต็ม เมื่อถึงจุดนี้ โปรแกรมจะหยุดและเริ่มรวบรวม
เมื่อถึงจุดนี้ ระบบจะสลับพื้นที่ทำงานต้นทางและปลายทาง ระบบจะสแกนพื้นที่จากซึ่งปัจจุบันคือพื้นที่ต้นทางตั้งแต่ต้นจนจบ และคัดลอกออบเจ็กต์ที่ยังอยู่ไปยังพื้นที่ปลายทางหรือโปรโมตไปยังกองรุ่นเก่า หากต้องการรายละเอียด เราขอแนะนำให้อ่านเกี่ยวกับอัลกอริทึมของ Cheney
คุณควรเข้าใจว่าทุกครั้งที่มีการจองหน่วยความจำให้กับออบเจ็กต์โดยนัยหรือโดยชัดแจ้ง (ผ่านการเรียกใช้ new, [], หรือ {}) แอปพลิเคชันของคุณก็เข้าใกล้การเก็บขยะและการหยุดแอปพลิเคชันชั่วคราวที่น่ากลัวมากขึ้นเรื่อยๆ
ปริมาณข้อมูลขยะ 10 MB/วินาทีสำหรับแอปพลิเคชันนี้เป็นสิ่งที่คาดไว้ใช่ไหม
สั้นๆ ก็คือไม่ นักพัฒนาแอปไม่ได้ทำอะไรที่คาดว่าจะมีขยะ 10 MB/วินาที
ผู้ต้องสงสัย
ระยะถัดไปของการตรวจสอบคือการระบุผู้ต้องสงสัยที่เป็นไปได้ แล้วค่อยๆ ตัดรายชื่อผู้ต้องสงสัยให้เหลือน้อยลง
ผู้ต้องสงสัย #1
การเรียกใช้ใหม่ระหว่างเฟรม โปรดทราบว่าออบเจ็กต์แต่ละรายการที่จัดสรรจะทําให้คุณเข้าใกล้การหยุดชั่วคราวของ GC มากขึ้น แอปพลิเคชันที่ทำงานในอัตราเฟรมสูงควรพยายามจัดสรรเป็น 0 ต่อเฟรม ซึ่งโดยปกติแล้วจะต้องมีระบบการรีไซเคิลออบเจ็กต์เฉพาะแอปพลิเคชันซึ่งต้องพิจารณาอย่างรอบคอบ นักสืบ V8 ตรวจสอบกับทีม Oz แล้ว และทีมไม่ได้เรียกใช้ใหม่ อันที่จริงแล้ว ทีม 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 ดำเนินการเหล่านี้หลายพันครั้งต่อเฟรม หากการคํานวณเหล่านี้เกิดขึ้นในฟังก์ชันที่ไม่เคยได้รับการเพิ่มประสิทธิภาพ อาจเป็นสาเหตุของขยะ เนื่องจากการคำนวณที่ไม่ได้เพิ่มประสิทธิภาพจะจัดสรรหน่วยความจำแม้สำหรับผลลัพธ์ชั่วคราว
ผู้ต้องสงสัย #4
การจัดเก็บตัวเลขความแม่นยําสูงลงในพร็อพเพอร์ตี้ คุณต้องสร้างออบเจ็กต์ HeapNumber เพื่อจัดเก็บตัวเลขและแก้ไขพร็อพเพอร์ตี้ให้ชี้ไปยังออบเจ็กต์ใหม่นี้ การเปลี่ยนพร็อพเพอร์ตี้ให้ชี้ไปที่ HeapNumber จะไม่ทำให้เกิดขยะ อย่างไรก็ตาม อาจมีตัวเลขแบบความละเอียดสูงจํานวนมากที่จัดเก็บเป็นพร็อพเพอร์ตี้ออบเจ็กต์ โค้ดเต็มไปด้วยคำสั่งต่างๆ เช่น
sprite.position.x += 0.5 * (dt);
ในโค้ดที่ได้รับการเพิ่มประสิทธิภาพ ทุกครั้งที่มีการกำหนดค่าที่คำนวณใหม่ให้กับ x ซึ่งเป็นคำสั่งที่ดูเหมือนจะไม่เป็นอันตราย ระบบจะจัดสรรออบเจ็กต์ HeapNumber ใหม่โดยปริยาย ซึ่งทำให้เราเข้าใกล้การหยุดการรวบรวมขยะมากขึ้น
โปรดทราบว่าการใช้อาร์เรย์ที่มีประเภท (หรืออาร์เรย์ปกติที่มีเฉพาะตัวเลขทศนิยมแบบละเอียด) จะช่วยให้คุณหลีกเลี่ยงปัญหานี้ได้อย่างสิ้นเชิง เนื่องจากระบบจะจัดสรรพื้นที่เก็บข้อมูลสำหรับตัวเลขทศนิยมแบบละเอียดเพียงครั้งเดียว และการเปลี่ยนค่าซ้ำๆ จะไม่จําเป็นต้องจัดสรรพื้นที่เก็บข้อมูลใหม่
ผู้ต้องสงสัย #4 อาจเป็นผู้ที่ก่อเหตุ
การสืบค้นเบาะแสเกี่ยวกับนิติวิทยาศาสตร์
เมื่อถึงจุดนี้ นักสืบมี 2 ตัวเลือกที่น่าสงสัย ได้แก่ การจัดเก็บตัวเลขกองเป็นพร็อพเพอร์ตี้ออบเจ็กต์ และการคํานวณทางคณิตศาสตร์ที่เกิดขึ้นภายในฟังก์ชันที่ไม่ได้เพิ่มประสิทธิภาพ ถึงเวลาไปที่ห้องทดลองเพื่อหาว่าผู้ต้องสงสัยคนไหนเป็นผู้กระทำผิด หมายเหตุ: ในส่วนนี้ เราจะใช้การจำลองปัญหาที่พบในซอร์สโค้ดจริงของ Oz การทําซ้ำนี้มีขนาดน้อยกว่าโค้ดต้นฉบับหลายเท่า จึงทําให้หาเหตุผลได้ง่ายขึ้น
การทดสอบ #1
กำลังตรวจสอบผู้ต้องสงสัย #3 (การคํานวณเชิงตัวเลขภายในฟังก์ชันที่ไม่ได้เพิ่มประสิทธิภาพ) เครื่องมือ JavaScript V8 มีระบบการบันทึกในตัวซึ่งให้ข้อมูลเชิงลึกที่ยอดเยี่ยมเกี่ยวกับสิ่งที่เกิดขึ้น
เริ่มจาก Chrome ไม่ทำงานเลย ให้เปิด Chrome ด้วย Flag ต่อไปนี้
--no-sandbox --js-flags="--prof --noprof-lazy --log-timer-events"
จากนั้นการปิด Chrome โดยสมบูรณ์จะส่งผลให้มีไฟล์ v8.log ในไดเรกทอรีปัจจุบัน
หากต้องการตีความเนื้อหาของ v8.log คุณต้องดาวน์โหลด v8 เวอร์ชันเดียวกับที่ Chrome ใช้อยู่ (ดูที่ about:version) และสร้าง
หลังจากสร้าง v8 สำเร็จแล้ว คุณสามารถประมวลผลบันทึกโดยใช้โปรแกรมประมวลผลการนับเวลาดังนี้
$ 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
แถวที่สําคัญที่สุดแถวหนึ่งคือ "ประเภทโค้ดที่กําลังเรียกใช้" ไฟจะเปลี่ยนเป็นสีเขียวเมื่อโค้ดที่ได้รับการเพิ่มประสิทธิภาพกำลังทำงาน และเปลี่ยนเป็นสีแดงปนน้ำเงินเมื่อโค้ดที่ไม่มีการเพิ่มประสิทธิภาพกำลังทำงาน ภาพหน้าจอต่อไปนี้แสดงการเปลี่ยนจากโค้ดที่เพิ่มประสิทธิภาพเป็นโค้ดที่ไม่ได้เพิ่มประสิทธิภาพ แล้วกลับไปเป็นโค้ดที่เพิ่มประสิทธิภาพ
ตามหลักการแล้ว เส้นนี้จะเปลี่ยนเป็นสีเขียวสนิทในทันที แต่อาจไม่เกิดขึ้นในทันที ซึ่งหมายความว่าโปรแกรมได้เปลี่ยนเป็นสถานะคงที่ที่ได้รับการเพิ่มประสิทธิภาพแล้ว โค้ดที่ไม่ได้เพิ่มประสิทธิภาพจะทำงานช้ากว่าโค้ดที่เพิ่มประสิทธิภาพเสมอ
หากทําตามขั้นตอนนี้ คุณควรทราบว่าคุณจะทํางานได้เร็วขึ้นมากด้วยการรีแฟกทอริงแอปพลิเคชันเพื่อให้ทํางานในเชลล์แก้ไขข้อบกพร่อง v8: d8 ได้ การใช้ d8 จะช่วยให้การวนซ้ำเร็วขึ้นด้วยเครื่องมือ tick-processor และ plot-timer-event ผลข้างเคียงอีกอย่างหนึ่งของการใช้ d8 คือช่วยให้แยกปัญหาจริงได้ง่ายขึ้น ซึ่งจะช่วยลดปริมาณข้อมูลรบกวนในข้อมูล
เมื่อดูผังเหตุการณ์ตัวจับเวลาจากซอร์สโค้ดของ Oz พบว่ามีการเปลี่ยนจากโค้ดที่ได้รับการเพิ่มประสิทธิภาพเป็นโค้ดที่ไม่ได้เพิ่มประสิทธิภาพ และขณะที่เรียกใช้โค้ดที่ไม่ได้เพิ่มประสิทธิภาพ ระบบจะทริกเกอร์คอลเล็กชันรุ่นใหม่จำนวนมาก ซึ่งคล้ายกับภาพหน้าจอต่อไปนี้ (โปรดทราบว่ามีการนําเวลาตรงกลางออกแล้ว)
หากมองอย่างละเอียด คุณจะเห็นเส้นสีดําที่ระบุว่า V8 กำลังเรียกใช้โค้ด JavaScript หายไปในช่วงเวลาการนับโปรไฟล์เดียวกันกับคอลเล็กชันรุ่นใหม่ (เส้นสีน้ำเงิน) ข้อมูลนี้แสดงให้เห็นอย่างชัดเจนว่าขณะที่ระบบกำลังรวบรวมขยะอยู่ สคริปต์จะหยุดชั่วคราว
เมื่อดูเอาต์พุตของ Tick Processor จากซอร์สโค้ดของ 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 ที่มีการแฟล็กนี้
--js-flags="--trace-deopt --trace-opt-verbose"
แสดงบันทึกแบบละเอียดของข้อมูลการเพิ่มประสิทธิภาพและการยกเลิกการเพิ่มประสิทธิภาพ เมื่อค้นหาข้อมูลสำหรับ updateSprites เราพบข้อมูลต่อไปนี้
[ปิดใช้การเพิ่มประสิทธิภาพสำหรับ updateSprites เนื่องจาก ForInStatement ไม่ใช่ 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 เกิดขึ้นน้อยลง คุณยืนยันได้โดยทําการทดสอบแบบเดียวกันด้วยรหัสใหม่ ผู้อ่านที่ละเอียดรอบคอบจะสังเกตเห็นว่าระบบยังคงจัดเก็บตัวเลขทศนิยมเป็นพร็อพเพอร์ตี้ หากการโปรไฟล์บ่งชี้ว่าคุ้มค่า การเปลี่ยนตำแหน่งให้เป็นอาร์เรย์ของเลขทศนิยมหรืออาร์เรย์ข้อมูลที่กําหนดประเภทจะช่วยลดจํานวนออบเจ็กต์ที่สร้างขึ้นได้
บทส่งท้าย
แต่นักพัฒนาแอป Oz ไม่ได้หยุดอยู่แค่นั้น เมื่อใช้เครื่องมือและเทคนิคที่นักสืบ V8 แชร์ให้ ทีมก็สามารถพบฟังก์ชันอื่นๆ อีก 2-3 รายการที่ตกอยู่ในสถานการณ์ที่ยากลำบากในการเพิ่มประสิทธิภาพ และแยกโค้ดการคํานวณออกเป็นฟังก์ชันระดับล่างซึ่งได้รับการเพิ่มประสิทธิภาพแล้ว ส่งผลให้ประสิทธิภาพดีขึ้น
ออกไปแก้ปัญหาด้านประสิทธิภาพกัน