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

การปรับแต่งการรวบรวมขยะในทางปฏิบัติใน Ruby

เราพบว่าโพสต์ด้านล่างอ้างอิงจากบทความของ 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 ที่ทำงานเมื่อวัตถุว่าง คล้ายกับตัวทำลายใน OOPS
  • heap_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 MB RUBY_GC_MALLOC_LIMIT_MAX คือขีดจำกัดสูงสุดสำหรับการเรียก malloc เดียวกัน ค่าเริ่มต้นคือ 32 MB คุณอาจต้องการเพิ่มขีดจำกัดทั้งสองนี้หากแอปพลิเคชันของคุณใช้หน่วยความจำที่สูงกว่าค่าเฉลี่ย อย่างไรก็ตาม ระวังอย่ายกสิ่งเหล่านี้มากเกินไป มิฉะนั้นอาจทำให้ใช้หน่วยความจำสูงสุดได้ เพิ่มขีดจำกัดเหล่านี้ทีละน้อยเสมอ เช่น 4 หรือ 8 MB
  • RUBY_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

ขอให้สนุกกับการเขียนโค้ด!