นักพัฒนาสร้าง Memory Allocator แบบง่ายใน 163 บรรทัด จุดประกายการถกเถียงเรื่องความซับซ้อนของ Allocator

ทีมชุมชน BigGo
นักพัฒนาสร้าง Memory Allocator แบบง่ายใน 163 บรรทัด จุดประกายการถกเถียงเรื่องความซับซ้อนของ Allocator

การที่นักพัฒนาคนหนึ่งได้สร้าง memory allocator พื้นฐานด้วยโค้ดเพียง 163 บรรทัดเมื่อเร็วๆ นี้ ได้จุดประกายการถกเถียงอย่างเข้มข้นในชุมชนโปรแกรมเมอร์เกี่ยวกับว่าการเขียน allocator นั้นง่ายจริงหรือไม่ตามที่ดูเผินๆ โปรเจกต์นี้เกิดขึ้นจากความต้องการในทางปฏิบัติที่จะเพิ่มการรองรับ pre-allocated memory ให้กับ mimalloc allocator ของ Microsoft แต่ได้พัฒนาไปสู่การสนทนาที่กว้างขึ้นเกี่ยวกับความซับซ้อนของการจัดการหน่วยความจำ

การเปรียบเทียบความซับซ้อนในการพัฒนา

  • Basic allocator: ~100 บรรทัด (assembly), ~163 บรรทัด (C)
  • Thread-safe allocator: ~200 บรรทัด (ตัวอย่าง Zig)
  • Production allocators: หลายพันบรรทัด
  • mimalloc: ไม่กี่พันบรรทัด (ส่วนใหญ่เป็นความซับซ้อนของ threading)

ข้อโต้แย้งเรื่องความเรียบง่ายได้รับการสนับสนุน

การใช้งานเดิมแสดงให้เห็นว่าการจัดสรร memory พื้นฐานนั้นสามารถทำได้อย่างตรงไปตรงมาจริงๆ ด้วยการใช้วิธี buddy system - ที่ memory chunks ถูกแบ่งออกเป็นบล็อกขนาด power-of-two - นักพัฒนาได้แสดงให้เห็นว่าการดำเนินการ allocation และ deallocation พื้นฐานทำงานอย่างไร เทคนิคนี้ใช้กันอย่างแพร่หลายในระบบต่างๆ เช่น Linux kernel สำหรับ page allocation โดยแบ่ง memory แบบ recursive จนกว่าจะได้ขนาดที่ต้องการ จากนั้นจึงรวมบล็อกที่ถูกปลดปล่อยกลับเข้าด้วยกันเมื่อเป็นไปได้

สมาชิกหลายคนในชุมชนสะท้อนความเห็นนี้ โดยสังเกตว่าการเขียน allocator พื้นฐานมักถูกมอบหมายเป็นแบบฝึกหัดการเขียนโปรแกรมในระดับต้น การดำเนินการพื้นฐานของการแบ่ง memory blocks และการติดตาม free space สามารถใช้งานได้ในโค้ดไม่ถึง 100 บรรทัด แม้แต่ใน assembly language

ตัวอย่างกระบวนการจัดสรรแบบ Buddy System

  • หน่วยความจำเริ่มต้น: บล็อก 256KB
  • คำขอ: จัดสรร 16KB
  • ขั้นตอนที่ 1: แบ่ง 256KB → เป็นสองส่วน 128KB
  • ขั้นตอนที่ 2: แบ่ง 128KB แรก → เป็นสองส่วน 64KB
  • ขั้นตอนที่ 3: แบ่ง 64KB แรก → เป็นสองส่วน 32KB
  • ขั้นตอนที่ 4: แบ่ง 32KB แรก → เป็นสองส่วน 16KB
  • ขั้นตอนที่ 5: ส่งคืนส่วน 16KB แรกให้กับผู้ใช้

ความซับซ้อนในโลกจริงปรากฏในการอภิปราย

อย่างไรก็ตาม ชุมชนได้เน้นย้ำอย่างรวดเร็วถึงช่องว่างระหว่างการใช้งานแบบของเล่นกับระบบที่พร้อมสำหรับการใช้งานจริง นักวิจัยระดับปริญญาเอกในสาขานี้เน้นย้ำว่าความท้าทายที่แท้จริงไม่ได้อยู่ที่การใช้งานพื้นฐาน แต่อยู่ที่การพัฒนากลยุทธ์อัจฉริยะที่ปรับตัวให้เข้ากับพฤติกรรมของโปรแกรมและลด memory fragmentation ให้น้อยที่สุด ปัญหาทางคณิตศาสตร์พื้นฐาน - การจัดเรียงวัตถุอย่างเหมาะสมที่มีขนาดและอายุการใช้งานที่ทราบแล้ว - เป็น NP-complete จริงๆ หมายความว่าไม่มีวิธีแก้ปัญหาที่สมบูรณ์แบบสำหรับกรณีทั่วไป

เหตุผลที่เป็นเช่นนี้เป็นเรื่องปฏิบัติ: กลยุทธ์ที่ซับซ้อนต้องใช้เวลาในการคำนวณเพิ่มเติมที่แพงเกินไปเมื่อเราอยู่ใน critical path

Thread safety, การพิจารณาด้านความปลอดภัย และการปรับปรุงประสิทธิภาพเพิ่มชั้นของความซับซ้อนที่การใช้งานแบบง่ายไม่ได้จัดการ Allocator สมัยใหม่ต้องจัดการกับการเข้าถึงแบบ concurrent, ป้องกันการใช้ประโยชน์ในทางที่ผิด และปรับปรุงสำหรับรูปแบบการใช้งานต่างๆ พร้อมกัน

Specialized Allocators แสดงให้เห็นคุณค่าของตนเอง

การอภิปรายเผยให้เห็นการสนับสนุนอย่างแข็งแกร่งสำหรับ domain-specific memory allocators ในอุตสาหกรรมต่างๆ นักพัฒนาเกมที่ทำงานกับ Burst compiler ของ Unity พึ่งพา specialized allocators อย่าง 'Temp' และ 'Persistent' อย่างมากเพื่อหลีกเลี่ยง garbage collection overhead ที่อาจทำให้เกิด frame drops นักพัฒนา embedded systems ที่ทำงานกับ FreeRTOS เลือกระหว่าง heap implementations ต่างๆ ขึ้นอยู่กับว่าพวกเขาให้ความสำคัญกับความเร็วหรือประสิทธิภาพของหน่วยความจำ

ระบบโครงสร้างพื้นฐานขนาดใหญ่ได้นำ arena allocators มาใช้มาหลายปีแล้ว ด้วยการใช้งานใน Folly library ของ Facebook, gRPC และ thread-local caching ใน jemalloc สิ่งเหล่านี้ไม่ใช่การปรับปรุงก่อนกำหนด แต่เป็นเครื่องมือที่จำเป็นสำหรับการจัดการแรงกดดัน allocation ที่ general-purpose allocators ไม่สามารถจัดการได้อย่างมีประสิทธิภาพ

ประเภทของ Memory Allocator และกรณีการใช้งาน

  • วัตถุประสงค์ทั่วไป: malloc/free, mimalloc, jemalloc
  • การพัฒนาเกม: Unity Burst (Temp, Persistent allocators)
  • ระบบฝังตัว: การใช้งาน heap ของ FreeRTOS, fixed-size pools
  • โครงสร้างพื้นฐาน: Arena allocators ( Folly , gRPC ), thread-local caching
  • การวิจัย: Bumpalo ของ Rust สำหรับการจัดสรรที่ตระหนักถึง lifetime

คุณค่าทางการศึกษายังคงชัดเจน

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

การสนทนายังได้สัมผัสกับความท้าทายเฉพาะภาษา โดยการทดลอง arena ที่ล้มเหลวของ Go ทำหน้าที่เป็นตัวอย่างของความยากในการรวมรูปแบบ specialized allocation เข้ากับภาษาระดับสูงที่ออกแบบมาเพื่อ garbage collection

บทสรุป

ในขณะที่การใช้งาน 163 บรรทัดเริ่มต้นแสดงให้เห็นสำเร็จว่าการจัดสรร memory พื้นฐานไม่ใช่วิทยาศาสตร์จรวด การอภิปรายของชุมชนเผยให้เห็นความเป็นจริงที่ละเอียดอ่อนของการจัดการหน่วยความจำในการผลิต Allocator แบบง่ายทำหน้าที่เป็นเครื่องมือการเรียนรู้ที่ยอดเยี่ยมและสามารถแก้ปัญหาเฉพาะได้อย่างมีประสิทธิภาพ อย่างไรก็ตาม เส้นทางจากแบบฝึกหัดทางการศึกษาไปสู่ระบบที่พร้อมสำหรับการผลิตเกี่ยวข้องกับการจัดการ threading, ความปลอดภัย, การปรับปรุงประสิทธิภาพ และการปรับตัวให้เข้ากับรูปแบบการใช้งานในโลกจริง - ความท้าทายที่เปลี่ยนแนวคิดที่ตรงไปตรงมาให้กลายเป็นปัญหาทางวิศวกรรมที่ซับซ้อน

อ้างอิง: Allocators are Monkeys With Typewriters