หน่วยความจำรั่วคือการใช้หน่วยความจำเพิ่มขึ้นโดยไม่ได้ตั้งใจ ไม่มีการควบคุม และไม่รู้จักจบสิ้น ไม่ว่าท้ายที่สุดแล้วการรั่วไหลจะเล็กน้อยเพียงใดก็ตาม จะทำให้กระบวนการของคุณมีหน่วยความจำไม่เพียงพอและหยุดทำงาน แม้ว่าคุณจะรีสตาร์ทแอปเป็นระยะๆ เพื่อหลีกเลี่ยงข้อขัดข้องนี้ (ไม่ได้ตัดสิน ฉันได้ทำไปแล้ว!) คุณยังคงประสบปัญหาด้านประสิทธิภาพจากหน่วยความจำรั่ว
ในโพสต์นี้ ซึ่งเป็นบทความสองตอนแรกเกี่ยวกับการรั่วไหลของหน่วยความจำ เราจะเริ่มด้วยการดูว่า Ruby จัดการหน่วยความจำอย่างไร ทำงาน Garbage Collection (GC) อย่างไร และจะค้นหาการรั่วไหลได้อย่างไร
ในส่วนที่สอง เราจะเจาะลึกยิ่งขึ้นในการติดตามการรั่วไหล
มาเริ่มกันเลย!
การจัดการหน่วยความจำ Ruby
อ็อบเจ็กต์ Ruby จะถูกเก็บไว้บนฮีป และแต่ละอ็อบเจ็กต์จะเติมหนึ่งช่องบนฮีป
ก่อน Ruby 3.1 ช่องทั้งหมดบนฮีปจะมีขนาดเท่ากัน — 40 ไบต์ ถ้าพูดตรงๆ วัตถุที่มีขนาดใหญ่เกินไปที่จะใส่ในช่องถูกเก็บไว้นอกฮีป แต่ละช่องมีการอ้างอิงถึงตำแหน่งที่วัตถุถูกย้าย
ใน Ruby 3.1 การจัดสรรความกว้างของตัวแปรสำหรับ 05 วัตถุถูกรวมเข้าด้วยกัน ในไม่ช้า การจัดสรรความกว้างของตัวแปรจะกลายเป็นบรรทัดฐานสำหรับประเภทออบเจ็กต์ทั้งหมด
การจัดสรรความกว้างแบบแปรผันมีจุดมุ่งหมายเพื่อปรับปรุงประสิทธิภาพโดยการปรับปรุงตำแหน่งแคช ข้อมูลทั้งหมดของออบเจ็กต์จะถูกจัดเก็บไว้ในที่เดียว แทนที่จะเก็บไว้ในตำแหน่งหน่วยความจำสองตำแหน่ง
นอกจากนี้ยังควรทำให้การจัดการหน่วยความจำง่ายขึ้น (บางส่วน) ในขณะนี้ มี 'ฮีป' สองรายการ:
- ฮีป Ruby (หรือฮีป GC) ที่เก็บอ็อบเจ็กต์ Ruby ที่เล็กกว่า
- ฮีป C (หรือฮีป malloc/ชั่วคราว) ที่เก็บอ็อบเจ็กต์ขนาดใหญ่
เมื่อการจัดสรรความกว้างของตัวแปรเป็นเรื่องปกติ ก็ไม่ควรจำเป็นต้องมีฮีปหลัง
ฮีปเริ่มต้นที่ขนาดที่กำหนด (10,000 ช่องโดยค่าเริ่มต้น) และออบเจ็กต์จะถูกกำหนดให้กับช่องว่างในขณะที่ถูกสร้างขึ้น เมื่อ Ruby พยายามสร้างอ็อบเจ็กต์และไม่มีช่องว่าง Garbage Collection (GC) จะทำให้มีช่องว่างว่าง
หากมีช่องว่างน้อยเกินไปหลังจาก GC ฮีปจะถูกขยาย (เพิ่มเติมเกี่ยวกับเรื่องนี้ในภายหลังเล็กน้อย)
ต่อไปนี้คือปัจจัยที่คุณสามารถควบคุมได้ ควบคู่ไปกับตัวแปรสภาพแวดล้อม:
- ขนาดเริ่มต้นของฮีป -
17 - จำนวนช่องว่างที่ควรมีหลังจาก GC เกิดขึ้น -
23 - จำนวนฮีปที่ถูกขยายด้วย -
33
การรวบรวมขยะใน Ruby
การรวบรวมขยะใน Ruby 'หยุดโลก' - ไม่มีกระบวนการอื่นเกิดขึ้นเมื่อ GC เกิดขึ้น Garbage Collection ใน Ruby(ตั้งแต่ 2.1) ก็มี รุ่น เช่นกัน หมายความว่าตัวรวบรวมขยะมีสองโหมด:
- ไมเนอร์ GC - ตรวจสอบวัตถุ 'อายุน้อย' (วัตถุที่สร้างขึ้นเมื่อเร็ว ๆ นี้)
- เมเจอร์ GC - ตรวจสอบวัตถุ 'เก่า' เช่นเดียวกับวัตถุ 'อายุน้อย' (ทั้งหมด วัตถุ)
หมายเหตุ :วัตถุ 'เก่า' ยังคงอยู่ 41 GC ทำงาน หลักหรือรอง
เมื่อฮีปเต็ม GC รองจะถูกเรียกใช้ก่อน หากไม่สามารถเพิ่มพื้นที่ว่างได้มากพอที่จะต่ำกว่าขีดจำกัด GC หลักจะถูกเรียกใช้ จากนั้นเท่านั้น หากยังมีช่องว่างไม่เพียงพอ ฮีปจะถูกขยาย
Major GC มีราคาแพงกว่า minor GC เนื่องจากดูวัตถุได้มากกว่า
ทฤษฎีเบื้องหลังว่าทำไม GC รุ่นรุ่นจึงมีประสิทธิภาพมากกว่าก็คือวัตถุมักจะแบ่งออกเป็นสองประเภท:
- วัตถุที่ได้รับการจัดสรรแล้วออกนอกขอบเขตอย่างรวดเร็ว ในแอป Rails โมเดลที่ดึงมาจากฐานข้อมูลเพื่อเรนเดอร์เพจจะอยู่นอกขอบเขตเมื่อคำขอสิ้นสุดลง
- วัตถุที่ได้รับการจัดสรรและเก็บไว้เป็นเวลานาน คลาสและแคชมีแนวโน้มที่จะยังคงใช้งานอยู่ตลอดอายุของแอป
Major GC จะทำงานตาม minor GC หากจำนวนของอ็อบเจ็กต์เก่าเกินขีดจำกัดที่กำหนด แม้ว่าจะมีช่องว่างเพียงพอก็ตาม ขีดจำกัดนี้จะเพิ่มขึ้นเมื่อขนาดของฮีปเพิ่มขึ้นและสามารถควบคุมได้ด้วย58 ตัวแปรสภาพแวดล้อม
เมื่อคุณมีรอยรั่ว คุณสร้างวัตถุที่ไม่สามารถทำความสะอาดได้ — มากขึ้นเรื่อยๆ 64 วัตถุ ซึ่งหมายความว่า GC หลัก (ราคาแพง) จะทำงานบ่อยกว่าที่ควรจะเป็น เนื่องจากไม่มีสิ่งใดทำงานอีกในขณะที่ GC กำลังทำงานอยู่ นี่จึงเป็นเวลาที่คุณต้องเสียไป
ฉันทิ้งลิงก์ไว้ท้ายบทความนี้เพื่ออ่านเพิ่มเติมเกี่ยวกับ memorylayout และตัวรวบรวมขยะใน Ruby
หน่วยความจำรั่วใน Ruby มีลักษณะเป็นอย่างไร
คุณสามารถดูหน่วยความจำรั่วได้โดยใช้เครื่องมือง่ายๆ ที่มีอยู่ในระบบ Unix ใช้โค้ดต่อไปนี้เป็นตัวอย่าง
การจะบอกว่าโค้ดนี้ 'รั่ว' นั้นไม่ยุติธรรมเลยสักนิด — สิ่งเดียวที่ทำคือรั่วไหล! —แต่มันตอบสนองวัตถุประสงค์ของเรา
เราสามารถสังเกตการรั่วไหลได้ง่ายๆ จากบรรทัดคำสั่งโดยการรันโปรแกรมนี้ในเทอร์มินัลเดียวและ 78 - หน่วยความจำเพิ่มขึ้นเมื่อเวลาผ่านไปด้วย 80 .
93รหัส> ค้นหา ID กระบวนการสำหรับเรา เพื่อให้เราสามารถจำกัด 109 ได้ ส่งออกไปยังกระบวนการที่เราสนใจเท่านั้น คุณอาจเดาได้ว่ามันเหมือนกับ 113 สำหรับกระบวนการ
120รหัส> เครื่องมือช่วยให้เราสามารถสำรวจผลลัพธ์ของคำสั่งที่กำหนดและอัปเดตได้ ทำให้เรามีแดชบอร์ดแบบสดภายในเทอร์มินัลของเรา
คุณจะได้ผลลัพธ์เช่นนี้ ซึ่งจะอัปเดตทุกๆ สองสามวินาที
คุณควรเห็น 131 และ 146รหัส> เพิ่มขึ้น. พวกเขาคือ:
154รหัส>- จำนวนหน่วยความจำที่กระบวนการใช้เป็นเปอร์เซ็นต์ของหน่วยความจำบนเครื่องโฮสต์163รหัส>(ขนาดชุดถิ่นที่อยู่) - จำนวน RAM ที่กระบวนการใช้ในหน่วยไบต์
ข้อมูลพื้นฐานเฉพาะระบบปฏิบัติการนี้เพียงพอที่จะระบุได้ว่าคุณมีรอยรั่วหรือไม่ หากหน่วยความจำเพิ่มขึ้นเรื่อยๆ แสดงว่าเป็นเช่นนั้น!
ค้นหารูบี้รั่วด้วยโมดูลตัวรวบรวมขยะ
นอกจากนี้เรายังสามารถตรวจจับการรั่วไหลภายในโค้ด Ruby ได้ด้วย 172 โมดูล.
185รหัส> method จะส่งคืนแฮชพร้อมข้อมูลที่เป็นประโยชน์มากมาย ที่นี่เราสนใจ 195 ซึ่งเป็นจำนวนสล็อตบนฮีปที่ใช้งานอยู่ ซึ่งตรงข้ามกับ 209 .ในตอนท้ายของลูป เราบังคับ GC หลักและพิมพ์จำนวนช่องที่ใช้แล้ว เช่น จำนวนอ็อบเจ็กต์ที่ยังคงอยู่หลังจาก GC
เมื่อเรารันโปรแกรมเล็กๆ ของเรา เราเห็นการเพิ่มขึ้นอย่างไม่สิ้นสุด เรามีรอยรั่ว! เรายังสามารถใช้ 213 ได้ด้วย ให้ได้ผลเหมือนกัน
ในขณะที่ 228 โมดูลสามารถใช้เพื่อดู if เรามีการรั่วไหล และ (หากคุณรีสมาร์ทด้วย 239 ของคุณ คำสั่ง) ที่อาจเกิดการรั่วไหล เราสามารถดูประเภทของวัตถุที่อาจรั่วได้ด้วย 249 โมดูล.
251รหัส> วิธีการส่งกลับแฮชด้วยจำนวนวัตถุที่มีชีวิต 261รหัส> ตัวอย่างเช่น คือจำนวนสตริงในหน่วยความจำแบบสด สำหรับโปรแกรมที่ค่อนข้างรั่ว ค่านี้จะเพิ่มขึ้นตามแต่ละลูป แม้จะหลังจาก GC ก็ตาม เราเห็นได้ว่าเรากำลังปล่อยวัตถุสตริงรั่วไหล
การตรวจสอบประสิทธิภาพของแอปพลิเคชันในการผลิตด้วย AppSignal
ขณะเล่นกับ 274 และ 288 อาจเป็นเส้นทางที่เหมาะสมสำหรับโปรเจ็กต์ของเล่น ทั้งยังสนุกและให้ความรู้อีกด้วย! — ฉันจะไม่ แนะนำให้ใช้เป็นโซลูชันการตรวจจับหน่วยความจำรั่วในแอปที่ใช้งานจริง
นี่คือที่ที่คุณจะใช้เครื่องมือ Application Performance Monitoring (APM) หากคุณเป็นบริษัทขนาดใหญ่ คุณสามารถสร้างสิ่งเหล่านี้ได้ด้วยตัวเอง อย่างไรก็ตาม สำหรับเสื้อผ้าที่มีขนาดเล็ก การเลือก APM นอกชั้นวางคือหนทางที่จะไป คุณจำเป็นต้องชำระค่าสมัครสมาชิกรายเดือน แต่ข้อมูลที่พวกเขาให้มานั้นเกินกว่าจะชดเชยได้
ในการตรวจจับการรั่วไหลของหน่วยความจำ คุณต้องการค้นหากราฟการใช้หน่วยความจำของเซิร์ฟเวอร์หรือกระบวนการ (บางครั้งเรียกว่า RSS) เมื่อเวลาผ่านไป นี่คือตัวอย่างภาพหน้าจอจากแดชบอร์ด 'การใช้หน่วยความจำกระบวนการ' ของ AppSignal ของแอปที่มีประสิทธิภาพดีหลังจากใช้งานไม่นาน:

และนี่คือแอปที่ไม่ดีหลังจากการปรับใช้:

AppSignal จะแสดงสถิติ Ruby VM เช่น GC และสล็อตฮีป ซึ่งจะทำให้คุณได้รับสัญญาณที่ชัดเจนยิ่งขึ้นสำหรับหน่วยความจำรั่ว หากจำนวนสล็อตสดเพิ่มขึ้น แสดงว่าคุณรั่ว!

อ่านเพิ่มเติมเกี่ยวกับ AppSignal สำหรับ Ruby
สรุปและอ่านเพิ่มเติม
ในโพสต์นี้ เราได้ทัวร์ชมการจัดการหน่วยความจำและตัวรวบรวมขยะของ Ruby อย่างรวดเร็ว จากนั้นเราวินิจฉัยวิธีค้นหาหน่วยความจำรั่วโดยใช้เครื่องมือ Unix และโมดูล GC ของ Ruby
คราวหน้ามาดูวิธีใช้ 299 กัน และ 300รหัส> เพื่อค้นหาและแก้ไขรอยรั่ว
ในระหว่างนี้ คุณสามารถอ่านเพิ่มเติมเกี่ยวกับเครื่องมือที่เราใช้:
312รหัส>321รหัส>337รหัส>
อ่านเพิ่มเติม:
340รหัส> เอกสารประกอบโมดูล354รหัส> เอกสารประกอบโมดูล- เจาะลึกการเก็บขยะ
- การจัดสรรความกว้างของตัวแปร
ขอให้สนุกกับการเขียนโค้ด แล้วพบกันใหม่ตอนหน้า!
ปล. หากคุณต้องการอ่านโพสต์ Ruby Magic ทันทีที่เผยแพร่ สมัครรับจดหมายข่าว Ruby Magic ของเราและไม่พลาดแม้แต่โพสต์เดียว! ป>