ใช้การพิสูจน์หลักฐานและการทำงานเชิงสืบสวนเพื่อไขปริศนาประสิทธิภาพของ JavaScript

บทนำ

ในช่วงไม่กี่ปีที่ผ่านมา เว็บแอปพลิเคชันทำงานได้เร็วขึ้นมาก ปัจจุบันแอปพลิเคชันจำนวนมากทำงานได้เร็วพอ เราได้ยินว่านักพัฒนาแอปบางรายสงสัยว่า "เว็บเร็วพอไหม" สำหรับแอปพลิเคชันบางรายการก็อาจเร็ว แต่สำหรับนักพัฒนาแอปพลิเคชันประสิทธิภาพสูง เราทราบดีว่าความเร็วนี้ไม่เพียงพอ แม้เทคโนโลยีเครื่องเสมือน 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 บล็อกนี้จะใช้เพียงบล็อกเดียวในแต่ละช่วงเวลา และบล็อกดังกล่าวเรียกว่าพื้นที่ "ไปยัง" เมื่อมีหน่วยความจำเหลืออยู่ในพื้นที่เป้าหมาย การจัดสรรออบเจ็กต์ใหม่จะเสียค่าใช้จ่ายน้อย เคอร์เซอร์ในพื้นที่ "ไปยัง" จะเลื่อนไปข้างหน้าตามจำนวนไบต์ที่จําเป็นสําหรับออบเจ็กต์ใหม่ ซึ่งจะดำเนินต่อไปจนกว่าพื้นที่ในช่อง "ถึง" จะเต็ม เมื่อถึงจุดนี้ โปรแกรมจะหยุดและเริ่มรวบรวม

หน่วยความจํา V8 ใหม่

เมื่อถึงจุดนี้ ระบบจะสลับพื้นที่ทำงานต้นทางและปลายทาง ระบบจะสแกนพื้นที่จากซึ่งปัจจุบันคือพื้นที่ต้นทางตั้งแต่ต้นจนจบ และคัดลอกออบเจ็กต์ที่ยังอยู่ไปยังพื้นที่ปลายทางหรือโปรโมตไปยังกองรุ่นเก่า หากต้องการรายละเอียด เราขอแนะนำให้อ่านเกี่ยวกับอัลกอริทึมของ 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 คือเวลา (มิลลิวินาที) ทางด้านซ้ายมือจะมีป้ายกำกับสำหรับแต่ละแถว ดังนี้

แกน Y ของเหตุการณ์ตัวจับเวลา

แถว 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 รายการที่ตกอยู่ในสถานการณ์ที่ยากลำบากในการเพิ่มประสิทธิภาพ และแยกโค้ดการคํานวณออกเป็นฟังก์ชันระดับล่างซึ่งได้รับการเพิ่มประสิทธิภาพแล้ว ส่งผลให้ประสิทธิภาพดีขึ้น

ออกไปแก้ปัญหาด้านประสิทธิภาพกัน