แก้ปัญหาเกี่ยวกับหน่วยความจำ

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

สรุป

Image for: สรุป
  • ดูว่าหน้าเว็บใช้หน่วยความจำเท่าใดด้วยตัวจัดการงานของ Chrome
  • แสดงภาพการใช้งานหน่วยความจำเมื่อเวลาผ่านไปด้วยการบันทึกไทม์ไลน์
  • ระบุแผนผัง DOM ที่แยกออก (สาเหตุที่พบบ่อยของการรั่วไหลของหน่วยความจํา) ด้วยภาพรวมกอง heap
  • ดูว่ามีการจองหน่วยความจําใหม่ในกอง JS เมื่อใดด้วยการบันทึกลําดับเวลาการจัดสรร
  • ระบุองค์ประกอบที่แยกออกซึ่งเก็บไว้โดยการอ้างอิง JavaScript

ภาพรวม

Image for: ภาพรวม

หัวใจสําคัญของรูปแบบประสิทธิภาพ RAIL คือผู้ใช้ควรเป็นจุดสนใจของการพัฒนาประสิทธิภาพ

ปัญหาเกี่ยวกับหน่วยความจํามีความสําคัญเนื่องจากผู้ใช้มักจะรับรู้ถึงปัญหาเหล่านี้ ผู้ใช้จะรับรู้ปัญหาเกี่ยวกับความทรงจำได้ดังนี้

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

หน่วยความจําที่เพิ่มขึ้น: "มากเกินไป" หมายถึงเท่าใด

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

เราไม่สามารถใช้ตัวเลขที่แน่นอนได้ เนื่องจากอุปกรณ์และเบราว์เซอร์แต่ละประเภทมีความสามารถแตกต่างกัน หน้าเดียวกันที่ทํางานได้อย่างราบรื่นในสมาร์ทโฟนระดับสูงอาจขัดข้องในสมาร์ทโฟนระดับล่าง

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

ตรวจสอบการใช้หน่วยความจำแบบเรียลไทม์ด้วยตัวจัดการงานของ Chrome

Image for: ตรวจสอบการใช้หน่วยความจำแบบเรียลไทม์ด้วยตัวจัดการงานของ Chrome

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

  1. กด Shift+Esc หรือไปที่เมนูหลักของ Chrome แล้วเลือกเครื่องมือเพิ่มเติม > ตัวจัดการงานเพื่อเปิดตัวจัดการงาน

  2. คลิกขวาที่ส่วนหัวของตารางในตัวจัดการงาน แล้วเปิดใช้หน่วยความจำ JavaScript

คอลัมน์ 2 คอลัมน์นี้บอกข้อมูลที่แตกต่างกันเกี่ยวกับวิธีที่หน้าเว็บใช้หน่วยความจํา

  • คอลัมน์หน่วยความจําที่ใช้แสดงหน่วยความจําของระบบปฏิบัติการ โหนด DOM จะจัดเก็บไว้ในหน่วยความจําของระบบปฏิบัติการ หากค่านี้เพิ่มขึ้น แสดงว่าระบบกําลังสร้างโหนด DOM
  • คอลัมน์หน่วยความจํา JavaScript แสดงกอง JS คอลัมน์นี้มี 2 ค่า ค่าที่คุณสนใจคือตัวเลขจริง (ตัวเลขในวงเล็บ) ตัวเลขที่แสดงอยู่แสดงถึงจํานวนหน่วยความจําที่ออบเจ็กต์ที่เข้าถึงได้ในหน้าเว็บใช้อยู่ หากตัวเลขนี้เพิ่มขึ้น แสดงว่าระบบกําลังสร้างออบเจ็กต์ใหม่หรือออบเจ็กต์ที่มีอยู่มีขนาดใหญ่ขึ้น

แสดงภาพหน่วยความจําที่รั่วด้วยไฟล์บันทึกประสิทธิภาพ

Image for: แสดงภาพหน่วยความจําที่รั่วด้วยไฟล์บันทึกประสิทธิภาพ

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

  1. เปิดแผงประสิทธิภาพในเครื่องมือสำหรับนักพัฒนาเว็บ
  2. เปิดใช้ช่องทำเครื่องหมายความทรงจำ
  3. บันทึกเสียง

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

var x = [];

function grow() {
  for (var i = 0; i < 10000; i++) {
    document.body.appendChild(document.createElement('div'));
  }
  x.push(new Array(1000000).join('x'));
}

document.getElementById('grow').addEventListener('click', grow);

ทุกครั้งที่กดปุ่มที่อ้างอิงในโค้ด ระบบจะเพิ่มโหนด div 10,000 รายการต่อท้ายเนื้อหาเอกสาร และพุชสตริงอักขระ x 1 ล้านรายการไปยังอาร์เรย์ x การรันโค้ดนี้จะสร้างการบันทึกไทม์ไลน์ดังภาพหน้าจอต่อไปนี้

ขั้น�����������อ����า����ิบายอินเทอร์เฟซผู้ใช้ กราฟ HEAP ในแผงภาพรวม (ใต้ NET) แสดงกอง JS ด้านล่างแผงภาพรวมคือแผงเคาน์เตอร์ ในส่วนนี้ คุณสามารถดูรายละเอียดการใช้งานหน่วยความจําตามฮีป JS (เหมือนกับกราฟ HEAP ในแผงภาพรวม) เอกสาร โหนด DOM รายการฟัง และหน่วยความจํา GPU การปิดใช้ช่องทําเครื่องหมายจะซ่อนช่องนั้นจากกราฟ

ตอนนี้มาดูการวิเคราะห์โค้ดเทียบกับภาพหน้าจอ หากดูตัวนับโหนด (กราฟสีเขียว) คุณจะเห็นว่ามันตรงกับโค้ดอย่างสมบูรณ์ จำนวนโหนดจะเพิ่มขึ้นทีละขั้น คุณสามารถสันนิษฐานได้ว่าการเพิ่มขึ้นของจำนวนโหนดแต่ละครั้งคือการเรียกใช้ grow() กราฟกองขยะ JS (กราฟสีน้ำเงิน) นั้นซับซ้อนกว่า ตามแนวทางปฏิบัติแนะนำ การลดลงครั้งแรกนั้นคือการรวบรวมขยะแบบบังคับ (ทำได้โดยกดปุ่มรวบรวมขยะ) เมื่อการบันทึกดำเนินไปเรื่อยๆ คุณจะเห็นขนาดฮีป JS เพิ่มขึ้น กรณีนี้เป็นเรื่องปกติและเป็นสิ่งที่คาดไว้ เนื่องจากโค้ด JavaScript กำลังสร้างโหนด DOM เมื่อมีการคลิกปุ่มทุกครั้ง และทํางานจํานวนมากเมื่อสร้างสตริงที่มีอักขระ 1 ล้านตัว ประเด็นสำคัญคือกอง JS สิ้นสุดสูงกว่าตอนเริ่มต้น (ซึ่ง "ตอนเริ่มต้น" ในที่นี้คือจุดหลังจากการบังคับใช้การเก็บขยะ) ในชีวิตจริง หากคุณเห็นรูปแบบการเพิ่มขนาดฮีป JS หรือขนาดโหนด แสดงว่าอาจมีการสูญเสียหน่วยความจํา

ค้นหาหน่วยความจําที่รั่วไหลของแผนผัง DOM ที่แยกออกด้วยสแนปชอตฮีป

Image for: ค้นหาหน่วยความจําที่รั่วไหลของแผนผัง DOM ที่แยกออกด้วยสแนปชอตฮีป

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

ต่อไปนี้เป็นตัวอย่างง่ายๆ ของโหนด DOM ที่แยกออก

var detachedTree;

function create() {
  var ul = document.createElement('ul');
  for (var i = 0; i < 10; i++) {
    var li = document.createElement('li');
    ul.appendChild(li);
  }
  detachedTree = ul;
}

document.getElementById('create').addEventListener('click', create);

การคลิกปุ่มที่อ้างอิงในโค้ดจะสร้างโหนด ul ที่มีโหนดย่อย li 10 รายการ โค้ดอ้างอิงโหนดเหล่านี้ แต่โหนดดังกล่าวไม่อยู่ในต้นไม้ DOM จึงถูกแยกออก

สแนปชอตฮีปเป็นวิธีหนึ่งในการระบุโหนดที่แยกออก สแนปชอตฮีปจะแสดงการแจกแจงหน่วยความจําของออบเจ็กต์ JS และโหนด DOM ของหน้า ณ เวลาที่สแนปชอต

หากต้องการสร้างสแนปชอต ให้เปิดเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์และไปที่แผงหน่วยความจำ เลือกปุ่มตัวเลือกสแนปชอตกอง แล้วกดปุ่มจับภาพสแนปชอต

ระบบอาจใช้เวลาสักครู่ในการประมวลผลและโหลดภาพรวม เมื่อดำเนินการเสร็จแล้ว ให้เลือกจากแผงด้านซ้าย (ชื่อ Heap snapshots)

พิมพ์ Detached ในช่องป้อนข้อมูลตัวกรองคลาสเพื่อค้นหาต้นไม้ DOM ที่แยกออก

ขยายเครื่องหมายกะรัตเพื่อตรวจสอบโครงสร้างที่แยกออกมา

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

ระบุหน่วยความจําฮีปของ JS ที่รั่วไหลด้วยไทม์ไลน์การจัดสรร

Image for: ระบุหน่วยความจําฮีปของ JS ที่รั่วไหลด้วยไทม์ไลน์การจัดสรร

ไทม์ไลน์การจัดสรรเป็นเครื่องมืออีกอย่างที่จะช่วยคุณติดตามการสูญเสียหน่วยความจําในกอง JS

ตัวอย่างโค้ดต่อไปนี้แสดงไทม์ไลน์การจัดสรร

var x = [];

function grow() {
  x.push(new Array(1000000).join('x'));
}

document.getElementById('grow').addEventListener('click', grow);

ทุกครั้งที่กดปุ่มที่อ้างอิงในโค้ด ระบบจะเพิ่มสตริงที่มีอักขระ 1 ล้านตัวลงในอาร์เรย์ x

หากต้องการบันทึกไทม์ไลน์การจัดสรร ให้เปิดเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์ ไปที่แผงหน่วยความจำ เลือกปุ่มตัวเลือกการจัดสรรในไทม์ไลน์ กดปุ่ม บันทึก ดำเนินการที่คุณสงสัยว่าทําให้หน่วยความจํารั่ว แล้วกดปุ่ม หยุดบันทึกเมื่อเสร็จแล้ว

ขณะบันทึก ให้สังเกตว่าแถบสีน้ำเงินปรากฏในไทม์ไลน์การจัดสรรหรือไม่ ดังในภาพหน้าจอต่อไปนี้

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

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

ตรวจสอบการจัดสรรหน่วยความจําตามฟังก์ชัน

Image for: ตรวจสอบการจัดสรรหน่วยความจําตามฟังก์ชัน

ใช้โปรไฟล์ประเภทการสุ่มตัวอย่างการจัดสรรในแผงหน่วยความจําเพื่อดูการจัดสรรหน่วยความจําตามฟังก์ชัน JavaScript

  1. เลือกปุ่มตัวเลือกการสุ่มตัวอย่างการกําหนดราคา หากมีเวิร์กเกอร์ในหน้าเว็บ คุณจะเลือกเวิร์กเกอร์นั้นให้เป็นเป้าหมายการสร้างโปรไฟล์ได้จากหน้าต่างเลือกอินสแตนซ์ VM ของ JavaScript
  2. กดปุ่มเริ่ม
  3. ดําเนินการในหน้าที่ต้องการตรวจสอบ
  4. กดปุ่มหยุดเมื่อดำเนินการเสร็จแล้ว

เครื่องมือสำหรับนักพัฒนาซอฟต์แวร์จะแสดงรายละเอียดการจัดสรรหน่วยความจําตามฟังก์ชัน มุมมองเริ่มต้นคือมาก (จากล่างขึ้นบน) ซึ่งจะแสดงฟังก์ชันที่จัดสรรหน่วยความจำมากที่สุดที่ด้านบน

ระบุออบเจ็กต์ที่เก็บไว้โดยการอ้างอิง JS

Image for: ระบุออบเจ็กต์ที่เก็บไว้โดยการอ้างอิง JS

โปรไฟล์องค์ประกอบที่แยกออกจะแสดงองค์ประกอบที่แยกออกซึ่งยังคงอยู่เนื่องจากโค้ด JavaScript อ้างอิงถึง

บันทึกโปรไฟล์องค์ประกอบที่แยกออกเพื่อดูโหนด HTML และจํานวนโหนดที่แน่นอน

สังเกตจุดที่เก็บขยะบ่อยครั้ง

Image for: สังเกตจุดที่เก็บขยะบ่อยครั้ง

หากหน้าเว็บหยุดชั่วคราวบ่อยครั้ง คุณอาจมีปัญหาในการรวบรวมขยะ

คุณสามารถใช้ตัวจัดการงานของ Chrome หรือบันทึกหน่วยความจำไทม์ไลน์เพื่อดูการเก็บรวบรวมขยะที่เกิดขึ้นบ่อย ใน Task Manager ค่าหน่วยความจําหรือหน่วยความจํา JavaScript ที่เพิ่มขึ้นและลดลงบ่อยๆ แสดงถึงการเก็บขยะบ่อยครั้ง กราฟจำนวนโหม่ง JS หรือจำนวนโหนดที่เพิ่มขึ้นและลดลงบ่อยครั้งในการบันทึกไทม์ไลน์บ่งบอกถึงการเก็บขยะบ่อยครั้ง

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