2 คะแนน โดย GN⁺ 4 시간 전 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • 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 ความคิดเห็น

 
GN⁺ 4 시간 전
ความคิดเห็นบน Lobste.rs
  • เข้าใจว่าเหตุผลที่ใน Zig ส่ง allocator เป็นอาร์กิวเมนต์ของฟังก์ชัน ก็เพื่อทำให้ฟังก์ชันที่ไม่ได้รับ allocator เป็นอาร์กิวเมนต์ไม่สามารถทำ การจัดสรรบนฮีป ได้ อยากรู้ว่าเข้าใจถูกไหม
    ถ้าคำว่า “ข้อตกลงสามารถถูกละเลยได้” เป็นเรื่องจริง ก็ดูเหมือนจะไม่ตรงกับเจตนา

    • ถูกแล้ว สุดท้ายก็เป็นแค่ ข้อตกลง เท่านั้น
      แม้อยู่ในฟังก์ชันก็สามารถสร้าง allocator ขึ้นมาใหม่ได้เหมือนที่ทำนอกฟังก์ชัน
  • gift link: https://theconsensus.dev/p/2026/…

    • D มี 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”, “ไม่มีการล็อก”, “จบการทำงานเสมอ”