นักพัฒนาสร้างเครื่องมือ eBPF เพื่อติดตามการจัดสรรหน่วยความจำของ Go ตามประเภทข้อมูล เผยปัญหาประสิทธิภาพการจัดสรร String

ทีมชุมชน BigGo
นักพัฒนาสร้างเครื่องมือ eBPF เพื่อติดตามการจัดสรรหน่วยความจำของ Go ตามประเภทข้อมูล เผยปัญหาประสิทธิภาพการจัดสรร String

นักพัฒนาได้สร้างเครื่องมือดีบักที่นวัตกรรมใหม่ที่ใช้ eBPF ( Extended Berkeley Packet Filter ) เพื่อติดตามการจัดสรรหน่วยความจำในโปรแกรม Go ตามประเภทข้อมูล ซึ่งแก้ไขช่องว่างที่สำคัญในเครื่องมือ profiling ที่มีอยู่ของ Go ที่สามารถแสดงตำแหน่งที่เกิดการจัดสรรได้ แต่ไม่สามารถบอกได้ว่าข้อมูลประเภทใดถูกจัดสรร

ปัญหาของเครื่องมือ Profiling ในตัวของ Go

เครื่องมือ profiling มาตรฐานของ Go ให้ข้อมูลเชิงลึกที่มีค่าเกี่ยวกับตำแหน่งการจัดสรรหน่วยความจำ แต่ยังไม่เพียงพอเมื่อนักพัฒนาต้องการเข้าใจว่าประเภทข้อมูลใดใช้หน่วยความจำมากที่สุด ข้อจำกัดนี้กลายเป็นปัญหาโดยเฉพาะเมื่อประเภทข้อมูลเฉพาะทำให้เกิดการจัดสรรหนักในหลายตำแหน่งของโค้ด ทำให้ยากต่อการระบุสาเหตุหลักของความดันหน่วยความจำ

ชุมชนต่างประสบปัญหาการจัดสรร string ใน Go มาอย่างยาวนาน นักพัฒนาคนหนึ่งสังเกตว่าการหลีกเลี่ยงการจัดสรร heap สำหรับ string นั้นยากมาก โดยเฉพาะเมื่อใช้ฟังก์ชันการจัดรูปแบบ เนื่องจาก string ที่ส่งไปยังฟังก์ชัน fmt มักหลุดไปยัง heap เนื่องจากข้อจำกัดในการจัดการ interface{}

การใช้งานทางเทคนิคโดยใช้ eBPF

วิธีแก้ปัญหาเกี่ยวข้องกับการแนบ eBPF uprobe เข้ากับฟังก์ชัน mallocgc ภายในของ Go ซึ่งจัดการการจัดสรร heap ทั้งหมด โดยการสกัดกั้นการเรียกฟังก์ชันนี้ เครื่องมือจะจับข้อมูลทั้งขนาดการจัดสรรและข้อมูลประเภทจาก CPU registers ความท้าทายอยู่ที่การถอดรหัสการแสดงประเภทภายในของ Go ซึ่งใช้ integer offsets แทนชื่อ string โดยตรง

การใช้งานต้องการการแยกวิเคราะห์ส่วน ELF executable และสร้างตรรกะการแก้ไขประเภทภายในของ Go ขึ้นใหม่เพื่อแปลง offsets เหล่านี้กลับเป็นชื่อประเภทที่อ่านได้ กระบวนการนี้เกี่ยวข้องกับการนำทางผ่าน linked lists ของข้อมูลโมดูลที่เก็บไว้ในส่วนคงที่ของ executable

การค้นพบแหล่งการจัดสรรที่ซ่อนอยู่

เครื่องมือเผยให้เห็นว่าการจัดสรรจำนวนมากเกิดขึ้นกับ null type pointers เมื่อข้อมูลที่จัดสรรไม่มี pointers เพื่อจับการจัดสรรที่มองไม่เห็นเหล่านี้ probes เพิ่มเติมถูกแนบเข้ากับฟังก์ชัน runtime เช่น makechan, makeslicecopy และ growslice พร้อมกับตัวระบุประเภทที่กำหนดเองเพื่อติดตามแหล่งการจัดสรรที่แตกต่างกัน

ข้อมูลเชิงลึกด้านประสิทธิภาพในโลกจริง

การสืบสวนยืนยันข้อสงสัยเกี่ยวกับรูปแบบโค้ดที่มีปัญหา โดยเฉพาะฟังก์ชันที่ส่งคืน pointers ไปยัง strings แทนที่จะส่งคืน strings โดยตรง รูปแบบต่อต้านทั่วไปปรากฏขึ้นที่ methods ส่งคืน *string เพื่อจัดการกรณี nil ทำให้เกิดการจัดสรร heap ที่ไม่จำเป็นและ pointer indirection เพิ่มเติม

ปัญหาใหญ่ที่สุดคือ string ใดๆ ที่คุณส่งเป็นอาร์กิวเมนต์ไปยังฟังก์ชัน fmt จะถูกย้ายไปยัง heap เพราะ interface{} ถูกนับว่าหลุดจาก stack เสมอ

การปรับปรุงในอนาคตและทางเลือกอื่น

แม้ว่าแนวทาง eBPF นี้จะให้ข้อมูลเชิงลึกทันที แต่ชุมชน Go ตระหนักถึงความจำเป็นในการมีวิธีแก้ปัญหาในตัวที่ดีกว่า การอภิปรายกำลังดำเนินอยู่เกี่ยวกับการเพิ่มข้อมูลประเภทลงใน allocation profiles ผ่าน pprof labels แม้ว่าจะยังมีความท้าทายเกี่ยวกับการใช้งานโดยไม่มี memory leaks ใน long-running profiles

การเปิดตัว Go 1.25 ที่กำลังจะมาถึงรวมการปรับปรุงสำหรับการจัดการ string ที่ควรลดความดันการจัดสรรบางส่วน แก้ไขปัญหาที่มีมายาวนานกับ escape analysis สำหรับพารามิเตอร์ interface{}

เครื่องมือนี้แสดงให้เห็นทั้งพลังของ eBPF สำหรับการตรวจสอบ runtime และวิวัฒนาการที่กำลังดำเนินอยู่ของระบบนิเวศเครื่องมือประสิทธิภาพของ Go แม้ว่าจะเปราะบางตามที่ยอมรับ แต่ก็ให้ข้อมูลเชิงลึกที่มีค่าซึ่งสามารถแนะนำความพยายามในการปรับปรุงประสิทธิภาพในแอปพลิเคชันที่ต้องการหน่วยความจำ

อ้างอิง: Go allocation probe