เราพบว่าโพสต์ด้านล่างอ้างอิงจากบทความของ Nate Berkopec เมื่อปี 2017 ที่ชื่อว่า 'Understanding Ruby GC through GC.stat' ดูเหมือนว่าบางส่วนของบทความนี้ถูกลอกเลียนแบบ ซึ่งเป็นสิ่งที่เราไม่เคยรู้มาก่อนจนกระทั่งผู้เขียนต้นฉบับกล่าวถึงเรื่องนี้ เราเรียกใช้บทความทั้งหมดของเราโดยใช้เครื่องมือลอกเลียนแบบก่อนที่จะเผยแพร่ แต่ก็ไม่สามารถทำได้ เราขออภัยเป็นอย่างสูงต่อเนทและผู้อ่านของเราสำหรับข้อผิดพลาดโดยไม่ได้ตั้งใจนี้
คุณจำเป็นต้องเข้าใจว่าการรวบรวมขยะทำงานอย่างไรใน Ruby เพื่อควบคุมประสิทธิภาพของแอปได้อย่างสมบูรณ์
ในโพสต์นี้ เราจะเจาะลึกถึงวิธีการปรับใช้และปรับแต่งการรวบรวมขยะใน Ruby
ไปกันเถอะ!
โมดูลรวบรวมขยะทับทิม
โมดูล Ruby Garbage Collector เป็นอินเทอร์เฟซสำหรับเครื่องหมายของ Ruby และกลไกการกวาดขยะ
ในขณะที่ทำงานโดยอัตโนมัติในพื้นหลังเมื่อจำเป็น โมดูล GC ช่วยให้คุณเรียกใช้ GC ด้วยตนเองได้ทุกเมื่อที่ต้องการ และรับข้อมูลเชิงลึกเกี่ยวกับวิธีการทำงานของรอบการรวบรวมขยะ โมดูลนี้มีพารามิเตอร์บางอย่างซึ่งคุณสามารถปรับเปลี่ยนเพื่อเพิ่มประสิทธิภาพได้
วิธีการที่ใช้บ่อยที่สุดของโมดูลนี้คือ:
- เริ่ม/garbage_collect :วิธีนี้จะเริ่มต้นรอบการรวบรวมขยะด้วยตนเอง
- เปิด/ปิด :วิธีการเหล่านี้เปิดใช้งานหรือปิดใช้งานรอบการรวบรวมขยะอัตโนมัติ จะส่งคืนค่าบูลีนที่ระบุว่าการดำเนินการสำเร็จหรือไม่
- สถิติ :เมธอดนี้แสดงรายการคีย์และค่าที่อธิบายประสิทธิภาพของโมดูล GC เราจะดูรายละเอียดเมตริกเหล่านี้ในหัวข้อถัดไป
ทำความเข้าใจเกี่ยวกับพารามิเตอร์ของ Ruby Garbage Collector
เพื่อให้เข้าใจว่า GC ของ Ruby ทำงานอย่างไรภายใน มาดูเมตริกของโมดูล GC กัน เรียกใช้คำสั่งต่อไปนี้บน irb ที่เพิ่งบูทใหม่:
puts GC.stat
คุณจะสังเกตเห็นว่าตัวเลขจำนวนมากปรากฏขึ้นบนหน้าจอของคุณ โดยมีลักษณะดังนี้:
{
:count=>12,
:heap_allocated_pages=>49,
:heap_sorted_length=>49,
:heap_allocatable_pages=>0,
:heap_available_slots=>19975,
:heap_live_slots=>19099,
:heap_free_slots=>876,
:heap_final_slots=>0,
:heap_marked_slots=>16659,
:heap_eden_pages=>49,
:heap_tomb_pages=>0,
:total_allocated_pages=>49,
:total_freed_pages=>0,
:total_allocated_objects=>66358,
:total_freed_objects=>47259,
:malloc_increase_bytes=>16216,
:malloc_increase_bytes_limit=>16777216,
:minor_gc_count=>10,
:major_gc_count=>2,
:remembered_wb_unprotected_objects=>191,
:remembered_wb_unprotected_objects_limit=>312,
:old_objects=>16024,
:old_objects_limit=>23556,
:oldmalloc_increase_bytes=>158824,
:oldmalloc_increase_bytes_limit=>16777216
}
ข้อมูลนี้จะเก็บข้อมูลทั้งหมดเกี่ยวกับการรวบรวมขยะที่เกิดขึ้นในรันไทม์ มาตรวจสอบตัวเลขเหล่านี้อย่างละเอียดกัน
นับใน Ruby Garbage Collector
เราจะเริ่มต้นด้วยการอธิบายคีย์เหล่านี้:
{
:count=>12,
#…
:minor_gc_count=>10,
:major_gc_count=>2,
}
สิ่งเหล่านี้คือจำนวน GC และให้ข้อมูลที่ค่อนข้างตรงไปตรงมา minor_gc_count
และ major_gc_count
เป็นการนับการทำงานของการรวบรวมขยะแต่ละประเภท
มีการรวบรวมขยะสองประเภทใน Ruby
Minor GC หมายถึงความพยายามในการรวบรวมขยะที่พยายามรวบรวมเฉพาะวัตถุที่เป็นของใหม่เท่านั้น นั่นคือ พวกมันรอดจากรอบการรวบรวมขยะสามรอบหรือน้อยกว่านั้น
ในทางกลับกัน GC หลักคือความพยายามในการรวบรวมขยะที่พยายามรวบรวมวัตถุทั้งหมด แม้แต่วัตถุที่รอดชีวิตจากรอบการรวบรวมขยะมากกว่าสามรอบ count
คือผลรวมของ minor_gc_count
และ major_gc_count
.
การติดตามจำนวน GC อาจมีประโยชน์ด้วยเหตุผลบางประการ คุณสามารถทราบได้ว่างานหรือกระบวนการใดเรียกใช้ GC เสมอหรือไม่ และจำนวนครั้งที่เรียกใช้งานหรือกระบวนการนั้น อาจไม่ถูกต้อง 100% ในกรณีเช่นแอปพลิเคชันแบบมัลติเธรด แต่เป็นจุดเริ่มต้นที่ดีในการค้นหาว่าหน่วยความจำของคุณมีเลือดออกที่ใด
Heap Numbers:สล็อตและเพจ
ต่อไป มาพูดถึงคีย์เหล่านี้กัน หรือที่เรียกว่า heap number :
{
# page numbers
:heap_allocated_pages=>49,
:heap_sorted_length=>49,
:heap_allocatable_pages=>0,
# slots
:heap_available_slots=>19975,
:heap_live_slots=>19099,
:heap_free_slots=>876,
:heap_final_slots=>0,
:heap_marked_slots=>16659,
# Eden and Tomb pages
:heap_eden_pages=>49,
:heap_tomb_pages=>0,
}
ฮีปที่เรากำลังพูดถึงที่นี่คือโครงสร้างข้อมูล C มันมีการอ้างอิงถึงวัตถุ Ruby ที่มีอยู่ทั้งหมดในปัจจุบัน ฮีป หน้า ประกอบด้วยสล็อตหน่วยความจำ และแต่ละสล็อตมีข้อมูลเกี่ยวกับออบเจ็กต์ Ruby ที่ใช้งานอยู่เพียงรายการเดียว:
heap_allocated_pages
คือจำนวนฮีปเพจที่จัดสรรในปัจจุบัน หน้าเหล่านี้อาจว่างเปล่า เติมทั้งหมด หรือเติมบางส่วนheap_sorted_length
คือขนาดจริงที่ฮีปครอบครองในหน่วยความจำและแตกต่างจากheap_allocated_pages
เนื่องจากความยาวคือ ความยาว ของฮีปเพจที่รวมกัน ไม่ใช่ จำนวน . หากคุณได้จัดสรรหน้าไว้ 10 หน้าในตอนแรก จากนั้นจึงปล่อยหนึ่งหน้าจากตรงกลางชุดheap_allocated_pages
ของคุณ จะเป็น 9 แต่heap_sorted_length
จะยังคงเป็น 10- สุดท้าย
heap_allocatable_pages
คือจำนวนฮีปที่ Ruby มีอยู่ในปัจจุบัน ซึ่งสามารถใช้ได้เมื่อจำเป็น
มาถึงสล็อตแล้ว:
heap_available_slots
คือจำนวนช่องทั้งหมดที่ใช้ได้ในหน้าฮีปheap_live_slots
คือจำนวนวัตถุที่มีชีวิตในหน่วยความจำheap_free_slots
เป็นช่องในหน้าฮีปที่จัดสรรไว้ซึ่งว่างเปล่าheap_final_slots
คือจำนวนช่องที่วัตถุมี ผู้เข้ารอบสุดท้าย ติดอยู่กับพวกเขา Finalizers คือ Procs ที่ทำงานเมื่อวัตถุว่าง คล้ายกับตัวทำลายใน OOPSheap_marked_slots
คือจำนวนอ็อบเจ็กต์เก่า (เช่น อ็อบเจ็กต์ที่อยู่รอบ GC มากกว่า 3 รอบ) และอ็อบเจ็กต์ที่ไม่มีการป้องกันสิ่งกีดขวางการเขียน
แล้วเราก็มี tomb_pages
และ eden_pages
.
tomb_pages
คือจำนวนหน้าที่ไม่มีวัตถุอยู่ หน้าเหล่านี้จะถูกปล่อยกลับสู่ระบบปฏิบัติการในที่สุดโดย Ruby
ในทางกลับกัน eden_pages
คือจำนวนหน้าที่มีวัตถุอยู่อย่างน้อยหนึ่งรายการ ดังนั้นจึงไม่สามารถปล่อยกลับไปยังระบบปฏิบัติการได้
พิจารณาตรวจสอบเมตริก heap_free_slots
หากคุณประสบปัญหาหน่วยความจำล้นในแอปพลิเคชันของคุณ
สล็อตว่างจำนวนมาก (มากกว่า 250,000) มักจะบ่งบอกว่าคุณมีแอคชันคอนโทรลเลอร์จำนวนหนึ่งที่จัดสรรอ็อบเจ็กต์จำนวนมากในคราวเดียวและปล่อยให้ว่าง ซึ่งอาจทำให้ขนาดของกระบวนการ Ruby ทำงานอยู่ขยายใหญ่ขึ้นอย่างถาวร
จำนวนสะสม
{
:total_allocated_pages=>49,
:total_freed_pages=>0,
:total_allocated_objects=>66358,
:total_freed_objects=>47259,
}
ตัวเลขเหล่านี้เป็นตัวเลขสะสมหรือบวกเพิ่มตลอดอายุของกระบวนการ GC จะไม่รีเซ็ตพวกเขาและไม่สามารถลงได้ในทางเทคนิค ตัวเลขทั้งสี่นี้เป็นตัวอธิบายตนเอง
เกณฑ์การเก็บขยะ
เพื่อให้เข้าใจตัวเลขเหล่านี้ ก่อนอื่นคุณต้องเข้าใจเมื่อ GC ถูกทริกเกอร์:
{
:malloc_increase_bytes=>16216,
:malloc_increase_bytes_limit=>16777216,
:remembered_wb_unprotected_objects=>191,
:remembered_wb_unprotected_objects_limit=>312,
:old_objects=>16024,
:old_objects_limit=>23556,
:oldmalloc_increase_bytes=>158824,
:oldmalloc_increase_bytes_limit=>16777216
}
ตรงกันข้ามกับสมมติฐานทั่วไปที่ว่าการรัน GC เกิดขึ้นในช่วงเวลาที่กำหนด การรัน GC จะถูกทริกเกอร์เมื่อ Ruby เริ่มใช้พื้นที่หน่วยความจำไม่เพียงพอ Minor GC เกิดขึ้นเมื่อ Ruby หมด free_slots
.
หากทับทิมยังเหลือ free_slots
หลังจากการรัน GC เล็กน้อย — หรือขีดจำกัดของ oldmalloc, malloc, จำนวนอ็อบเจ็กต์เก่า หรือ ร่มรื่น /write-barrier-unprotected เกินจำนวน — การรัน GC หลักถูกทริกเกอร์ ส่วนด้านบนของ gc.stat แสดงค่าของเกณฑ์เหล่านี้
malloc_increase_bytes
หมายถึงจำนวนหน่วยความจำที่จัดสรรภายนอก heap เราพูดถึงจนถึงตอนนี้ เมื่อขนาดของวัตถุเกินขนาดมาตรฐานของสล็อตหน่วยความจำ — พูด 40 ไบต์ — Ruby malloc
เป็นพื้นที่อื่นเพียงสำหรับวัตถุนั้น เมื่อพื้นที่ที่จัดสรรเพิ่มเติมทั้งหมดเกิน malloc_increase_bytes_limit
, GC หลักถูกเรียก
oldmalloc_increase_bytes
เป็นธรณีประตูที่คล้ายกันสำหรับวัตถุเก่า old_objects
คือจำนวนช่องอ็อบเจ็กต์ที่ทำเครื่องหมายว่าเก่า เมื่อตัวเลขเกิน old_objects_limit
, GC หลักถูกเรียก
remembered_wb_unprotected_objects
คือจำนวนรวมของอ็อบเจ็กต์ที่ไม่ได้รับการป้องกันโดย write-barrier และเป็นส่วนหนึ่งของ ชุดที่จำได้ .
อุปสรรคการเขียนเป็นส่วนต่อประสานระหว่างรันไทม์ของ Ruby กับอ็อบเจ็กต์ ซึ่งช่วยให้ล่ามติดตามการอ้างอิงถึงและจากอ็อบเจ็กต์ทันทีที่สร้างขึ้น
ส่วนขยาย C สามารถสร้างการอ้างอิงใหม่ไปยังวัตถุโดยไม่ต้องใช้สิ่งกีดขวางการเขียน ซึ่งในกรณีนี้ วัตถุจะถูกทำเครื่องหมาย ร่มรื่น หรือ ไม่มีการป้องกันการขีดเขียน . ชุดที่จำได้เป็นเพียงรายการของ เก่า ออบเจ็กต์ที่มีการอ้างอิงถึง ใหม่ . อย่างน้อยหนึ่งรายการ วัตถุ
ปรับแต่งประสิทธิภาพการเก็บขยะทับทิม
เมื่อคุณเข้าใจวิธีที่ Ruby GC จัดการหน่วยความจำของแอปพลิเคชันแล้ว ก็ถึงเวลาดูตัวเลือกต่างๆ ที่มีให้เพื่อปรับแต่งการทำงานของ GC
ต่อไปนี้คือตัวแปรสภาพแวดล้อมที่คุณสามารถใช้เพื่อกลั่นกรองประสิทธิภาพของ Ruby GC และในทางกลับกัน ปรับปรุงประสิทธิภาพของแอปพลิเคชันของคุณ:
RUBY_GC_HEAP_INIT_SLOTS
RUBY_GC_HEAP_FREE_SLOTS
RUBY_GC_HEAP_GROWTH_FACTOR
RUBY_GC_HEAP_GROWTH_MAX_SLOTS
RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR
and other variables
มาพูดถึงพารามิเตอร์ที่สำคัญกันที่นี่ทีละตัว:
RUBY_GC_HEAP_INIT_SLOTS
:กำหนดจำนวนช่องเริ่มต้นบน Ruby heap และตั้งค่าเป็น 10000 ตามค่าเริ่มต้น คุณอาจต้องการเปลี่ยนพารามิเตอร์นี้หากคุณแน่ใจว่าแอปของคุณจะจัดสรรออบเจ็กต์ส่วนใหญ่ในตอนแรกRUBY_GC_HEAP_FREE_SLOTS
:ควบคุมจำนวนช่องว่างขั้นต่ำที่ต้องพร้อมใช้งานทันทีหลังจากรอบ GC ค่าเริ่มต้นคือ 4096 ค่านี้ใช้เพียงครั้งเดียวที่รันไทม์ระหว่างการเติบโตของฮีปครั้งแรกRUBY_GC_HEAP_GROWTH_FACTOR
:ปัจจัยที่ฮีปที่มีอยู่สำหรับล่าม Ruby เติบโต ค่าเริ่มต้นคือ 1.8 การเปลี่ยนแปลงนี้ไม่สมเหตุสมผล เนื่องจาก Ruby นั้นมีความก้าวร้าวอยู่แล้วในการเติบโตของฮีป มันจะไม่สร้างความแตกต่างมากนักหากคุณลองลดมันลง เนื่องจากมีการจัดสรรฮีปตามความต้องการในล่ามสมัยใหม่RUBY_GC_HEAP_GROWTH_MAX_SLOTS
:จำนวนช่องสูงสุดที่ Ruby สามารถเพิ่มลงใน Heap Space ได้ในคราวเดียว ค่าเริ่มต้นคือ 0 ซึ่งหมายถึงไม่จำกัดจำนวน หากแอปของคุณต้องการจัดสรรออบเจ็กต์หลายล้านรายการในช่วงอายุ คุณอาจต้องการกำหนดพารามิเตอร์นี้ อย่างไรก็ตาม มันจะมีผลค่อนข้างต่ำต่อเวลา GC ของแอปของคุณRUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR
บังคับให้ล่ามดำเนินการรอบ GC ที่สำคัญเมื่อจำนวนวัตถุเก่าทั้งหมดในหน่วยความจำ =มากกว่าจำนวนนี้ x จำนวนวัตถุเก่าในหน่วยความจำหลังจากรอบ GC ล่าสุด คุณอาจต้องการเพิ่มจำนวนนี้หากคุณคิดว่าวัตถุจำนวนมากของคุณจะไม่ได้ใช้หลังจากเข้าสู่ยุคเก่า อย่างไรก็ตาม สิ่งนี้ไม่ค่อยมีความจำเป็นนักRUBY_GC_MALLOC_LIMIT
คือขีดจำกัดขั้นต่ำของการเรียก malloc สำหรับคนรุ่นใหม่ ค่าเริ่มต้นคือ 16 MBRUBY_GC_MALLOC_LIMIT_MAX
คือขีดจำกัดสูงสุดสำหรับการเรียก malloc เดียวกัน ค่าเริ่มต้นคือ 32 MB คุณอาจต้องการเพิ่มขีดจำกัดทั้งสองนี้หากแอปพลิเคชันของคุณใช้หน่วยความจำที่สูงกว่าค่าเฉลี่ย อย่างไรก็ตาม ระวังอย่ายกสิ่งเหล่านี้มากเกินไป มิฉะนั้นอาจทำให้ใช้หน่วยความจำสูงสุดได้ เพิ่มขีดจำกัดเหล่านี้ทีละน้อยเสมอ เช่น 4 หรือ 8 MBRUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR
เป็นปัจจัยการเติบโตของ malloc limit สำหรับคนรุ่นใหม่ ค่าเริ่มต้นคือ 1.4 คุณควรพิจารณาเพิ่มจำนวนนี้หากแอปของคุณจัดสรรหน่วยความจำเป็นส่วนๆ แทนที่จะจัดสรรทั้งหมดในคราวเดียว- ในทำนองเดียวกัน
RUBY_GC_OLDMALLOC_LIMIT
และRUBY_GC_OLDMALLOC_LIMIT_MAX
คือขีดจำกัดขั้นต่ำและสูงสุดสำหรับคนรุ่นเก่า ค่าเริ่มต้นของพารามิเตอร์เหล่านี้คือ 16 MB และ 128 MB RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR
เป็นปัจจัยการเจริญเติบโตของขีด จำกัด นี้ ค่าเริ่มต้นคือ 1.2 คุณสามารถพิจารณาเปลี่ยนแปลงสิ่งเหล่านี้ด้วยขีดจำกัดรุ่นใหม่เพื่อให้ได้ผลสูงสุด
การเก็บขยะแบบละเอียดใน Ruby
เราได้พูดคุยถึงวิธีทั่วไปและวิธีง่ายๆ ในการปรับแต่งโมดูล GC เพื่อช่วยคุณปรับปรุงประสิทธิภาพโดยรวมของแอปพลิเคชัน อย่างไรก็ตาม การปรับแต่งเหล่านี้อาจใช้ไม่ได้ในทุกกรณี คุณต้องหารูปแบบการใช้หน่วยความจำของแอปก่อนตัดสินใจว่าจะปรับแต่งอะไร
ในทางกลับกัน คุณสามารถลองใช้การทดสอบอัตโนมัติเพื่อค้นหาค่าพารามิเตอร์เหล่านี้ที่ดีที่สุดสำหรับคุณ เครื่องมืออย่าง TuneMyGC ค่อนข้างตรงไปตรงมาเมื่อค้นหาชุดค่าที่ดีที่สุดสำหรับตัวแปรสภาพแวดล้อมของคุณ
ดูพารามิเตอร์ GC อย่างแน่นอนหากแอปพลิเคชันของคุณทำงานผิดปกติ การเปลี่ยนแปลงเล็กๆ น้อยๆ ในที่นี้อาจช่วยลดการใช้หน่วยความจำของแอปและป้องกันไม่ให้หน่วยความจำเพิ่มขึ้น
ฉันหวังว่าบทความนี้จะช่วยให้คุณมีความคิดที่ดีเกี่ยวกับสิ่งที่ควรระวังเมื่อปรับแต่งโมดูล Ruby Garbage Collection ของคุณ สำหรับข้อมูลเพิ่มเติม โปรดดูบทนำสู่การเก็บขยะตอนที่ 1 และตอนที่ II
ขอให้สนุกกับการเขียนโค้ด!