- ภาษา Go แทบไม่มี พฤติกรรมที่ไม่ถูกกำหนดไว้ และมี เซแมนติกของ GC (garbage collection) ที่เรียบง่าย
- ใน Go สามารถทำ การจัดการหน่วยความจำแบบแมนนวล ได้ และทำได้โดยทำงานร่วมกับ GC
- Arena คือโครงสร้างข้อมูลสำหรับจัดสรรหน่วยความจำอย่างมีประสิทธิภาพให้กับข้อมูลที่มีอายุการใช้งานเท่ากัน และบทความนี้อธิบายวิธีนำแนวคิดนี้มาใช้งานใน Go
- อธิบายวิธีที่ GC จัดการหน่วยความจำผ่านอัลกอริทึม Mark and Sweep
- สามารถใช้ Arena เพื่อปรับปรุง ประสิทธิภาพการจัดสรรหน่วยความจำ ได้ และทำได้ผ่านการปรับแต่งหลายแบบ
- พยายามเพิ่มประสิทธิภาพและลดภาระของ GC ให้ต่ำที่สุดผ่าน การลบ write barrier, การนำหน่วยความจำกลับมาใช้ใหม่, chunk pooling เป็นต้น
- นำเสนอแพตเทิร์นที่ปลอดภัยและรวดเร็วสำหรับการจัดการหน่วยความจำขนาดใหญ่จริง ผ่าน การทำ Realloc, การนำ Arena กลับมาใช้ใหม่ และการรีเซ็ต (Reset)
ภาพรวมของการจัดสรรหน่วยความจำแบบแมนนวลด้วย Arena ใน Go
- Go เป็นภาษาที่ปลอดภัยด้วย การทำงานของ GC ที่ชัดเจน และ Undefined Behavior ที่แทบไม่มี
- สามารถใช้แพ็กเกจ
unsafe เพื่อ ควบคุมหน่วยความจำโดยตรงให้สอดคล้องกับการทำงานภายในของ GC
- บทความนี้อธิบายวิธีสร้างตัวจัดสรรหน่วยความจำแบบ Arena structure ที่สามารถทำงานร่วมกับ GC ได้ใน Go
ความหมายของ Arena และเหตุผลที่ต้องใช้
- Arena คือโครงสร้างสำหรับ จัดสรรอ็อบเจ็กต์ที่มีอายุการใช้งานเท่ากันอย่างมีประสิทธิภาพ
- หาก
append แบบทั่วไปใช้วิธี ขยายอาร์เรย์แบบเอ็กซ์โปเนนเชียล Arena จะใช้วิธี เพิ่มบล็อกใหม่และส่งพอยน์เตอร์กลับไป
- อินเทอร์เฟซมาตรฐานมีดังนี้:
Alloc(size, align uintptr) unsafe.Pointer
พอยน์เตอร์และวิธีการทำงานของ GC
- GC ทำงานด้วยวิธี ติดตามหน่วยความจำ (mark) และกวาดคืน (sweep)
- เพื่อให้เป็น GC แบบแม่นยำ จะใช้เมทาดาทาชื่อ pointer bits เพื่อบอกตำแหน่งของพอยน์เตอร์
- หากจัดการพอยน์เตอร์ใน Arena ผิดพลาด GC อาจติดตามพอยน์เตอร์ไม่เจอ และอาจเกิด ข้อผิดพลาดแบบ Use-After-Free ได้
วิธีออกแบบ Arena
- โครงสร้าง Arena มีฟิลด์ดังนี้:
- การจัดสรรทั้งหมดจะจัดแนวที่ 8 ไบต์ และถ้าไม่พอจะสร้าง chunk ใหม่ด้วย
nextPow2
- chunk จะถูกจัดสรรเป็นชนิด
struct { A [N]uintptr; P *Arena } แทน []uintptr เพื่อให้ GC สามารถติดตาม Arena ได้
วิธีทำให้ Arena ปลอดภัยต่อพอยน์เตอร์
- หากใช้พอยน์เตอร์ที่จัดสรรจากภายใน Arena เท่านั้น GC จะคงทั้ง Arena เอาไว้
- ตั้งค่าให้พอยน์เตอร์อ้างอิง Arena เพื่อ รับประกันว่า Arena ทั้งก้อนจะรอดจาก GC
- เมธอดจัดสรรของ Arena จะทำสิ่งต่อไปนี้:
- เก็บพอยน์เตอร์ของ Arena ไว้ที่ท้าย chunk ใน
allocChunk()
ผลลัพธ์เบนช์มาร์กด้านประสิทธิภาพ
- เมื่อเทียบกับ
new ปกติ การจัดสรรด้วย Arena ให้ประสิทธิภาพดีขึ้นเฉลี่ย มากกว่า 2~4 เท่า
- แม้ในสถานการณ์ที่มีภาระ GC สูง วิธีแบบ Arena ก็ยังให้ประสิทธิภาพเหนือกว่าถึง มากกว่า 2 เท่า
- การปรับแต่งอย่างการลบ write barrier และการใช้
uintptr ช่วยเพิ่มประสิทธิภาพ สูงสุด 20% ในการจัดสรรขนาดเล็ก
กลยุทธ์การนำ Chunk กลับมาใช้ใหม่และการลดการใช้ Heap
- สามารถนำ chunk กลับมาใช้ใหม่ได้ด้วย
sync.Pool
- ใช้
runtime.SetFinalizer() เพื่อคืน chunk กลับเข้าพูลเมื่อ Arena ถูกทำลาย
- ประสิทธิภาพจะดีมากในการจัดสรรขนาดเล็ก แต่ในการจัดสรรขนาดใหญ่ อาจช้ากว่า
new ได้
ฟังก์ชันการรีเซ็ตและนำ Arena กลับมาใช้ใหม่
- สามารถทำให้ Arena กลับไปยังสถานะเริ่มต้นได้ด้วยเมธอด
Reset()
- แม้จะมีความเสี่ยงสูง แต่สามารถนำโครงสร้างเดิมกลับมาใช้ใหม่ได้โดยไม่ต้องจัดสรรหน่วยความจำใหม่
- แม้ตอนนำกลับมาใช้ใหม่ ก็ยังใช้ chunk เดิมซ้ำได้ ทำให้ประสิทธิภาพดีขึ้นอย่างมาก
การทำฟังก์ชัน Realloc
- สามารถทำฟังก์ชัน
realloc ใน Arena เพื่อ ขยายหน่วยความจำแบบไดนามิกสำหรับการจัดสรรล่าสุด ได้
- หากทำไม่ได้ จะจัดสรรหน่วยความจำใหม่แล้วคัดลอกข้อมูลแทน
บทสรุปและโค้ดฉบับเต็ม
- บทความนี้ทำตัวจัดการหน่วยความจำแบบ Arena-based ให้สมบูรณ์ โดยอาศัยความเข้าใจเชิงลึกเกี่ยวกับกลไก GC ของ Go และการทำงานภายใน
- เป็นโครงสร้างที่ได้ทั้งความปลอดภัยและประสิทธิภาพ และหากใช้อย่างเหมาะสมจะมีประโยชน์มากกับการจัดการโครงสร้างข้อมูลขนาดใหญ่
- โค้ดฉบับเต็มประกอบด้วยโครงสร้าง Arena และ
New, Alloc, Reset, allocChunk, finalize เป็นต้น
1 ความคิดเห็น
ความคิดเห็นบน Hacker News
บทความนี้อ่านสนุกดี
Reference[T]ที่ให้ความสามารถแบบเดียวกันช่วงหลังฉันปรับจูนประสิทธิภาพใน Go และลงเอยด้วยการใช้ดีไซน์ arena ที่คล้ายกันมากเพื่อรีดประสิทธิภาพให้สุด
จุดปรับปรุงง่าย ๆ บางอย่าง
unsafe.Stringมีประโยชน์สำหรับส่งต่อสตริงจาก byte slice โดยไม่ต้องมีการจัดสรรไม่ค่อยเกี่ยวกับประเด็นหลัก แต่ฉันชอบ minimap ที่อยู่ด้านข้าง
สรุปสำหรับคนที่ไม่อยากอ่านบทความยาว
unsafe.PointerGC จะมองไม่เห็นสิ่งที่ถูกอ้างถึงจากใน arena อย่างถูกต้องและอาจปล่อยทิ้งโดยไม่ตั้งใจreflect.StructOfเพื่อสร้างชนิดใหม่ที่มีฟิลด์ pointer เพิ่มเติมไปยังบล็อกเหล่านี้ที่เกี่ยวข้อง: การถกเถียงเรื่องการเพิ่ม "memory regions" เข้าไปใน standard library
เนื้อหาน่าสนใจ
Go ให้ความสำคัญกับการไม่ทำลาย ecosystem
หมายเหตุเชิงเมตาสั้น ๆ