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

เกริ่นนำ

ในช่วงไม่กี่ปีที่ผ่านมา เว็บแอปพลิเคชันมีประสิทธิภาพเพิ่มขึ้นอย่างมาก ตอนนี้แอปพลิเคชันจำนวนมากทำงานเร็วพอ ผมได้ยินนักพัฒนาซอฟต์แวร์บางคนถามว่า "เว็บเร็วพอไหม" อาจใช้ได้กับบางแอปพลิเคชัน แต่สำหรับนักพัฒนาซอฟต์แวร์ที่ทำงานในแอปพลิเคชันประสิทธิภาพสูง เราทราบดีว่ายังเร็วไม่เพียงพอ แม้ว่าเทคโนโลยีเครื่องเสมือน JavaScript จะพัฒนาไปอย่างน่าทึ่ง แต่การศึกษาล่าสุดแสดงให้เห็นว่าแอปพลิเคชันของ Google ใช้เวลาอยู่ใน V8 ถึง 50% ถึง 70% แอปพลิเคชันของคุณมีระยะเวลาจำกัด ดังนั้น การตัดรอบการทำงานจากระบบหนึ่งออกไปทำให้อีกระบบหนึ่งสามารถทำสิ่งต่างๆ ได้มากกว่า โปรดทราบว่าแอปพลิเคชันที่ทำงานที่ 60fps จะมีเวลาเพียง 16 มิลลิวินาทีต่อเฟรมเท่านั้น หรืออื่นๆ อาจเป็น jank อ่านต่อเพื่อดูข้อมูลเกี่ยวกับการเพิ่มประสิทธิภาพ 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 ได้ดำเนินการตรวจสอบเบื้องต้นบางส่วนโดยใช้แผงไทม์ไลน์ในเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome เมื่อดูการใช้งานหน่วยความจำ นักเรียนก็พบกราฟฟันเลื่อยที่น่าสะพรึงกลัว เครื่องเก็บขยะกำลังเก็บขยะ 10 MB ต่อวินาที และการหยุดเก็บขยะชั่วคราวสอดคล้องกับการทิ้งขยะ คล้ายกับภาพหน้าจอต่อไปนี้จากไทม์ไลน์ในเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome

ไทม์ไลน์ของเครื่องมือนักพัฒนาเว็บ

นักสืบ V8 Jakob และ Yang รับคดีนี้ สิ่งที่เกิดขึ้นระหว่าง Jakob กับ Yang จากทีม V8 และทีม Oz นั้นเกิดขึ้นติดต่อกันนาน ผมได้ปรับแต่งการสนทนานี้เพื่อให้เห็นเหตุการณ์สำคัญๆ ที่ช่วยแก้ไขปัญหานี้

หลักฐาน

ขั้นตอนแรกคือรวบรวมและศึกษาหลักฐานเบื้องต้น

เรากําลังพิจารณาใบสมัครประเภทใด

การสาธิตออซเป็นแอปพลิเคชัน 3 มิติแบบอินเทอร์แอกทีฟ ด้วยเหตุนี้ การหยุดชั่วคราวเนื่องจากการรวบรวมขยะจึงเป็นเรื่องละเอียดอ่อนมาก โปรดจำไว้ว่าแอปพลิเคชันแบบอินเทอร์แอกทีฟที่ทำงานที่ 60fps มีเวลา 16 มิลลิวินาทีในการทำงาน JavaScript ทั้งหมดและจะต้องเผื่อเวลาไว้เพื่อให้ Chrome ประมวลผลการเรียกกราฟิกและวาดหน้าจอ

Oz ทำการคำนวณทางเลขคณิตเป็นจำนวนมากกับค่าทศนิยมและเรียก WebAudio และ WebGL เป็นประจำ

เรากำลังประสบปัญหาด้านประสิทธิภาพประเภทใด

เราเห็นการหยุดชั่วคราวหรือที่เรียกว่าเฟรมหลุดหรือที่เรียกว่าความยุ่งยาก การหยุดชั่วคราวเหล่านี้สัมพันธ์กับการเรียกใช้การเก็บขยะ

นักพัฒนาซอฟต์แวร์ทำตามแนวทางปฏิบัติแนะนำไหม

ใช่ นักพัฒนาซอฟต์แวร์ Oz เชี่ยวชาญในเรื่องการเพิ่มประสิทธิภาพ VM ของ JavaScript และเทคนิคการเพิ่มประสิทธิภาพ นักพัฒนาซอฟต์แวร์ Oz ได้ใช้ CoffeeScript เป็นภาษาต้นฉบับและสร้างโค้ด JavaScript ผ่านคอมไพเลอร์ CoffeeScript ซึ่งทำให้การตรวจสอบบางส่วนเป็นไปได้ยากขึ้น เนื่องจากนักพัฒนาซอฟต์แวร์ Oz ที่เขียนโค้ดไว้และโค้ดที่ V8 ใช้อยู่ต่างไปจากเดิม ตอนนี้เครื่องมือสำหรับนักพัฒนาเว็บใน Chrome รองรับแผนที่แหล่งที่มาแล้ว ซึ่งจะช่วยให้ดำเนินการได้ง่ายขึ้น

ทำไมระบบเก็บขยะถึงทำงาน

VM จะจัดการหน่วยความจำใน JavaScript ให้นักพัฒนาซอฟต์แวร์โดยอัตโนมัติ V8 ใช้ระบบรวบรวมขยะทั่วไปซึ่งแบ่งหน่วยความจำเป็นgenerations 2 (ขึ้นไป) เด็กรุ่นใหม่ถือวัตถุที่ได้รับการจัดสรรเมื่อเร็วๆ นี้ หากวัตถุยังคงอยู่ได้นานพอ วัตถุนั้นก็จะถูกย้ายไปยังยุคเก่า

คนรุ่นใหม่ได้รับการรวบรวมบ่อยกว่าคนรุ่นเก่ามาก เป็นการออกแบบเพราะคอลเล็กชันของคนรุ่นใหม่มีราคาถูกกว่ามาก มักคาดเดาได้ว่าการหยุด GC ชั่วคราวเกิดจากการเก็บรวบรวมข้อมูลของคนรุ่นเยาว์

ใน V8 พื้นที่ความทรงจำในวัยเยาว์จะแบ่งออกเป็น 2 บล็อกหน่วยความจำติดต่อกันที่มีขนาดเท่ากัน มีการใช้หน่วยความจำเพียง 1 ใน 2 บล็อกนี้ในช่วงเวลาหนึ่งๆ หน่วยความจำนี้เรียกว่า "พื้นที่" แม้จะมีหน่วยความจำเหลืออยู่ในพื้นที่ดังกล่าว แต่การจัดสรรออบเจ็กต์ใหม่จะมีราคาถูก เลื่อนเคอร์เซอร์ไปยังช่องว่างไปข้างหน้าตามจำนวนไบต์ที่ต้องใช้สำหรับออบเจ็กต์ใหม่ การดําเนินการนี้จะดำเนินต่อไปจนกว่าพื้นที่ว่างจะเต็ม ณ จุดนี้โปรแกรมจะหยุดลงและเริ่มการเก็บรวบรวม

ความทรงจำของเด็ก V8

จุดนี้สลับจากอวกาศและไปยังอวกาศแล้ว สิ่งที่ "ไปยังอวกาศ" และตอนนี้คือ "จากอวกาศ" จะถูกสแกนตั้งแต่ต้นจนจบ และวัตถุที่ยังมีชีวิตจะถูกคัดลอกไปยังอวกาศหรือได้รับการโปรโมตเป็นฮีปยุคเก่า หากต้องการรายละเอียด เราขอแนะนําให้อ่านเกี่ยวกับอัลกอริทึมของเชนีย์

ตามความเป็นจริง คุณควรเข้าใจว่าทุกครั้งที่มีการจัดสรรออบเจ็กต์ทั้งแบบโดยนัยหรือโดยชัดแจ้ง (ผ่านการเรียกไปยังแอปพลิเคชันใหม่, [] หรือ {}) แอปพลิเคชันของคุณกำลังเข้าใกล้การเก็บขยะและการหยุดแอปพลิเคชันที่น่ากลัวไว้ชั่วคราว

คาดว่าแอปพลิเคชันนี้จะมีพื้นที่เก็บข้อมูลขนาด 10 MB/วินาทีใช่ไหม

คำตอบคือไม่ นักพัฒนาซอฟต์แวร์ไม่ได้ดำเนินการใดๆ กับขยะ 10 MB/วินาที

ผู้ต้องสงสัย

ขั้นตอนต่อไปของการตรวจสอบคือการระบุตัวผู้ต้องสงสัย จากนั้นจึงคัดออก

ผู้ต้องสงสัย #1

กำลังเรียกใหม่ระหว่างเฟรม โปรดทราบว่าแต่ละออบเจ็กต์ที่จัดสรรจะย้ายคุณเข้าใกล้การหยุดชั่วคราว GC มากขึ้น โดยเฉพาะอย่างยิ่งแอปพลิเคชันที่ทำงานที่อัตราเฟรมสูงควรพยายามให้มีการจัดสรรต่อเฟรมเป็นศูนย์ ซึ่งโดยปกติจะต้องใช้ระบบรีไซเคิลวัตถุที่คิดมาอย่างรอบคอบและเจาะจงแอปพลิเคชัน นักสืบ V8 ตรวจสอบกับทีม Oz แล้ว และพบว่าพวกเขาไม่ได้โทรหาใหม่ อันที่จริง ทีมออซรับทราบข้อกำหนดนี้อยู่แล้วและกล่าวว่า "คงน่าอาย" ขูดออกให้หมดเลย

ผู้ต้องสงสัย #2

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

การจัดเก็บหมายเลขความแม่นยําแบบ 2 ชั้นไปยังพร็อพเพอร์ตี้ โดยต้องสร้างออบเจ็กต์ HeapNumber เพื่อจัดเก็บตัวเลขและคุณสมบัติที่มีการเปลี่ยนแปลงให้ชี้ไปที่ออบเจ็กต์ใหม่นี้ การเปลี่ยนแปลงพร็อพเพอร์ตี้ให้ชี้ไปที่ HeapNumber จะไม่สร้างขยะ อย่างไรก็ตาม อาจเป็นไปได้ที่จะมีการจัดเก็บตัวเลขความแม่นยำคู่จำนวนมากเป็นคุณสมบัติของออบเจ็กต์ โค้ดเต็มไปด้วยข้อความดังต่อไปนี้

sprite.position.x += 0.5 * (dt);

ในโค้ดที่เพิ่มประสิทธิภาพ ทุกครั้งที่ x ได้รับค่าที่คำนวณใหม่ ซึ่งเป็นคำสั่งที่ดูเหมือนว่าจะไม่เป็นอันตราย จะมีการจัดสรรออบเจ็กต์ HeapNumber ใหม่โดยนัย ทำให้เราเข้าใกล้การหยุดเก็บขยะไว้ชั่วคราว

โปรดทราบว่าการใช้อาร์เรย์แบบพิมพ์ (หรืออาร์เรย์ปกติที่มีเฉพาะเลขทศนิยมคู่เท่านั้น) คุณจะหลีกเลี่ยงปัญหานี้ได้อย่างสิ้นเชิง เนื่องจากระบบจะจัดสรรพื้นที่เก็บข้อมูลสำหรับตัวเลขทศนิยมเพียงครั้งเดียวเท่านั้น และเมื่อเปลี่ยนค่าซ้ำหลายครั้งจะไม่ต้องจัดสรรพื้นที่เก็บข้อมูลใหม่

ผู้เชี่ยวชาญข้อ 4 อาจเป็นไปได้

การสืบค้นเบาะแสเกี่ยวกับนิติวิทยาศาสตร์

ในจุดนี้ นักสืบมีผู้ต้องสงสัย 2 อย่าง ได้แก่ การจัดเก็บเลขฮีปเป็นคุณสมบัติของวัตถุ และการคำนวณทางคณิตศาสตร์ที่เกิดขึ้นภายในฟังก์ชันที่ไม่ได้เพิ่มประสิทธิภาพ ถึงเวลาแล้วนำไปยังห้องทดลองและตัดสินว่าผู้ต้องสงสัยคนใดมีความผิด หมายเหตุ: ในส่วนนี้ เราจะใช้การสร้างปัญหาซ้ำที่พบในซอร์สโค้ดออซจริง การจำลองนี้เป็นลำดับที่มีขนาดเล็กกว่าโค้ดต้นฉบับ ดังนั้นจึงอธิบายได้ง่ายขึ้น

การทดสอบ #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 สำเร็จแล้ว คุณจะประมวลผลบันทึกโดยใช้ตัวประมวลผลติ๊กได้โดยทำดังนี้

$ 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 และหลัก ฟังก์ชันที่เพิ่มประสิทธิภาพจะมีเครื่องหมายดอกจัน (*) ข้างชื่อ โปรดสังเกตว่าการเลือกใช้ฟังก์ชันได้รับการเพิ่มประสิทธิภาพ และยกเลิกการเพิ่มประสิทธิภาพก็ไม่มีผล

เครื่องมือสำคัญอีกอย่างในกระเป๋าเครื่องมือของนักสืบ V8 ก็คือเหตุการณ์เครื่องควบคุมเวลาพล็อต ซึ่งสามารถดำเนินการได้ดังนี้

$ tools/plot-timer-event /path/to/v8.log

หลังจากเรียกใช้ ไฟล์ png ชื่อ timer-events.png จะอยู่ในไดเรกทอรีปัจจุบัน เมื่อเปิดขึ้นมา คุณควรจะเห็นหน้าตาแบบนี้

เหตุการณ์ตัวจับเวลา

นอกจากกราฟที่ด้านล่างแล้ว ข้อมูลจะแสดงเป็นแถวด้วย แกน X คือเวลา (มิลลิวินาที) ทางด้านซ้ายมือจะมีป้ายกำกับสำหรับแต่ละแถว:

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

แถว V8.Execute มีเส้นแนวตั้งสีดำที่ขีดทับอยู่บนแต่ละโปรไฟล์ที่เครื่องหมายถูกที่ V8 เรียกใช้โค้ด JavaScript V8.GCScavenger มีเส้นแนวตั้งสีน้ำเงินที่วาดบนแต่ละโปรไฟล์ที่เครื่องหมายถูกที่ V8 ทำคอลเล็กชันรุ่นใหม่ และเช่นเดียวกันสำหรับรัฐ V8 ที่เหลือ

หนึ่งในแถวที่สำคัญที่สุดคือ "ประเภทโค้ดที่เรียกใช้" โค้ดจะเป็นสีเขียวเมื่อมีการเรียกใช้โค้ดที่เพิ่มประสิทธิภาพ และสีแดงและน้ำเงินผสมกันเมื่อมีการเรียกใช้โค้ดที่ไม่ได้เพิ่มประสิทธิภาพ ภาพหน้าจอต่อไปนี้แสดงการเปลี่ยนจากเพิ่มประสิทธิภาพเป็นไม่เพิ่มประสิทธิภาพ จากนั้นกลับไปเป็นโค้ดที่เพิ่มประสิทธิภาพ

กำลังเรียกใช้ประเภทโค้ด

ตามหลักการแล้ว แต่ไม่ควรในทันที เส้นนี้จะเป็นสีเขียวนิ่ง หมายความว่าโปรแกรมของคุณได้เปลี่ยนเป็นสถานะที่มั่นคงซึ่งมีการเพิ่มประสิทธิภาพ โค้ดที่ไม่ได้เพิ่มประสิทธิภาพจะทํางานช้ากว่าโค้ดที่เพิ่มประสิทธิภาพเสมอ

หากเว้นระยะเวลาส่วนนี้แล้ว คุณจะจำได้ว่าคุณสามารถทำงานได้เร็วขึ้นมากด้วยการปรับโครงสร้างแอปพลิเคชันให้ใหม่เพื่อให้ทำงานใน Shell ดีบัก v8: d8 การใช้ d8 จะช่วยให้คุณทำซ้ำได้เร็วขึ้นโดยใช้เครื่องมือตัวประมวลผลติ๊กและตัวจับเวลาเหตุการณ์ ผลข้างเคียงอีกอย่างของการใช้ d8 คือแยกปัญหาที่แท้จริงได้ง่ายขึ้น โดยลดปริมาณสัญญาณรบกวนที่แสดงในข้อมูล

เมื่อดูผังเหตุการณ์ของเครื่องควบคุมเวลาจากซอร์สโค้ดออซ แสดงการเปลี่ยนจากการเพิ่มประสิทธิภาพเป็นโค้ดที่ไม่ได้เพิ่มประสิทธิภาพ และในขณะที่เรียกใช้โค้ดที่ไม่ได้เพิ่มประสิทธิภาพ มีการทริกเกอร์คอลเล็กชันรุ่นใหม่จำนวนมากที่คล้ายกับภาพหน้าจอต่อไปนี้ (โปรดทราบว่าได้นำเวลาออกตรงกลางออกแล้ว)

ผังเหตุการณ์ตัวจับเวลา

หากคุณสังเกตอย่างใกล้ชิด คุณจะเห็นเส้นสีดำที่ระบุว่าเมื่อ 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 และเข้าใจดี พวกเขาก็รู้ทันทีว่า บางครั้งการสร้างห่วงสำหรับ I-In อาจไม่เพิ่มประสิทธิภาพโดย V8 กล่าวคือ หากฟังก์ชันมีโครงสร้างสำหรับลูป for-i-in ฟังก์ชันนั้นอาจไม่ได้รับการเพิ่มประสิทธิภาพ นี่เป็นกรณีพิเศษในวันนี้ และมีแนวโน้มจะเปลี่ยนแปลงในอนาคต กล่าวคือ V8 อาจเพิ่มประสิทธิภาพการสร้างลูปนี้ในวันหนึ่ง เนื่องจากเราไม่ใช่นักสืบ V8 และไม่รู้จัก V8 เหมือนเป็นมือเป็นหลังของเรา เราจะทราบได้อย่างไรว่าเหตุใด UpdateSprites จึงไม่ได้รับการเพิ่มประสิทธิภาพ

การทดสอบ #2

การเรียกใช้ Chrome ที่มีแฟล็กนี้

--js-flags="--trace-deopt --trace-opt-verbose"

จะแสดงบันทึกแบบละเอียดของข้อมูลการเพิ่มประสิทธิภาพและการเพิ่มประสิทธิภาพ เราพบข้อมูลต่อไปนี้เพื่อค้นหาข้อมูลupdateSprite

[ปิดใช้การเพิ่มประสิทธิภาพสำหรับupdateSprites เหตุผล: ForInStatement ไม่ใช่กรณีที่รวดเร็ว]

เช่นเดียวกับที่นักสืบตั้งข้อสันนิษฐานไว้ โครงสร้างสำหรับห่วงเข้า (For-in Loop) ก็เป็นเหตุผลนั่นเอง

ปิดเคสแล้ว

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

ลุกขึ้นมาแล้วเริ่มแก้ปัญหาการก่อเหตุอาชญากรรมได้เลย!