- เมื่อเก็บ enum/tagged union ที่มี variant ขนาดต่างกันจำนวนมาก จะต้องจองพื้นที่ตาม variant ที่ใหญ่ที่สุด ทำให้ต้นทุนจาก padding และ fragmentation ใน
Vec·HashMapสูงขึ้น - Zig สามารถตรวจดูขนาดฟิลด์·alignment·discriminant ผ่าน
comptimeและการสะท้อนชนิดข้อมูล แล้วแปลงคอนเทนเนอร์ enum แบบ generic ตาม memory layout ได้ Vec<Enum>แบบธรรมดาทำให้ทุกองค์ประกอบใช้พื้นที่เท่ากับ variant ที่ใหญ่ที่สุด ส่วน SoA ช่วยลด padding ของ tag ได้ แต่ยังเหลือ variant fragmentation ในพื้นที่เก็บค่า- dense AoVA ที่จัดกลุ่ม variant ขนาดเท่ากันเข้าด้วยกัน สามารถลดจาก 15 เวกเตอร์ใน enum ตัวอย่างให้เหลือ 3 คลัสเตอร์ขนาด 2·4·8 ไบต์ แต่ถ้ามีหลาย variant ปะปนอยู่ใน allocation เดียวกัน การวนลูปแบบ type-safe จะทำได้ยาก
- Rust proc macro เข้าถึงข้อมูลขนาด·alignment ของชนิดข้อมูลได้ยาก และยังมีข้อจำกัดในการคำนวณความยาวอาร์เรย์แบบ generic จึงทำให้ staged แบบรับรู้ชนิดข้อมูลของ Zig แสดงให้เห็นการประกอบรวมด้านประสิทธิภาพหน่วยความจำของโค้ดระบบได้ชัดกว่า
อาร์เรย์ enum ของ Rust เปลืองพื้นที่อย่างไร
- enum/tagged union ที่มี variant ขนาดต่างกัน ต้องจองหน่วยความจำให้พอสำหรับเก็บ variant ที่ใหญ่ที่สุด
- enum ตัวอย่าง
Fooมี variant แบบu8,u16,u32,u64และเพราะ tag กับ alignment ทำให้ขนาดของชนิดข้อมูลนี้กลายเป็น 16 ไบต์ - เมื่อนำ enum แบบนี้ไปใส่ใน
VecหรือHashMapจำนวนมาก แต่ละองค์ประกอบจะกินพื้นที่ตาม variant ที่ใหญ่ที่สุด ทำให้ padding และ fragmentation สูงขึ้น - การแปลงเป็น struct of arrays(SoA) ที่แยก tag ไปไว้อีก allocation หนึ่ง ช่วยลด padding บางส่วนได้ แต่ไม่ได้กำจัด fragmentation ในส่วนข้อมูลค่าที่เกิดจากความต่างของขนาด variant
- แม้ใน Rust จะสร้างโครงสร้างข้อมูลเฉพาะสำหรับ enum บางตัวได้เอง แต่การสร้างโครงสร้างข้อมูลแบบ generic ที่ใช้หน่วยความจำได้คุ้มที่สุดสำหรับ enum ใดก็ได้ ยังทำได้ยากมากหรือแทบเป็นไปไม่ได้
- proc macro ติด
#[derive]ให้กับชนิดข้อมูลของบุคคลที่สามหรือtype aliasได้ยาก และมี composability ต่ำ - มันไม่มีการรับรู้ชนิดข้อมูล และวิธีอ้อมด้วย
generic_const_exprก็ทำให้whereclause ยืดยาวและแพร่ไปทั่ว call graph อีกทั้งยังไม่เข้ากับ generic type parameter มากนัก
- proc macro ติด
ทำไมปัญหานี้เด่นชัดใน AST ของคอมไพเลอร์
- หนึ่งในแรงจูงใจใหญ่ของอาร์เรย์ enum ที่มีประสิทธิภาพ คือการใช้หน่วยความจำของ AST ของคอมไพเลอร์
- AST ขนาดใหญ่ทำให้เกิด memory latency และ cache eviction ระหว่างคอมไพล์ ซึ่งมีต้นทุนสูงต่อประสิทธิภาพฝั่ง frontend
- ในวิดีโอเกี่ยวกับ Carbon compiler ของ Chandler Carruth มีการบอกว่า AST ของ clang ที่ parse แล้ว มักใช้หน่วยความจำมากกว่าซอร์สโค้ดต้นฉบับถึง 50 เท่า
- ตัวอย่างการแทนโหนด expression ใน Rust ใช้ enum
ExprUnitNumberBinary(Operation, ExprId, ExprId)Ident(Symbol)Eval(ExprId, ExprSlice)BlockExpression(ExprId, StatementSlice)
- OCaml สามารถแทนชนิดข้อมูลแบบ recursive ได้โดยไม่ต้องมี indirection แบบชัดเจน เพราะระบบรันไทม์และ GC จัดการหน่วยความจำให้
- ใน Rust,
Vec<Expr>ทำให้ทุกองค์ประกอบใช้พื้นที่เท่ากับsizeof(Enum)ซึ่งรวมทั้งขนาดของ variant ที่ใหญ่ที่สุด, tag และ padding
ลด fragmentation ด้วย SoA และ AoVA
- หาก enum แบบง่ายมี 3 variant ที่มีสมาชิกขนาด 8, 16 และ 32 บิต
Vecปกติจะต้องจองพื้นที่มากให้ทุกองค์ประกอบเพื่อรองรับ variant 32 บิตและเงื่อนไข alignment - วิธีปรับปรุงที่พบได้บ่อยคือใช้ tagged index เป็นต้น เพื่อ ทำให้ตัว enum variant เองมีขนาดเล็กลง
tagged_indexcrate ของ Rust compiler- กรณีของ small-string optimization
- เป็น optimization ที่ใช้บ่อยในโค้ดประสิทธิภาพสูง เช่น language runtime, GC, compiler, game engine และ OS kernel
- อีกทางหนึ่งคือเปลี่ยนคอนเทนเนอร์เป็นแบบ SoA ที่เก็บ discriminant และค่าไว้คนละ allocation
- self-hosted Zig compiler ใช้วิธีนี้
- มันช่วยลด padding ที่เกิดจาก tag แต่ใน collection ของค่า union ก็ยังมี variant fragmentation อยู่
- staged compilation ของ Zig ทำให้สามารถสร้างคอนเทนเนอร์แบบ generic ที่แปลงเป็น SoA สำหรับชนิดข้อมูลใดก็ได้
- ฝั่ง Rust ต้องพึ่ง proc macro อย่าง
soa_deriveและติดข้อจำกัดว่าต้องแก้ซอร์สของชนิดข้อมูลจากภายนอกไม่ได้เพื่อใส่#[derive]
อาร์เรย์แยกตาม variant และการจัดกลุ่มตามขนาด
- หากต้องการลด fragmentation ในพื้นที่เก็บค่าให้มากขึ้น สามารถมี หนึ่งเวกเตอร์ต่อหนึ่ง variant ได้
- ตอนแทรกข้อมูล จะคืนค่าเป็น tagged index ที่เก็บทั้ง tag ของ enum และ index ภายในอาร์เรย์ของ variant นั้น
- แพตเทิร์นนี้เรียกว่า array of variant arrays(AoVA)
- AoVA สามารถทำได้ด้วย proc macro ใน Rust และด้วย
comptimeใน Zig - ถ้ามี variant จำนวนมาก และหลาย variant มีขนาดเท่ากัน จำนวนเวกเตอร์แบบแยกตาม variant จะเพิ่มมากเกินไป
- enum ตัวอย่าง
Fooมี 15 variant - วิธีแยกเวกเตอร์ตาม variant ต้องเพิ่มเวกเตอร์ 15 ตัว
- อาจทำให้เกิดการ reallocation และ system call มากขึ้น และอาจต้องใช้หน่วยความจำเพื่อ amortization มากกว่า
Vecแบบ naive - เวกเตอร์อาจกระจายอยู่ในหน่วยความจำแบบสุ่ม ทำให้โอกาสเกิด cache conflict สูงขึ้น
- คอนเทนเนอร์ AoVA เองก็ใช้หน่วยความจำมาก และอาจทำให้ struct ที่บรรจุมันอยู่มีขนาดบวมขึ้น
- enum ตัวอย่าง
- หากจัดกลุ่มตามขนาด enum ตัวอย่างจะถูกแบ่งเป็น 3 คลัสเตอร์คือ 2 ไบต์, 4 ไบต์, 8 ไบต์
c_2: Vec<[u8; 2]>เก็บAถึงDc_4: Vec<[u8; 4]>เก็บEถึงIc_8: Vec<[u8; 8]>เก็บJถึงO
- วิธี dense AoVA ช่วยลดจำนวนเวกเตอร์ทั้งหมดได้ 80%
- แต่เมื่อเก็บ variant ต่างชนิดไว้ใน allocation เดียวกัน ก็จะวนลูปกับเวกเตอร์แบบ type-safe ได้ยาก
- การเข้าถึงทำได้ผ่าน tagged pointer ที่สร้างตอนแทรกข้อมูลเท่านั้น
- ถ้าเป็นโครงสร้างต้นไม้ที่อิง flattened index และไม่ต้องทำ blind iteration ก็นับเป็น trade-off ที่ยอมรับได้
- ถ้าจำเป็นต้องมีการวนลูปแบบ type-safe ก็สามารถยอมรับต้นทุน padding แล้วใส่ tag กลับเข้าไปได้
- หาก padding ใหญ่เกินไป ก็อาจใช้การแปลง SoA กับอาร์เรย์ของแต่ละ variant ได้ แต่กรณีนี้จำนวนเวกเตอร์จะเพิ่มเป็นสองเท่า
comptime ของ Zig กับ composability ของ memory layout
- โปรโตไทป์ของ Zig ถูกนำไปทำไว้ใน osmium
- หัวใจสำคัญคือ compile-time reflection ที่ใช้ built-in ของคอมไพเลอร์เพื่อตรวจดูชนิดข้อมูลฟิลด์, ขนาดเป็นไบต์, ขนาดเป็นบิต และ discriminant
- โค้ดตัวอย่างใช้
@typeInfo(inner)เพื่อตรวจชนิดข้อมูล และประมวลผลเฉพาะกรณีที่เป็น union- วนลูปผ่าน field ของ union
- คำนวณพื้นที่ที่ต้องใช้ด้วย
@max(field.alignment, @sizeOf(field.type)) - เก็บข้อมูลขนาดไว้ในเวกเตอร์ที่จัดสรรบนสแตก
- สร้าง mapping จาก union field ไปยัง cluster index
- หากไม่ใช่ union จะทำให้เกิด compile error
- ดูโค้ดส่วนที่ตรงกันได้ที่ ซอร์สนี้
- การทำตัวอย่างเดียวกันด้วย Rust proc macro นั้นแทบเป็นไปไม่ได้โดยพื้นฐาน
- proc macro เข้าถึงข้อมูล
sizeหรือalignmentของชนิดข้อมูลไม่ได้ - แม้จะสร้าง
const fnสำหรับคำนวณคลัสเตอร์ของ enum เฉพาะตัวได้ ก็ไม่สามารถนำไปใช้กำหนดความยาวอาร์เรย์ของชนิดข้อมูล generic ได้
- proc macro เข้าถึงข้อมูล
- การทำ generic container ใน Rust ก็ยากที่จะเปลี่ยนพฤติกรรมแบบมีเงื่อนไขตามว่าชนิดข้อมูลที่รับมาเป็น enum หรือ struct
- ใน Zig สามารถนึกภาพเป็นการเลือก
EfficientEnumArray<T>หรือEfficientStructArray<T>ตามT.isEnum()ได้ - การทำ AoVA ก็สามารถเลือกใช้ตามคุณสมบัติของ enum ได้เช่นกัน
- ตัวอย่างเช่น อาจทำ specialization โดยตัดสินว่าการวาง variant ต่างชนิดรวมกันจะมีความหมายก็ต่อเมื่อลดจำนวนเวกเตอร์ได้เกิน 90% เท่านั้น
- ถ้าทราบ capacity สูงสุดตั้งแต่คอมไพล์ ฟังก์ชันสร้างชนิดข้อมูลก็สามารถกำหนด bitwidth ที่จำเป็นสำหรับ tagged index ได้
- หาก tagged index นี้ถูกบรรจุอยู่ในโครงสร้างข้อมูลอื่น เช่น enum อีกชั้นหนึ่ง ก็สามารถนำบิตที่เหลือไปใช้กับ discriminant ได้
- Zig ระบุจำนวนบิตที่ต้องใช้ได้อย่างเจาะจง ทำให้ส่วนอื่นของโค้ดนำข้อมูลนี้ไปใช้ต่อได้อย่างเป็นธรรมชาติ เกิดเป็น ประสิทธิภาพด้านหน่วยความจำแบบ composable
- ด้วย implicit widening integer coercion การใช้งาน API ที่มี bitwidth ต่างกันก็ยังสะดวก
- หากเป็นภาษาสำหรับ system programming ที่ให้ความสำคัญกับประสิทธิภาพและ zero-cost abstraction ก็ควรกลับมาพิจารณา staged programming โดยเฉพาะ
comptimeของ Zig อีกครั้ง
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
ยังมีกลยุทธ์อื่นที่มีประสิทธิภาพด้านการจัดเก็บดีและยังคงการวนผ่านองค์ประกอบไว้ได้ เวกเตอร์แรกเป็นรายการแท็ก เวกเตอร์ที่สองเป็น byte offset ของแต่ละองค์ประกอบ ส่วนตัวที่สามไม่เชิงเป็นเวกเตอร์ แต่เป็น ข้อมูล variant ที่ถูกแพ็ก ซึ่งเวกเตอร์ที่สองชี้ไปหา
วิธีนี้ทำให้จำนวนเวกเตอร์เหลือครึ่งหนึ่งเมื่อเทียบกับทางออกสุดท้ายของผู้เขียน (6 ตัว vs 3 ตัว) ไม่เปลืองไบต์ padding เว้นแต่ในกรณีที่จำเป็นเพราะการจัดแนว และข้อมูลจะวางเรียงตามลำดับในหน่วยความจำโดยไม่ขึ้นกับชนิด ทำให้วนผ่านได้แบบเป็นมิตรต่อแคช นอกจากนี้ยังเข้าถึงองค์ประกอบด้วยดัชนีได้แบบ O(1) โดยรวมแล้วมีลักษณะด้านประสิทธิภาพคล้าย
Vecสำหรับข้อมูลต่างชนิดกันTขนาดเล็ก เช่น กรณีใช้size_t64 บิตคู่กับuint8_t Tและถ้าระวังแค่เรื่อง ขนาด offset ก็ดูเป็นแนวทางที่สมเหตุสมผลสงสัยว่าโครงสร้างข้อมูล AoVA นี้ทำงานจริง ๆ อย่างไร จากมุมมองของอาร์เรย์ การคำนวณเลขคณิตของดัชนีอาจไม่มีความหมายอีกต่อไป แบบนี้ไม่ได้สูญเสีย การเข้าถึงแบบอิงดัชนี ไปหรือ? การวนผ่านก็ดูเหมือนไม่น่าจะรักษาลำดับการแทรกไว้ด้วย
ในบริบทนี้ ผมคิดว่า TLV(tag-length-value) ซึ่งมีคุณสมบัติด้านแคชดีกว่า ถูกใช้กันบ่อยกว่า ความยาวอาจอนุมานได้จากแท็ก หรืออย่างน้อยก็ให้การวนไปข้างหน้าที่มีความหมาย ดู
getdents,inotify, การส่งข้อความของ Netlink ได้เมื่อเทียบกับเลย์เอาต์ SoA ก่อนหน้า มันจะเกิดลำดับบางส่วน ไม่ใช่ลำดับรวม ตอนแทรกจะได้ดัชนีติดแท็กที่มีทั้ง enum tag และดัชนีภายในอาร์เรย์ของ variant นั้นกลับมา ดังนั้นการเข้าถึงแบบเรียงลำดับดูเหมือนจะอยู่นอกขอบเขตตรงนี้ ถ้าเก็บดัชนี global ไว้ในแต่ละองค์ประกอบ ก็อาจกู้การวนผ่านแบบเรียงลำดับกลับมาได้ แต่ก็ยังไม่ช่วยเรื่องการเข้าถึงแบบสุ่มที่เรียงลำดับอยู่ดี และโค้ดก็น่าจะมี branch ค่อนข้างมาก
วิธีจัดเก็บของตามขนาดยังถูกใช้ใน garbage collector และ allocator อเนกประสงค์ด้วย การที่รู้ขนาด object ที่เป็นไปได้ทั้งหมดอาจทำให้ได้ประสิทธิภาพ และยังอาจได้ประสิทธิภาพจากวิธีคืนหน่วยความจำที่เรียบง่ายกว่าแบบ arena ด้วย
ในกรณีแบบนี้ อาร์เรย์เหล่านี้อาจมองได้ว่าเป็นองค์ประกอบหนึ่งของโครงสร้างคล้าย heap หรือก็คือเหมือน arena ต้นทุนคือดัชนีต้องเป็น 2 มิติ เช่น
(tag_idx, va_for_tag_idx)แต่จำนวนแท็กเป็นที่รู้ตอน compile time จึงสามารถ optimize การจัดเก็บได้โดยแพ็กtag_idxไว้ใน 4~5 บิตบน และให้va_for_tag_idxใช้บิตที่เหลือ อ้างอิง: https://www.cs.cornell.edu/~asampson/blog/flattening.htmlค่อนข้างเสียดายที่ pattern matching ของ Rust ไม่สามารถแสดงออกได้มากกว่านี้ในรูปแบบ trait ของ type system ที่ struct ใด ๆ ก็ทำตามได้ แทนที่จะเป็นชนิด object แบบ hard-code ระดับ first-class ที่ชัดเจนและมีโครงสร้างจัดเก็บของตัวเอง
ช่วงหลังผมได้ implement AST คล้ายบทความนี้ และยังทำ interpreter สำหรับ opcode/bytecode ด้วย แต่รู้สึกว่า enum ของ Rust ไม่ได้เหมาะสมอย่างสมบูรณ์กับทั้งสองกรณี สำหรับ AST ผมอยากใส่แอตทริบิวต์เลขบรรทัด/คอลัมน์ให้กับ statement node ทุกตัว แต่ถ้าใส่บรรทัด/คอลัมน์ในทุกกรณีของ enum
Stmtboilerplate ก็จะรก และถ้าห่อ enum ด้วย structStmtใหม่ที่เก็บ enum เดิมพร้อมแอตทริบิวต์บรรทัด/คอลัมน์ ก็ต้อง refactor เยอะและไม่ค่อยสง่างาม ฝั่ง opcode เอง enum ของ Rust ที่ใช้ pattern matching ได้ก็ไม่ใช่ encoding ที่เหมาะที่สุดในแง่ประสิทธิภาพของ VM opcode interpreter แต่ภาษากลับชี้นำไปทางนี้ และความสามารถของรูปแบบ destructuring ก็น่าดึงดูดมาก ดูเหมือนยังมีพื้นที่ให้ปรับปรุง type system เพื่อให้ใช้ implementation ระดับต่ำตามที่ต้องการได้ พร้อมยังได้ความสามารถ pattern matchinghttps://en.wikipedia.org/wiki/Structural_type_system
computed gotoสำหรับเรื่องนี้ และถ้าเป็น Rust ก็น่าจะต้องมีอะไรสักอย่างที่บังคับใช้ function pointer และ tail-call optimization ได้ถ้าวาง indirect jump แบบนี้ไว้ตอนต้นของ implementation ของแต่ละ opcode ตัวทำนาย indirect jump ที่ CPU มีเพราะ OOP จะมีโมเดลแยกกันสำหรับส่วนท้ายของ opcode ต่าง ๆ ทำให้อัตราทำนายถูกสูงขึ้น ถึงแม้คำสั่งถัดไปเองจะทำนายยาก แต่เช่น หลัง test มีโอกาสตามด้วย branch สูงกว่ามาก อย่างไรก็ดี เทคนิคอื่น ๆ เช่นการเก็บยอดบนสุดของ stack ไว้ใน register ใน stack machine น่าจะสำคัญกว่า และก็ไม่แน่ใจว่าเทคนิคข้างต้นยังมีความหมายอยู่ในปัจจุบันหรือไม่
ประโยคที่ว่า “clang AST ที่ parse แล้วกินหน่วยความจำเป็นประจำมากกว่าซอร์สโค้ดต้นฉบับ 50 เท่า” ดูเป็นเรื่องใหญ่ทีเดียว แต่บริบทที่ขาดไปคือ มันจะดีขึ้นได้มากแค่ไหน ถ้าต้องเก็บตำแหน่งในซอร์สของแต่ละโทเคน และต้องเข้ารหัสข้อมูลให้เพียงพอจนกู้คืนจาก AST ได้อย่างถูกต้อง อยากรู้ว่าอัตราการเพิ่มขึ้นในอุดมคติเมื่อเทียบกับต้นฉบับคือ 1.5 เท่า หรือ 15 เท่ากันแน่
เป็นเรื่องยากที่จะบอกว่าอัตราการขยายจากซอร์ส→AST ในอุดมคติสำหรับภาษาที่เป็นมิตรทั้งกับผู้ใช้และนักพัฒนาคอมไพเลอร์ควรเป็นเท่าไร แต่ 50 เท่าก็ยังใช้งานได้อยู่ ในบทความต้นฉบับใช้การขยาย 50 เท่าเป็นแรงจูงใจในการทำให้การปรับแต่งเฉพาะอย่างหนึ่งเป็นอัตโนมัติ ถ้าเวกเตอร์ enum ของ Rust สามารถแยกค่า enum ออกเป็นแท็กกับค่าทึบแสงโดยอัตโนมัติ แล้วเก็บในรูปแบบ struct of arrays เหมือนที่บทความต้นฉบับทำใน Zig ได้ ก็น่าจะน่าสนใจ และดูเหมือนจะไม่มีที่ให้ซ่อนการใช้
unsafeมากนักด้วยในเอกสารที่ส่วนใหญ่เป็นอักขระ
[]หรืออักขระ0,ดูเหมือน overhead สูงสุดจะอยู่ราว 8 เท่าไบต์ของซอร์ส: 139 KiB, โทเคน: 24646 รายการ (120 KiB), โหนด AST: 10998 รายการ (140 KiB) โทเคนแต่ละตัวถูกทำให้เล็กมากที่ 5 ไบต์ (แท็ก 1 ไบต์ + file offset 4 ไบต์) และโหนด AST ก็ถูกเข้ารหัสอย่างหนาแน่นและไม่สม่ำเสมอ โดยในกรณีนี้อยู่ที่ประมาณ 13 ไบต์ต่อโหนด แม้จะเป็นการเข้ารหัสขั้นต่ำแบบนี้ parse tree ก็ยังมีขนาดเกือบ 2 เท่าของไฟล์ซอร์ส แต่ถึงอย่างนั้น 2 เท่าก็ดีกว่า 50 เท่ามาก ที่มา:
zig ast-check -t lib/std/zig/Parse.zig | head -n7พื้นที่ปัญหานี้ให้ความรู้สึกเหมือนเป็นรูปแบบหนึ่งของ packing problem
คงจะดีถ้าสามารถเริ่มจากโครงสร้างสุดท้ายที่มนุษย์จัดการได้ง่าย แล้วสร้างคำแนะนำโครงสร้างข้อมูลที่ลดการสิ้นเปลืองหน่วยความจำ เคารพกฎการจัดแนว และเพิ่ม locality ของพื้นที่ได้ https://en.wikipedia.org/wiki/Packing_problems
อยากให้ proc macro พัฒนาไปจนสอบถามข้อมูลจากคอมไพเลอร์ได้ ถึงจะต้องออกแบบอย่างระมัดระวังเกี่ยวกับการเพิ่มขั้นตอนคอมไพล์ก็ตาม แต่สิ่งอย่าง “struct นี้ implement trait นี้หรือไม่”, “ขอรายการ trait ทั้งหมดที่ implement แบบเป็นรูปธรรม” มักมีประโยชน์มากใน proc macro
ผมเข้าใจบทความแค่บางส่วน แต่ในฐานะคนที่กำลังจะเขียน spreadsheet engine ด้วย Rust ดูเหมือนจะเกี่ยวข้องมาก ค่าในเซลล์ต้องมีรูปแบบแบบนี้
pub enum Expr { Number(i32), String(String), Reference(Position), Binary(Box, char, Box), Function(String, Vec), }ตั้งใจว่าจะอ่านและศึกษาไปเรื่อย ๆ และยินดีรับเอกสารอ้างอิงถ้ามี
เทคนิคทั่วไปจากฝั่งเกมคือแยก array of structs (AoS) ออกเป็น struct of arrays (SoA) เช่นถ้าเขียนเป็น
struct Humans { healths: Vec, ammo: Vec, … }ดัชนีที่ i ของแต่ละเวกเตอร์ก็จะเป็นHumanตัวที่ i ใน layout แบบ AoS เวกเตอร์ขนานแบบนี้เป็นเพียงตัวอย่าง ไม่ใช่ประสิทธิภาพที่ดีที่สุด เพราะเสียพื้นที่จากการทำบัญชีความยาวและความจุซ้ำในแต่ละฟิลด์ โดยพื้นฐานแล้วบทความนี้พยายามนำแนวคิดคล้ายกันมาใช้กับ enum แบบอัตโนมัติ และใน Rust ทำตรง ๆ ได้ยาก ปัญหานี้ใหญ่จริงแค่ไหนอาจถูกพูดเกินไปบ้าง สำหรับ spreadsheet ควรเก็บไว้ในใจเป็นการปรับแต่งที่อาจทำได้ก่อน และต้องตัดสินใจก่อนว่ากำลังทำเพื่อความเร็ว หรือเพื่อความเรียบง่ายและความเข้าใจง่ายถ้าอนุญาต 1 ล้าน × 1 ล้านเซลล์ แล้วเก็บ
nullในทุกเซลล์ที่ยังไม่ถูกเติม หน่วยความจำจะหมด ดังนั้นอาจพิจารณาวิธีเก็บเนื้อหาเซลล์แบบ sparse วิธีหนึ่งคือใช้ implementation ของ hash map อย่างhashbrownประเด็นของบทความนี้เป็นรายละเอียดระดับต่ำ ถ้าเริ่มจาก hash map ตั้งแต่แรกเพื่อหลีกเลี่ยงข้อจำกัดหน่วยความจำช่วงต้น ก็ยังไม่จำเป็นต้องคิดลึกเรื่องนี้ในตอนนี้ปัญหาเดี่ยวที่ยากที่สุดคือ กลยุทธ์การประเมินผล
https://doc.rust-lang.org/reference/type-layout.html#the-alignment-modifiers เป็นอย่างไรบ้าง
ดูเหมือนว่าโค้ดตัวอย่างจะมีบั๊ก
field_map[idx] = svec.len - 1;ถ้า
svecมีsizeอยู่ในตำแหน่งที่ไม่ใช่ entry สุดท้ายอยู่แล้ว ก็น่าจะผิด