Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

เชี่ยวชาญ Ruby GC: Practical Tuning สำหรับ Optimal Performance

เราพบว่าโพสต์นี้อิงจากบทความของ Nate Berkopec ปี 2017 'Understanding Ruby GC through GC.stat' ส่วนหนึ่งลอกเลียนแบบ ซึ่งเราไม่รู้จนกว่าผู้เขียนต้นฉบับจะบอก เราตรวจสอบ plagiarism ก่อนเผยแพร่แต่ไม่จับได้ ขอโทษนักเขียนและผู้อ่านมาก

เข้าใจ garbage collection ใน Ruby สำคัญมาก เพื่อควบคุม performance ของ app ให้เต็มที่

ในโพสต์นี้ จะลุยการ implement และ customize garbage collection ใน Ruby

เริ่มกันเลย!

Ruby Garbage Collector Module

Ruby GC module เป็น interface ของ mark-and-sweep garbage collection mechanism ของ Ruby

มันรันอัตโนมัติใน background เมื่อจำเป็น แต่ GC module ให้เรียก GC manually เมื่อต้องการ และได้ insights ว่า GC cycles ทำงานอย่างไร Module มี parameters ปรับเพื่อ moderate performance

เมธอด常用:

  • start/garbage_collect: เริ่ม GC cycle manually
  • enable/disable: เปิด/ปิด automatic GC cycles คืน boolean ว่าสำเร็จ
  • stat: คืน keys/values อธิบาย performance ของ GC module 详细在下一节

เข้าใจ Ruby Garbage Collector Parameters

เข้าใจ GC ทำงานภายในอย่างไร ดู metrics ของ GC module รันคำสั่งบน irb เพิ่ง boot:

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 }

เก็บข้อมูลทั้งหมดเกี่ยวกับ GC ใน runtime มาวิเคราะห์แต่ละตัวเลข

Counts ใน Ruby GC

เริ่มจาก keys นี้:

{ :count=>12, #… :minor_gc_count=>10, :major_gc_count=>2, }

นี่คือ GC counts สื่อข้อมูลตรงไปตรงมา minor_gc_count และ major_gc_count คือจำนวนครั้งของแต่ละประเภท GC

มี 2 ประเภท GC ใน Ruby

Minor GC = พยายาม GC เฉพาะ objects ใหม่ (survived ≤ 3 GC cycles)

Major GC = พยายาม GC ทุก objects รวมถึงที่ survived > 3 cycles count = ผลรวม minor + major

Track GC count ช่วยหา job/process ที่ trigger GC บ่อยแค่ไหน อาจไม่ 100% accurate (multi-threaded) แต่จุดเริ่มต้นดีสำหรับหา memory leak

Heap Numbers: Slots และ Pages

ต่อมา keys นี้ เรียกว่า heap numbers:

{ # 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 และ Tomb pages :heap_eden_pages=>49, :heap_tomb_pages=>0, }

Heap ที่พูดถึงคือ C data structure เก็บ references ทุก live Ruby objects Heap page ประกอบด้วย memory slots แต่ละ slot เก็บ info ของ live Ruby object เดียว:

  • heap_allocated_pages: จำนวน heap pages ที่ allocated อยู่ สามารถว่าง เต็ม หรือบางส่วนได้
  • heap_sorted_length: ขนาดจริงที่ heap ครอบครองใน memory ต่างจาก allocated_pages เพราะ length = ความยาว heap pages รวมกัน ไม่ใช่ count ถ้า allocate 10 pages แล้ว free 1 จากกลาง allocated_pages=9 แต่ sorted_length=10
  • heap_allocatable_pages: จำนวน heaps ที่ Ruby ครองอยู่ พร้อมใช้เมื่อต้องการ

Slots:

  • heap_available_slots: total slots ใน heap pages
  • heap_live_slots: จำนวน live objects ใน memory
  • heap_free_slots: slots ว่างใน allocated heap pages
  • heap_final_slots: slots ที่ objects มี finalizers (Procs รันตอน object ถูก free เหมือน destructors)
  • heap_marked_slots: จำนวน old objects (survived > 3 GC cycles) และ write-barrier unprotected objects

tomb_pages และ eden_pages:

  • tomb_pages: pages ไม่มี live objects จะถูกคืนให้ OS โดย Ruby
  • eden_pages: pages มี live object อย่างน้อย 1 ไม่คืนให้ OS ได้

Monitor heap_free_slots หากเจอ memory bloat

Free slots มาก (> 250,000) มักบ่งชี้ controller actions จัดสรร objects มากในคราวเดียว แล้ว free ทำให้ Ruby process bloat อย่างถาวร

Cumulative Numbers

{ :total_allocated_pages=>49, :total_freed_pages=>0, :total_allocated_objects=>66358, :total_freed_objects=>47259, }

ตัวเลขสะสมตลอดชีวิต process ไม่เคย reset โดย GC และไม่ลดลง ทั้ง 4 ชัดเจนเอง

Garbage Collection Thresholds

เข้าใจตัวเลขเหล่านี้ ต้องเข้าใจว่า GC triggered เมื่อไหร่:

{ :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 triggered เมื่อ Ruby เริ่มขาด memory space Minor GC เมื่อหมด free_slots

ถ้ายังขาด free_slots หลัง minor GC — หรือเกิน threshold ของ oldmalloc, malloc, old object count, หรือ shady/write-barrier-unprotected count — major GC triggered ส่วนนี้ของ gc.stat แสดงค่า thresholds

malloc_increase_bytes: memory allocated นอก heap ที่พูดถึง ถ้า object size > slot standard size (~40 bytes) Ruby malloc พื้นที่แยกให้ object นั้น ถ้า extra allocated space เกิน malloc_increase_bytes_limit → major GC

oldmalloc_increase_bytes: threshold เหมือนกันแต่สำหรับ old objects old_objects: count object slots marked old เกิน old_objects_limit → major GC

remembered_wb_unprotected_objects: count objects ไม่มี write-barrier protection อยู่ใน remembered set

Write-barrier = interface ระหว่าง Ruby runtime และ objects ให้ interpreter track references ทันทีที่สร้าง

C-extensions สร้าง references ใหม่โดยไม่ใช้ write-barrier → objects marked shady หรือ write-barrier unprotected Remembered set = list ของ old objects ที่มี reference อย่างน้อย 1 ไป new object

Customize Ruby Garbage Collection Performance

เข้าใจ Ruby GC จัดการ memory แล้ว มาดู options customize พฤติกรรม 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 และตัวแปรอื่นๆ

มาพูด parameters สำคัญทีละตัว:

  • RUBY_GC_HEAP_INIT_SLOTS: initial slots บน Ruby heap default 10000 เปลี่ยนได้ถ้าแน่ใจว่า app allocate 部分 lớn objects เริ่มต้น
  • RUBY_GC_HEAP_FREE_SLOTS: ควบคุม minimum free slots หลัง GC cycle default 4096 ใช้แค่ครั้งแรกตอน heap growth ครั้งแรก
  • RUBY_GC_HEAP_GROWTH_FACTOR: factor ที่ available heaps โต default 1.8 เปลี่ยนไม่คุ้ม Ruby aggressive แล้ว ไม่ต่างมากถ้าลด เพราะ heaps allocated on-demand
  • RUBY_GC_HEAP_GROWTH_MAX_SLOTS: max slots ที่ Ruby เพิ่ม heap ได้ครั้งเดียว default 0 = ไม่ limit ถ้า app allocate ล้านๆ objects อาจ cap parameter นี้ แต่ effect ต่อ GC time น้อย
  • RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR factor นี้ x old objects หลัง GC ล่าสุด เพิ่มได้ถ้าคิดว่ามี objects มากที่จะ unused หลังเข้า old generation แต่ rarely needed
  • RUBY_GC_MALLOC_LIMIT: min limit malloc calls สำหรับ new generation default 16 MB RUBY_GC_MALLOC_LIMIT_MAX: max limit เดียวกัน default 32 MB เพิ่มได้ถ้า app ใช้ memory สูงกว่าเฉลี่ย แต่ระวัง peak memory consumption เพิ่มทีละ 4-8 MB
  • RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR: growth factor malloc limit new gen default 1.4 เพิ่มได้ถ้า app allocate memory แบบช่วงๆ ไม่ใช่ทั้งหมด
  • RUBY_GC_OLDMALLOC_LIMIT และ RUBY_GC_OLDMALLOC_LIMIT_MAX: min/max malloc limits สำหรับ old generation default 16 MB และ 128 MB
  • RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR: growth factor ของ limit นี้ default 1.2 เปลี่ยนคู่กับ new gen limits เพื่อ max effect

Fine-tuning Garbage Collection ใน Ruby

พูดถึง common/simple ways customize GC module ช่วย improve performance แต่อาจไม่ work ทุกกรณี ต้องเข้าใจ memory usage pattern ของ app ก่อนตัดสินใจ customize

อีกทาง: รัน automated test หา best values ของ parameters Tools เช่น TuneMyGC 直截了当 หา best set สำหรับ env variables

ดู GC parameters หาก app 行為แปลก การเปลี่ยนเล็กน้อย ลด memory consumption และป้องกัน memory bloats ได้มาก

หวังว่าบทความนี้ให้ความเข้าใจดีเกี่ยวกับการ customize Ruby GC module อ่านเพิ่ม: Introduction to Garbage Collection Part I และ Part II

Happy coding!