JavaScript ของหน่วยความจำแบบคงที่ที่มีพูลออบเจ็กต์

เกริ่นนำ

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

สแนปชอตจากไทม์ไลน์ความทรงจำของคุณ

เพื่อนร่วมงานคนหนึ่งหัวเราะเบาๆ เพราะพวกเขารู้ดีว่าคุณกำลังมีปัญหาด้านประสิทธิภาพที่เกี่ยวกับหน่วยความจำ

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

ฟันเลื่อยหมายถึงอะไร

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

ค่าเก็บขยะและต้นทุนด้านประสิทธิภาพ

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

ไฟล์เก็บขยะมักมีภาพที่ตรงข้ามกับการจัดการหน่วยความจำด้วยตนเอง ซึ่งโปรแกรมเมอร์จะต้องระบุออบเจ็กต์ที่จะจัดการกับและกลับไปยังระบบหน่วยความจำ

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

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

ลดการเลิกใช้งานหน่วยความจำ, ลดภาษีการเก็บขยะ

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

ขั้นตอนนี้จะย้ายกราฟหน่วยความจําของคุณจากรายการต่อไปนี้

สแนปชอตจากไทม์ไลน์ความทรงจำของคุณ

เป็น

JavaScript หน่วยความจำแบบคงที่

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

การเปลี่ยนไปใช้ JavaScript แบบหน่วยความจำคงที่

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

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

ในความเป็นจริง การจะบรรลุข้อ 1 ได้นั้น เราต้องทำตามข้อที่ 2 สักเล็กน้อย เรามาเริ่มกันที่จุดนั้นเลย

พูลออบเจ็กต์

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

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

var newEntity = gEntityObjectPool.allocate();
newEntity.pos = {x: 215, y: 88};

//..... do some stuff with the object that we need to do

gEntityObjectPool.free(newEntity); //free the object when we're done
newEntity = null; //free this object reference

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

ออบเจ็กต์ที่มีการจัดสรรล่วงหน้า

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

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

function init() {
  //preallocate all our pools. 
  //Note that we keep each pool homogeneous wrt object types
  gEntityObjectPool.preAllocate(256);
  gDomObjectPool.preAllocate(888);
}

จำนวนเงินที่คุณเลือกมีผลอย่างมากต่อลักษณะการทำงานของแอปพลิเคชันของคุณ บางครั้ง ค่าสูงสุดในทางทฤษฎีอาจไม่ใช่ตัวเลือกที่ดีที่สุด ตัวอย่างเช่น การเลือกจำนวนสูงสุดโดยเฉลี่ยอาจทำให้คุณได้รับพื้นที่หน่วยความจำน้อยลงสำหรับผู้ที่ไม่ได้ใช้ขั้นสูง

ไม่ไกลจากลูกกระสุนเงิน

แอปมีการจัดประเภททุกแบบที่รูปแบบการเติบโตของหน่วยความจำคงที่อาจมีประโยชน์มากกว่า ดังที่เพื่อนๆ ชาว Chrome DevRel Renato Mangini ได้อธิบายไว้ว่ามีข้อเสียบางอย่าง

บทสรุป

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

ซอร์สโค้ด

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

รายการอ้างอิง