บทนำ
ดังนั้นคุณจะได้รับอีเมลแจ้งว่าเกมบนเว็บ / เว็บแอปมีประสิทธิภาพไม่ดีหลังจากผ่านไประยะหนึ่ง โดยลองตรวจดูโค้ด ไม่เห็นอะไรที่โดดเด่น จนกว่าจะเปิดเครื่องมือประสิทธิภาพหน่วยความจำของ Chrome แล้วพบข้อมูลต่อไปนี้
เพื่อนร่วมงานคนหนึ่งหัวเราะเบาๆ เพราะรู้ว่าคุณมีปัญหาด้านประสิทธิภาพที่เกี่ยวข้องกับหน่วยความจำ
ในมุมมองกราฟหน่วยความจำ รูปแบบฟันเลื่อยนี้บอกเกี่ยวกับปัญหาด้านประสิทธิภาพที่อาจจะร้ายแรงมาก เมื่อปริมาณการใช้หน่วยความจําเพิ่มขึ้น คุณจะเห็นพื้นที่แผนภูมิเพิ่มขึ้นในการบันทึกไทม์ไลน์ด้วย เมื่อแผนภูมิลดลงอย่างฉับพลัน แสดงว่ามีการเรียกใช้โปรแกรมเก็บขยะและล้างข้อมูลออบเจ็กต์หน่วยความจำที่อ้างอิง
ในกราฟลักษณะนี้ คุณจะเห็นว่ามีเหตุการณ์การเก็บขยะเกิดขึ้นหลายรายการ ซึ่งอาจเป็นอันตรายต่อประสิทธิภาพของเว็บแอป บทความนี้จะกล่าวถึงวิธีควบคุมการใช้หน่วยความจําเพื่อลดผลกระทบต่อประสิทธิภาพ
ค่าใช้จ่ายในการเก็บขยะและประสิทธิภาพ
โมเดลหน่วยความจําของ JavaScript สร้างขึ้นจากเทคโนโลยีที่เรียกว่า Garbage Collector ในภาษาต่างๆ โปรแกรมเมอร์มีหน้าที่รับผิดชอบโดยตรงในการจองและปลดปล่อยหน่วยความจำจากกองหน่วยความจำของระบบ อย่างไรก็ตาม ระบบ Garbage Collector จะจัดการงานนี้ในนามของผู้เขียนโปรแกรม ซึ่งหมายความว่าระบบจะไม่เพิ่มพื้นที่ว่างในหน่วยความจำโดยตรงเมื่อผู้เขียนโปรแกรมยกเลิกการอ้างอิง แต่จะทำในภายหลังเมื่อ Heuristic ของ GC ตัดสินใจว่าควรดำเนินการดังกล่าว กระบวนการตัดสินใจนี้กำหนดให้ GC ดำเนินการวิเคราะห์ทางสถิติบางอย่างเกี่ยวกับออบเจ็กต์ที่ใช้งานอยู่และไม่ได้ใช้งาน ซึ่งต้องใช้เวลาสักระยะ
การเก็บขยะมักแสดงเป็นกระบวนการที่ตรงข้ามกับการจัดการหน่วยความจำด้วยตนเอง ซึ่งกำหนดให้โปรแกรมเมอร์ต้องระบุออบเจ็กต์ที่จะยกเลิกการจัดสรรและส่งคืนไปยังระบบหน่วยความจำ
กระบวนการที่ GC เรียกคืนหน่วยความจำนั้นไม่ได้เกิดขึ้นโดยอัตโนมัติ โดยปกติแล้วระบบจะลดประสิทธิภาพที่มีอยู่ด้วยการใช้ช่วงเวลาหนึ่งในการทำงาน และระบบจะเป็นผู้ตัดสินใจว่าควรเรียกใช้เมื่อใด คุณไม่สามารถควบคุมการดำเนินการนี้เนื่องจาก GC Pulse อาจเกิดขึ้นได้ทุกเมื่อระหว่างการเรียกใช้โค้ด ซึ่งจะบล็อกการเรียกใช้โค้ดจนกว่าการดำเนินการจะเสร็จสิ้น โดยทั่วไปแล้ว คุณจะไม่ทราบระยะเวลาของจังหวะนี้ และจะใช้เวลาสักครู่ในการเรียกใช้ ทั้งนี้ขึ้นอยู่กับว่าโปรแกรมใช้หน่วยความจำอย่างไรในช่วงเวลาหนึ่งๆ
แอปพลิเคชันที่มีประสิทธิภาพสูงต้องอาศัยขอบเขตประสิทธิภาพที่สอดคล้องกันเพื่อให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่ราบรื่น ระบบเก็บขยะอาจทำให้เป้าหมายนี้ไม่บรรลุได้ เนื่องจากสามารถทํางานในเวลาที่ไม่แน่นอนเป็นระยะเวลาที่ไม่แน่นอน ซึ่งจะกินเวลาที่มีให้แอปพลิเคชันต้องบรรลุเป้าหมายด้านประสิทธิภาพ
ลดการเลิกใช้งานหน่วยความจำ ลดภาษีการเก็บขยะ
ดังที่กล่าวไว้ก่อนหน้านี้ พัลส์ GC จะปรากฏขึ้นเมื่อชุดการเรียนรู้เชิง heuristics พิจารณาว่ามีออบเจ็กต์ที่ไม่ทำงานมากพอที่การเรียกใช้พัลส์จะมีประโยชน์ ดังนั้น หัวใจสําคัญในการลดเวลาที่ Garbage Collector ใช้ในการประมวลผลแอปพลิเคชันของคุณคือการลดการสร้างและปล่อยออบเจ็กต์ที่มากเกินไปให้เหลือน้อยที่สุด กระบวนการสร้าง/เพิ่มพื้นที่ว่างของออบเจ็กต์บ่อยครั้งนี้เรียกว่า "การย้ายข้อมูลหน่วยความจำ" หากคุณสามารถลดการย้ายข้อมูลหน่วยความจำได้ตลอดอายุการใช้งานของแอปพลิเคชัน ก็จะลดระยะเวลาที่ GC ใช้ในการดำเนินการด้วย ซึ่งหมายความว่าคุณต้องนําออก / ลดจํานวนออบเจ็กต์ที่สร้างและทำลาย ซึ่งหมายความว่าคุณต้องหยุดจัดสรรหน่วยความจํา
กระบวนการนี้จะย้ายกราฟความทรงจำจากตำแหน่งต่อไปนี้
ดังนี้
ในโมเดลนี้ คุณจะเห็นได้ว่ากราฟไม่มีรูปแบบฟันเลื่อยอีกต่อไป แต่เพิ่มขึ้นอย่างมากในช่วงแรก จากนั้นค่อยๆ เพิ่มขึ้นเมื่อเวลาผ่านไป หากคุณพบปัญหาด้านประสิทธิภาพเนื่องจากมีการเปลี่ยนแปลงหน่วยความจํา คุณควรสร้างกราฟประเภทนี้
การเปลี่ยนไปใช้ JavaScript แบบหน่วยความจำแบบคงที่
JavaScript หน่วยความจําแบบคงที่เป็นเทคนิคที่จัดสรรล่วงหน้าเมื่อเริ่มต้นแอป โดยจัดสรรหน่วยความจําทั้งหมดที่จําเป็นตลอดอายุการใช้งานของแอป และจัดการหน่วยความจํานั้นในระหว่างการดําเนินการเมื่อไม่จําเป็นต้องใช้ออบเจ็กต์อีกต่อไป เราบรรลุเป้าหมายนี้ได้ง่ายๆ ในไม่กี่ขั้นตอน ดังนี้
- เครื่องมือวัดแอปพลิเคชันเพื่อระบุจํานวนสูงสุดของออบเจ็กต์หน่วยความจําที่ใช้งานอยู่ (ต่อประเภท) สําหรับสถานการณ์การใช้งานที่หลากหลาย
- ใช้โค้ดอีกครั้งเพื่อจัดสรรจำนวนสูงสุดดังกล่าวล่วงหน้า จากนั้นดึงข้อมูล/ปล่อยข้อมูลด้วยตนเองแทนที่จะไปที่หน่วยความจำหลัก
ความจริงแล้ว การทำ #1 ให้สำเร็จจำเป็นต้องใช้ #2 อยู่บ้าง เราจึงขอเริ่มที่นั่น
พูลออบเจ็กต์
กล่าวอย่างง่ายคือ การรวมออบเจ็กต์คือกระบวนการเก็บชุดออบเจ็กต์ที่ไม่ได้ใช้ซึ่งแชร์ประเภทเดียวกันไว้ เมื่อต้องการออบเจ็กต์ใหม่สําหรับโค้ด คุณจะรีไซเคิลออบเจ็กต์ที่ไม่ได้ใช้จากพูลแทนที่จะจัดสรรออบเจ็กต์ใหม่จากกองหน่วยความจําของระบบ เมื่อโค้ดภายนอกใช้ออบเจ็กต์เสร็จแล้ว ระบบจะส่งออบเจ็กต์กลับไปยังพูลแทนที่จะปล่อยไปยังหน่วยความจำหลัก เนื่องจากออบเจ็กต์ไม่เคยยกเลิกการอ้างอิง (หรือถูกลบ) จากโค้ด ดังนั้นจึงจะไม่มีการรวบรวมไฟล์ขยะ การใช้พูลออบเจ็กต์จะทำให้โปรแกรมเมอร์ควบคุมหน่วยความจําได้อีกครั้ง ซึ่งจะลดอิทธิพลของโปรแกรมรวบรวมขยะต่อประสิทธิภาพ
เนื่องจากแอปพลิเคชันมีชุดออบเจ็กต์ประเภทต่างๆ ที่ต้องดูแลรักษา การใช้พูลออบเจ็กต์อย่างเหมาะสมจึงกำหนดให้คุณต้องมีพูล 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);
}
จำนวนที่คุณเลือกจะส่งผลอย่างมากต่อลักษณะการทํางานของแอปพลิเคชัน บางครั้งจำนวนสูงสุดตามทฤษฎีอาจไม่ใช่ตัวเลือกที่ดีที่สุด เช่น การเลือกค่าสูงสุดเฉลี่ยอาจทำให้พื้นที่หน่วยความจำสำหรับผู้ใช้ทั่วไปมีขนาดเล็กลง
ไม่ได้เป็นวิธีแก้ปัญหาที่ได้ผลแน่นอน
มีแอปหลายประเภทที่รูปแบบการเติบโตของหน่วยความจําแบบคงที่อาจให้ผลลัพธ์ที่ดี อย่างไรก็ตาม Renato Mangini เพื่อนร่วมงานจากทีม DevRel ของ Chrome ชี้ให้เห็นข้อเสียบางประการ
บทสรุป
เหตุผลหนึ่งที่ JavaScript เหมาะสำหรับเว็บนั้นขึ้นอยู่กับการเริ่มต้นใช้งานด้วยภาษาที่รวดเร็ว สนุก และง่ายดาย สาเหตุหลักคือข้อจำกัดด้านไวยากรณ์ที่ต่ำและการจัดการปัญหาหน่วยความจำในนามของคุณ คุณเขียนโค้ดได้โดยไม่ต้องกังวลเรื่องงานที่น่าเบื่อ อย่างไรก็ตาม สําหรับเว็บแอปพลิเคชันประสิทธิภาพสูง เช่น เกม HTML5 GC มักจะกินอัตราเฟรมที่จําเป็นอย่างยิ่ง ซึ่งจะลดประสบการณ์ของผู้ใช้ปลายทาง การใช้เครื่องมือวัดผลอย่างละเอียดและการใช้พูลออบเจ็กต์จะช่วยลดความหนักอึ้งนี้ในอัตราเฟรม และช่วยให้คุณมีเวลาทำสิ่งอื่นๆ ที่ยอดเยี่ยมมากขึ้น
ซอร์สโค้ด
การใช้งานพูลออบเจ็กต์มีอยู่มากมายบนเว็บ เราจึงไม่อยากทำให้คุณเบื่อกับอีกวิธีหนึ่ง เราจะนําคุณไปยังบทความเหล่านี้แทน ซึ่งแต่ละบทความมีรายละเอียดการใช้งานเฉพาะ ซึ่งเป็นสิ่งที่สําคัญเนื่องจากการใช้งานแต่ละแอปพลิเคชันอาจจําเป็นต้องใช้การติดตั้งใช้งานที่เฉพาะเจาะจง
- Object Pool ของ Gamecore.js
- Beej’s Object Pools
- พูลออบเจ็กต์ที่แสนง่ายของ Emehrkay
- กลุ่มออบเจ็กต์ที่เน้นเกมของ Steven Lambert
- การตั้งค่า objectPool ของ RenderEngine