การดีบักหน่วยความจำรั่วไหลใน Node.js Applications

เผยแพร่แล้ว: 2022-03-11

ครั้งหนึ่งฉันเคยขับ Audi ที่มีเครื่องยนต์ V8 ทวินเทอร์โบ และประสิทธิภาพของมันก็น่าทึ่งมาก ฉันขับรถด้วยความเร็วประมาณ 140 ไมล์ต่อชั่วโมงบนทางหลวง IL-80 ใกล้เมืองชิคาโกตอนตี 3 เมื่อไม่มีใครอยู่บนถนน ตั้งแต่นั้นมา คำว่า “V8” ก็มีความเกี่ยวข้องกับประสิทธิภาพสูงสำหรับฉัน

Node.js เป็นแพลตฟอร์มที่สร้างขึ้นบนเอ็นจิ้น V8 JavaScript ของ Chrome สำหรับการสร้างแอปพลิเคชันเครือข่ายที่รวดเร็วและปรับขนาดได้ง่าย

แม้ว่า V8 ของ Audi จะทรงพลังมาก แต่คุณก็ยังถูกจำกัดด้วยความจุของถังแก๊สของคุณ เช่นเดียวกับ V8 ของ Google ซึ่งเป็นเอ็นจิ้น JavaScript ที่อยู่เบื้องหลัง Node.js ประสิทธิภาพของมันน่าทึ่งมากและมีหลายสาเหตุที่ Node.js ทำงานได้ดีสำหรับกรณีการใช้งานหลายๆ กรณี แต่คุณมักถูกจำกัดด้วยขนาดฮีป เมื่อคุณต้องการประมวลผลคำขอเพิ่มเติมในแอปพลิเคชัน Node.js คุณมีสองทางเลือก: ปรับขนาดในแนวตั้งหรือแนวนอน การปรับขนาดในแนวนอนหมายความว่าคุณต้องเรียกใช้อินสแตนซ์ของแอปพลิเคชันที่เกิดขึ้นพร้อมกันมากขึ้น เมื่อทำถูกต้องแล้ว คุณจะสามารถให้บริการตามคำขอได้มากขึ้น การปรับขนาดแนวตั้งหมายความว่าคุณต้องปรับปรุงการใช้หน่วยความจำและประสิทธิภาพของแอปพลิเคชันของคุณ หรือเพิ่มทรัพยากรที่มีให้สำหรับอินสแตนซ์แอปพลิเคชันของคุณ

การดีบักหน่วยความจำรั่วไหลใน Node.js Applications

การดีบักหน่วยความจำรั่วไหลใน Node.js Applications
ทวีต

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

สำหรับ API ใหม่ เราเลือกการคืนค่าด้วยไดรเวอร์ MongoDB ดั้งเดิมและ Kue สำหรับงานพื้นหลัง ฟังดูเหมือนกองที่เบามากใช่ไหม? ไม่ค่อยเท่าไหร่ ในระหว่างการโหลดสูงสุด อินสแตนซ์แอปพลิเคชันใหม่อาจใช้ RAM สูงสุด 270MB ดังนั้นความฝันของฉันที่จะมีอินสแตนซ์แอปพลิเคชันสองอินสแตนซ์ต่อ 1X Heroku Dyno หายไป

Node.js Memory Leak Debugging Arsenal

เมมวอตช์

หากคุณค้นหา “วิธีค้นหารอยรั่วในโหนด” เครื่องมือแรกที่คุณอาจพบคือ memwatch แพ็คเกจเดิมถูกละทิ้งไปนานแล้วและไม่ได้รับการบำรุงรักษาอีกต่อไป อย่างไรก็ตาม คุณสามารถค้นหาเวอร์ชันที่ใหม่กว่าได้อย่างง่ายดายในรายการส้อมของ GitHub สำหรับที่เก็บ โมดูลนี้มีประโยชน์เพราะสามารถปล่อยเหตุการณ์การรั่วไหลได้ หากเห็นว่าฮีปเติบโตมากกว่าการรวบรวมขยะติดต่อกัน 5 รายการ

กองขยะ

เครื่องมือที่ยอดเยี่ยมที่ช่วยให้นักพัฒนา Node.js ถ่ายภาพฮีปและตรวจสอบในภายหลังด้วยเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์ Chrome

Node-inspector

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

นำ "ตัวตรวจสอบโหนด" สำหรับ Spin

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

เพื่อสัมผัสประสบการณ์การใช้งาน node-inspector เราจะเขียนแอปพลิเคชัน Node.js อย่างง่ายโดยใช้การ Restify และใส่แหล่งที่มาของหน่วยความจำรั่วเล็กน้อยไว้ภายใน การทดลองทั้งหมดที่นี่สร้างขึ้นด้วย Node.js v0.12.7 ซึ่งได้รับการคอมไพล์เทียบกับ V8 v3.28.71.19

 var restify = require('restify'); var server = restify.createServer(); var tasks = []; server.pre(function(req, res, next) { tasks.push(function() { return req.headers; }); // Synchronously get user from session, maybe jwt token req.user = { id: 1, username: 'Leaky Master', }; return next(); }); server.get('/', function(req, res, next) { res.send('Hi ' + req.user.username); return next(); }); server.listen(3000, function() { console.log('%s listening at %s', server.name, server.url); });

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

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

 [28093] 7644 ms: Mark-sweep 10.9 (48.5) -> 10.9 (48.5) MB, 25.0 ms [HeapObjectsMap::UpdateHeapObjectsMap] [GC in old space requested]. [28093] 7717 ms: Mark-sweep 10.9 (48.5) -> 10.9 (48.5) MB, 18.0 ms [HeapObjectsMap::UpdateHeapObjectsMap] [GC in old space requested]. [28093] 7866 ms: Mark-sweep 11.0 (48.5) -> 10.9 (48.5) MB, 23.2 ms [HeapObjectsMap::UpdateHeapObjectsMap] [GC in old space requested]. [28093] 8001 ms: Mark-sweep 11.0 (48.5) -> 10.9 (48.5) MB, 18.4 ms [HeapObjectsMap::UpdateHeapObjectsMap] [GC in old space requested]. ... [28093] 633891 ms: Mark-sweep 235.7 (290.5) -> 235.7 (290.5) MB, 357.3 ms [HeapObjectsMap::UpdateHeapObjectsMap] [GC in old space requested]. [28093] 635672 ms: Mark-sweep 235.7 (290.5) -> 235.7 (290.5) MB, 331.5 ms [HeapObjectsMap::UpdateHeapObjectsMap] [GC in old space requested]. [28093] 637508 ms: Mark-sweep 235.7 (290.5) -> 235.7 (290.5) MB, 357.2 ms [HeapObjectsMap::UpdateHeapObjectsMap] [GC in old space requested].

รายการบันทึกเหล่านี้ถูกพิมพ์เมื่อแอปพลิเคชัน Node.js เริ่มต้นด้วยแฟ ล็ก –trace_gc :

 node --trace_gc app.js

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

 kill -SIGUSR1 $pid # Replace $pid with the actual process ID

การทำเช่นนี้ทำให้แอปพลิเคชัน Node.js (V8 แม่นยำ) เข้าสู่โหมดการดีบัก ในโหมดนี้ แอปพลิเคชันจะเปิดพอร์ต 5858 โดยอัตโนมัติด้วย V8 Debugging Protocol

ขั้นตอนต่อไปของเราคือเรียกใช้ node-inspector ซึ่งจะเชื่อมต่อกับอินเทอร์เฟซการแก้ไขข้อบกพร่องของแอปพลิเคชันที่ทำงานอยู่และเปิดเว็บอินเทอร์เฟซอื่นบนพอร์ต 8080

 $ node-inspector Node Inspector v0.12.2 Visit http://127.0.0.1:8080/?ws=127.0.0.1:8080&port=5858 to start debugging.

ในกรณีที่แอปพลิเคชันกำลังทำงานในการผลิตและคุณมีไฟร์วอลล์อยู่แล้ว เราสามารถอุโมงค์พอร์ต 8080 ระยะไกลไปยัง localhost:

 ssh -L 8080:localhost:8080 [email protected]

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

มาหารอยรั่วกันเถอะ!

หน่วยความจำรั่วใน V8 ไม่ใช่การรั่วไหลของหน่วยความจำจริงอย่างที่เราทราบจากแอปพลิเคชัน C/C++ ในตัวแปร JavaScript จะไม่หายไปในช่องว่าง แต่จะถูก "ลืม" เป้าหมายของเราคือค้นหาตัวแปรที่ถูกลืมและเตือนพวกเขาว่าด๊อบบี้เป็นอิสระ

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

เริ่มบันทึกการจัดสรรฮีพและมาจำลองผู้ใช้พร้อมกัน 50 รายบนโฮมเพจของเราโดยใช้ Apache Benchmark

ภาพหน้าจอ

 ab -c 50 -n 1000000 -k http://example.com/

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

แก้ไขรอยรั่วบนเครื่องบิน

หลังจากรวบรวมสแนปชอตการจัดสรรฮีปในช่วงเวลา 3 นาที เราก็ได้ผลลัพธ์ดังนี้:

ภาพหน้าจอ

เราจะเห็นได้อย่างชัดเจนว่ามีอาร์เรย์ขนาดมหึมา วัตถุ IncomingMessage, ReadableState, ServerResponse และ Domain จำนวนมากเช่นกันในฮีป ลองวิเคราะห์แหล่งที่มาของการรั่วไหล

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

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

นอกจากนี้เรายังสามารถเห็นได้ว่าวัตถุ "(ปิด)" เติบโตอย่างรวดเร็วเช่นกัน

มันอาจจะสะดวกที่จะดูสตริงเช่นกัน ใต้รายการสตริงมีวลี "Hi Leaky Master" มากมาย สิ่งเหล่านี้อาจให้เบาะแสบางอย่างแก่เราเช่นกัน

ในกรณีของเรา เรารู้ว่าสตริง "Hi Leaky Master" สามารถประกอบได้ภายใต้เส้นทาง "GET /" เท่านั้น

หากคุณเปิดเส้นทางการยึด คุณจะเห็นสตริงนี้ถูกอ้างอิงผ่าน req ดังนั้นจึงมีการสร้างบริบทและทั้งหมดนี้ถูกเพิ่มลงในอาร์เรย์ขนาดยักษ์บางอัน

ภาพหน้าจอ

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

ภาพหน้าจอ

หลังจากที่เราแก้ไขโค้ดเสร็จแล้ว เราสามารถกด CTRL+S เพื่อบันทึกและคอมไพล์โค้ดใหม่ได้ทันที!

ตอนนี้ เรามาบันทึกส แนปชอต Heap Allocations อื่น และดูว่าการปิดใดที่กำลังครอบครองหน่วยความจำอยู่

เป็นที่ชัดเจนว่า SomeKindOfClojure() เป็นผู้ร้ายของเรา ตอนนี้เราจะเห็นว่ามีการเพิ่มการปิด SomeKindOfClojure() ในอาร์เรย์บาง งานที่ มีชื่อในพื้นที่ส่วนกลาง

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

ภาพหน้าจอ

ด๊อบบี้ ฟรี!

ชีวิตของขยะใน V8

V8 JS ไม่มีหน่วยความจำรั่ว มีเพียงตัวแปรที่ถูกลืม

V8 JS ไม่มีหน่วยความจำรั่ว มีเพียงตัวแปรที่ถูกลืม
ทวีต

ฮีป V8 ถูกแบ่งออกเป็นช่องว่างต่างๆ:

  • New Space : พื้นที่นี้ค่อนข้างเล็กและมีขนาดระหว่าง 1MB ถึง 8MB วัตถุส่วนใหญ่ได้รับการจัดสรรที่นี่
  • Old Pointer Space : มีวัตถุที่อาจมีตัวชี้ไปยังวัตถุอื่น หากวัตถุอยู่ได้นานพอใน New Space วัตถุนั้นจะได้รับการเลื่อนตำแหน่งเป็น Old Pointer Space
  • Old Data Space : มีเฉพาะข้อมูลดิบ เช่น สตริง ตัวเลขในกล่อง และอาร์เรย์ของคู่ที่ยังไม่ได้แกะกล่อง วัตถุที่รอดชีวิตจาก GC ใน New Space นานพอก็จะถูกย้ายมาที่นี่เช่นกัน
  • Large Object Space : วัตถุที่มีขนาดใหญ่เกินกว่าจะใส่ลงในช่องว่างอื่นได้ถูกสร้างขึ้นในพื้นที่นี้ แต่ละอ็อบเจ็กต์มีพื้นที่ mmap 'ed ของตัวเองในหน่วยความจำ
  • พื้นที่โค้ด : ประกอบด้วยโค้ดแอสเซมบลีที่สร้างโดยคอมไพเลอร์ JIT
  • พื้นที่เซลล์ พื้นที่เซลล์คุณสมบัติ พื้นที่แผนที่ : พื้นที่นี้ประกอบด้วย Cell s, PropertyCell s และ Map s ใช้เพื่อลดความซับซ้อนของการรวบรวมขยะ

แต่ละช่องว่างประกอบด้วยหน้า เพจคือพื้นที่ของหน่วยความจำที่จัดสรรจากระบบปฏิบัติการด้วย mmap แต่ละหน้ามีขนาด 1MB เสมอ ยกเว้นหน้าในพื้นที่วัตถุขนาดใหญ่

V8 มีกลไกการเก็บขยะในตัวสองแบบ: Scavenge, Mark-Sweep และ Mark-Compact

Scavenge เป็นเทคนิคการเก็บขยะที่รวดเร็วมากและทำงานกับวัตถุใน New Space Scavenge คือการนำอัลกอริทึมของเชนีย์ไปใช้ แนวคิดนี้ง่ายมาก New Space แบ่งออกเป็นสองส่วนเท่าๆ กัน: To-Space และ From-Space Scavenge GC เกิดขึ้นเมื่อ To-Space เต็ม มันเพียงแค่สลับ To และ From ช่องว่าง และคัดลอกวัตถุที่มีชีวิตทั้งหมดไปยัง To-Space หรือเลื่อนมันไปยังพื้นที่เก่าแห่งใดแห่งหนึ่ง หากพวกมันรอดชีวิตจากการกวาดล้างสองครั้ง จากนั้นจึงลบออกจากพื้นที่ทั้งหมด การไล่ล่านั้นเร็วมาก แต่พวกมันมีค่าใช้จ่ายในการรักษาฮีปสองเท่าและคัดลอกอ็อบเจกต์ในหน่วยความจำอย่างต่อเนื่อง เหตุผลในการใช้การไล่ล่าเพราะวัตถุส่วนใหญ่ตายตั้งแต่ยังเล็ก

Mark-Sweep & Mark-Compact เป็นเครื่องเก็บขยะอีกประเภทหนึ่งที่ใช้ใน V8 อีกชื่อหนึ่งคือเก็บขยะเต็ม มันทำเครื่องหมายโหนดที่ใช้งานอยู่ทั้งหมด จากนั้นกวาดโหนดที่ตายแล้วทั้งหมดและจัดเรียงหน่วยความจำ

เคล็ดลับประสิทธิภาพและการดีบัก GC

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

ให้ชื่อแก่การปิดและการทำงานเสมอ

การตรวจสอบสแต็กเทรซและฮีปนั้นง่ายกว่ามากเมื่อการปิดและฟังก์ชันทั้งหมดของคุณมีชื่อ

 db.query('GIVE THEM ALL', function GiveThemAllAName(error, data) { ... })

หลีกเลี่ยงวัตถุขนาดใหญ่ในฟังก์ชั่นร้อน

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

ฟังก์ชันยอดนิยมควรได้รับการปรับให้เหมาะสม

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

หลีกเลี่ยงความหลากหลายสำหรับ IC ในฟังก์ชันร้อน

Inline Caches (IC) ใช้เพื่อเร่งการประมวลผลโค้ดบางส่วน ไม่ว่าจะโดยการแคชการเข้าถึงคุณสมบัติของอ็อบเจ็กต์ obj.key หรือฟังก์ชันง่ายๆ

 function x(a, b) { return a + b; } x(1, 2); // monomorphic x(1, “string”); // polymorphic, level 2 x(3.14, 1); // polymorphic, level 3

เมื่อ x(a,b) ทำงานเป็นครั้งแรก V8 จะสร้างไอซีโมโนมอร์ฟิค เมื่อคุณเรียก x เป็นครั้งที่สอง V8 จะลบ IC เก่าและสร้าง IC แบบ polymorphic ใหม่ ซึ่งรองรับตัวถูกดำเนินการทั้งสองประเภทเป็นจำนวนเต็มและสตริง เมื่อคุณเรียก IC เป็นครั้งที่สาม V8 จะทำซ้ำขั้นตอนเดียวกันและสร้าง IC แบบโพลีมอร์ฟิกอีกระดับที่ 3

อย่างไรก็ตาม มีข้อจำกัด หลังจากที่ระดับ IC ถึง 5 (สามารถเปลี่ยนแปลงได้ด้วยแฟ ล็ก –max_inlining_levels ) ฟังก์ชันจะกลายเป็น megamorphic และไม่ถือว่าเหมาะสมอีกต่อไป

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

อย่าเพิ่มไฟล์ขนาดใหญ่ลงในหน่วยความจำ

อันนี้ชัดเจนและเป็นที่รู้จักกันดี หากคุณมีไฟล์ขนาดใหญ่ที่ต้องดำเนินการ เช่น ไฟล์ CSV ขนาดใหญ่ ให้อ่านทีละบรรทัดและประมวลผลเป็นชิ้นเล็กๆ แทนการโหลดไฟล์ทั้งหมดไปยังหน่วยความจำ มีบางกรณีที่ค่อนข้างหายากที่ csv บรรทัดเดียวจะมีขนาดใหญ่กว่า 1mb ทำให้คุณใส่ลงใน New Space ได้

ห้ามบล็อคเซิฟเวอร์หลัก

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

อย่าสร้างข้อมูลที่ไม่จำเป็น

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

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

รู้จักเครื่องมือของคุณ

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

บทสรุป

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

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

Joyent เพิ่งเปิดตัว Node.js เวอร์ชันใหม่ซึ่งใช้ V8 เวอร์ชันล่าสุด แอปพลิเคชันบางตัวที่เขียนขึ้นสำหรับ Node.js v0.12.x อาจเข้ากันไม่ได้กับเวอร์ชัน v4.x ใหม่ อย่างไรก็ตาม แอปพลิเคชันต่างๆ จะพบกับประสิทธิภาพการทำงานที่ยอดเยี่ยมและการปรับปรุงการใช้หน่วยความจำภายใน Node.js เวอร์ชันใหม่