ภาษาอื่นอีกมากควรเรียนรู้การตรวจสอบฟังก์ชันแบบไม่จัดสรรหน่วยความจำของ OxCaml
(theconsensus.dev)- OxCaml ซึ่งเป็น superset ของ OCaml จาก Jane Street ทำให้สามารถประกาศกับคอมไพเลอร์ได้ด้วย
[@zero_alloc]ว่าไม่อนุญาตให้มีการจัดสรรบนฮีปตลอดทั้งต้นไม้การเรียกฟังก์ชัน - หากเกิดการจัดสรรขึ้นในเส้นทางการเรียกที่ประกาศไว้ จะปรากฏเป็น คอมไพล์ไม่ผ่าน ทันที ทำให้ป้องกัน regression ได้เร็วกว่าแนวทางที่ไปหาเจอภายหลังด้วย profiler
- ใน C, C++, Java, Go, C#, Rust, Zig, OCaml ฯลฯ โดยทั่วไปมักเน้นแนวทางใช้ profiler เพื่อค้นหาและลดการจัดสรรใน hot path
- โค้ดใน hot path สามารถกลับมามีการจัดสรรได้อีกจากการแก้เพียงเล็กน้อย และหากลืมบริบทของการปรับแต่งเดิม ก็ต้องกลับไปสืบหาสาเหตุแบบเดิมซ้ำอีก
- ธรรมเนียมการส่ง allocator ใน Zig หรือ Rust รุ่นใหม่บางส่วนก็ช่วยได้ แต่ การตรวจสอบโดยคอมไพเลอร์ เป็นกลไกป้องกันที่ตรงไปตรงมายิ่งกว่า
สิ่งที่ [@zero_alloc] เปลี่ยนไปในการจัดการการจัดสรร
- OxCaml เป็น superset ของ OCaml จาก Jane Street และรองรับการยืนยันว่าฟังก์ชันจะไม่จัดสรรบนฮีป
- เมื่อติด
[@zero_alloc]ให้กับฟังก์ชัน คอมไพเลอร์จะตรวจสอบการจัดสรรบนฮีปไม่เฉพาะในฟังก์ชันนั้น แต่รวมถึง ต้นไม้การเรียกฟังก์ชัน ใต้ฟังก์ชันนั้นด้วย - หากเกิดการจัดสรรภายในต้นไม้การเรียก การบิลด์จะล้มเหลว และคอมไพเลอร์จะแจ้งว่ามีการจัดสรรเกิดขึ้น
- แม้อาจสร้างการตรวจสอบลักษณะคล้ายกันได้ด้วย static analysis แต่ในบรรดา ภาษา mainstream ยังแทบไม่มีภาษาที่ใส่ความสามารถแบบนี้ไว้ในคอมไพเลอร์โดยตรงบนฐานของสรุปที่สร้างขึ้น
ความต่างจากแนวทางที่ยึด profiler เป็นศูนย์กลาง
- ในภาษาอื่น ๆ โดยทั่วไปมักใช้ profiler เพื่อค้นหาการจัดสรรก่อน แล้วจึงลบหรือลดการจัดสรร โดยเฉพาะการจัดสรรภายใน ลูป ที่รันนับล้านครั้ง
- C, C++, Java, Go, C#, Rust, Zig, OCaml ถูกยกเป็นตัวอย่างของแนวทางที่ยึด profiler เป็นหลัก
- แค่แก้โค้ดใน hot path เพียงหนึ่งบรรทัด ก็อาจทำให้มีการจัดสรรกลับเข้ามาอีก และต้องย้อนกลับไปใช้ profiler เพื่อหาสาเหตุ
- ใน Zig หรือ Rust รุ่นใหม่บางส่วน สามารถลด regression ได้ด้วยวิธีไม่ส่ง allocator เข้าไปในฟังก์ชัน แต่แนวทางนี้ยังอาศัย ธรรมเนียมปฏิบัติ
- ความต่างของ
[@zero_alloc]คือไม่ได้พึ่งการวิเคราะห์ย้อนหลังหรือกฎของทีม แต่ให้คอมไพเลอร์บล็อก allocation regression ตั้งแต่ขั้นตอนบิลด์
1 ความคิดเห็น
ความคิดเห็นบน Lobste.rs
เข้าใจว่าเหตุผลที่ใน Zig ส่ง allocator เป็นอาร์กิวเมนต์ของฟังก์ชัน ก็เพื่อทำให้ฟังก์ชันที่ไม่ได้รับ allocator เป็นอาร์กิวเมนต์ไม่สามารถทำ การจัดสรรบนฮีป ได้ อยากรู้ว่าเข้าใจถูกไหม
ถ้าคำว่า “ข้อตกลงสามารถถูกละเลยได้” เป็นเรื่องจริง ก็ดูเหมือนจะไม่ตรงกับเจตนา
แม้อยู่ในฟังก์ชันก็สามารถสร้าง allocator ขึ้นมาใหม่ได้เหมือนที่ทำนอกฟังก์ชัน
gift link: https://theconsensus.dev/p/2026/…
nogcมานานพอสมควรแล้ว และมองว่ามี semantics คล้ายกันในแง่ที่ GC เป็นผู้รับผิดชอบการจัดสรรhttps://dlang.org/phobos/dmd_nogc.html
ช่วงหนึ่งเคยเพิ่ม ข้อยกเว้นแบบ nogc ในเชิงทดลองด้วย แต่ไม่ได้ตรวจสอบสถานะหรือการใช้งานปัจจุบัน
https://dlang.org/changelog/2.079.0.html#dip1008
ใน Rust การใช้เฉพาะ core ก็เป็นอีกวิธีหนึ่งในการหลีกเลี่ยงการจัดสรร
แนวทางนั้นสุดท้ายแล้วใกล้เคียงกับ “ควบคุม dependency” หรือ “ตรวจสอบ call graph ทั้งหมดด้วยมือ” มากกว่า
จุดเน้นของบทความอยู่ที่วิธีที่ เครื่องมือ อย่างคอมไพเลอร์บังคับใช้คุณสมบัติบางอย่างแบบสแตติก และให้ เวิร์กโฟลว์ ที่ช่วยค้นหาจุดที่คุณสมบัตินั้นถูกทำลายได้อย่างมีประสิทธิผล
D มี
@nogcและจากนั้นก็เป็นเรื่องของการใช้เฉพาะ abstraction ที่ควบคุมได้โดยตรงและมี รูปแบบการจัดสรร ที่ชัดเจนเท่านั้นขอเสริม เพราะเหมือนเราจะสูญเสียความสามารถในการตั้งชื่อบทความให้สื่อความหมายไปแล้ว แก่นของเรื่องคือฟีเจอร์ที่ติด
[@zero_alloc]ให้ฟังก์ชัน แล้วถ้า ต้นไม้การเรียกใช้ ของฟังก์ชันนั้นไปแตะฮีป คอมไพเลอร์ก็จะปฏิเสธโปรแกรมสงสัยว่าวิธีแบบนี้จะนำไปใช้กับเงื่อนไขหลายอย่างได้ไหม เช่น “ไม่โยน exception หรือ panic”, “ไม่มีการล็อก”, “จบการทำงานเสมอ”