ฉันหมุนส่วนกลางของ codebase ของฉัน (เอ็นจิน ECS) รอบ ๆ ประเภทของโครงสร้างข้อมูลที่คุณอธิบายถึงแม้ว่ามันจะใช้บล็อกต่อเนื่องขนาดเล็ก (เช่น 4 กิโลไบต์แทนที่จะเป็น 4 เมกะไบต์)
ใช้รายการฟรีสองครั้งเพื่อให้ได้การแทรกและลบเวลาคงที่ด้วยรายการฟรีหนึ่งรายการสำหรับบล็อกฟรีที่พร้อมที่จะแทรก (บล็อกที่ไม่เต็ม) และรายการย่อยฟรีภายในบล็อกสำหรับดัชนีในบล็อกนั้น พร้อมที่จะถูกเรียกคืนเมื่อมีการแทรก
ฉันจะครอบคลุมข้อดีข้อเสียของโครงสร้างนี้ เริ่มจากข้อเสียกันก่อนเพราะมีหลายข้อ:
จุดด้อย
- การแทรกองค์ประกอบสองสามร้อยล้านครั้งเข้ากับโครงสร้างนี้ใช้เวลานานกว่าประมาณ 4 เท่ากว่า
std::vector
(โครงสร้างที่ต่อเนื่องกันหมดจด) และฉันก็ค่อนข้างดีที่การเพิ่มประสิทธิภาพแบบไมโคร แต่มีแนวคิดที่จะต้องทำมากกว่าเพราะกรณีทั่วไปต้องตรวจสอบบล็อกฟรีที่ด้านบนสุดของรายการบล็อกฟรีก่อนจากนั้นเข้าถึงบล็อกและป๊อปดัชนีฟรีจากบล็อก รายการอิสระเขียนองค์ประกอบที่ตำแหน่งว่างจากนั้นตรวจสอบว่าบล็อกเต็มหรือไม่และแสดงป๊อปอัพจากรายการว่างของบล็อกถ้าใช่ ก็ยังคงดำเนินการอย่างต่อเนื่องเวลา std::vector
แต่มีความคงที่มากใหญ่กว่าการผลักดันกลับไป
- ใช้เวลาประมาณสองครั้งในการเข้าถึงองค์ประกอบโดยใช้รูปแบบการเข้าถึงแบบสุ่มที่ให้เลขคณิตพิเศษสำหรับการทำดัชนีและเลเยอร์ทางอ้อมเพิ่มเติม
- การเข้าถึงแบบลำดับไม่ได้แมปอย่างมีประสิทธิภาพกับการออกแบบตัววนซ้ำเนื่องจากตัววนซ้ำต้องทำการแยกสาขาเพิ่มเติมในแต่ละครั้งที่มีการเพิ่มขึ้น
- มีค่าใช้จ่ายหน่วยความจำเล็กน้อยโดยทั่วไปประมาณ 1 บิตต่อองค์ประกอบ 1 บิตต่อองค์ประกอบอาจไม่ฟังดูมากนัก แต่ถ้าคุณใช้สิ่งนี้เพื่อเก็บจำนวนเต็ม 16- ล้านล้านนั่นหมายความว่าการใช้หน่วยความจำเพิ่มขึ้น 6.25% มากกว่าอาร์เรย์ขนาดกะทัดรัด อย่างไรก็ตามในทางปฏิบัติสิ่งนี้มีแนวโน้มที่จะใช้หน่วยความจำน้อยกว่า
std::vector
นอกจากว่าคุณกำลังทำการกระชับข้อมูลvector
เพื่อกำจัดความจุที่เหลือที่สำรองไว้ นอกจากนี้ฉันมักจะไม่ใช้มันเพื่อจัดเก็บองค์ประกอบเล็ก ๆ
ข้อดี
- การเข้าถึงแบบลำดับโดยใช้
for_each
ฟังก์ชั่นที่ใช้ช่วงการประมวลผลการเรียกกลับขององค์ประกอบภายในบล็อกเกือบจะเทียบเคียงกับความเร็วของการเข้าถึงตามลำดับด้วยstd::vector
(เช่น 10% diff เท่านั้น) ดังนั้นจึงไม่ค่อยมีประสิทธิภาพในกรณีการใช้งานที่สำคัญที่สุดสำหรับฉัน ( เวลาส่วนใหญ่ที่ใช้ในเอ็นจิน ECS อยู่ในการเข้าถึงแบบลำดับ)
- จะช่วยให้การลบเวลาคงที่จากตรงกลางด้วยโครงสร้าง deallocating บล็อกเมื่อพวกเขากลายเป็นที่ว่างเปล่าอย่างสมบูรณ์ ผลก็คือโดยทั่วไปค่อนข้างดีที่ทำให้แน่ใจว่าโครงสร้างข้อมูลไม่เคยใช้หน่วยความจำมากกว่าที่จำเป็น
- มันไม่ได้ทำให้ดัชนีขององค์ประกอบที่ไม่ได้ลบออกจากภาชนะโดยตรงเนื่องจากเป็นเพียงแค่ปล่อยให้รูอยู่ข้างหลังโดยใช้วิธีการแบบอิสระเพื่อเรียกคืนรูเหล่านั้นเมื่อทำการแทรกครั้งต่อไป
- คุณไม่ต้องกังวลมากเกี่ยวกับหน่วยความจำไม่เพียงพอแม้ว่าโครงสร้างนี้จะมีองค์ประกอบจำนวนมากเพราะมันเพียงแค่ร้องขอบล็อกเล็ก ๆ ที่ต่อเนื่องกันซึ่งไม่สร้างความท้าทายให้กับระบบปฏิบัติการเพื่อค้นหาสิ่งที่ไม่ได้ใช้งานจำนวนมากที่ต่อเนื่องกัน หน้า
- มันให้ยืมตัวเองได้ดีกับการทำงานพร้อมกันและความปลอดภัยของเธรดโดยไม่ต้องล็อคโครงสร้างทั้งหมดเนื่องจากการดำเนินการโดยทั่วไปจะทำการแปลแต่ละบล็อก
ตอนนี้หนึ่งในข้อดีที่ใหญ่ที่สุดสำหรับฉันก็คือมันกลายเป็นเรื่องไม่สำคัญที่จะทำให้โครงสร้างข้อมูลนี้เปลี่ยนแปลงไม่ได้แบบนี้:
นับตั้งแต่นั้นเป็นต้นมาก็เปิดประตูทุกประเภทเพื่อเขียนฟังก์ชั่นเพิ่มเติมโดยไม่มีผลข้างเคียงซึ่งทำให้ง่ายขึ้นมากในการบรรลุข้อยกเว้นความปลอดภัยความปลอดภัยของด้าย ฯลฯ ความไม่เปลี่ยนรูปนั้นเป็นสิ่งที่ฉันค้นพบว่าฉันสามารถทำได้ด้วย โครงสร้างข้อมูลในแบบย้อนหลังและโดยไม่ได้ตั้งใจ แต่หนึ่งในประโยชน์ที่ดีที่สุดที่มันมีเพราะมันทำให้การบำรุงรักษา codebase ง่ายขึ้นมาก
อาร์เรย์ที่ไม่ต่อเนื่องกันไม่มีตำแหน่งแคชซึ่งส่งผลให้ประสิทธิภาพไม่ดี อย่างไรก็ตามขนาดบล็อก 4M ดูเหมือนว่าจะมีพื้นที่เพียงพอสำหรับการแคชที่ดี
สถานที่อ้างอิงไม่ใช่สิ่งที่คุณต้องคำนึงถึงด้วยตัวเองที่บล็อกของขนาดนั้นปล่อยให้อยู่เพียง 4 บล็อกต่อกิโลไบต์ โดยทั่วไปแล้วบรรทัดแคชมีขนาดเพียง 64 ไบต์ หากคุณต้องการลดการพลาดของแคชคุณเพียงแค่มุ่งเน้นการจัดบล็อกเหล่านั้นให้เหมาะสม
วิธีที่รวดเร็วมากในการเปลี่ยนรูปแบบหน่วยความจำที่เข้าถึงแบบสุ่มเป็นแบบลำดับคือการใช้บิตเซ็ต สมมติว่าคุณมีดัชนีปริมาณเรือและพวกมันอยู่ในลำดับแบบสุ่ม คุณสามารถไถพรวนมันและทำเครื่องหมายบิตในบิตเซ็ต จากนั้นคุณสามารถวนซ้ำผ่านชุดบิตของคุณและตรวจสอบว่าไบต์ใดไม่เป็นศูนย์ตรวจสอบพูดครั้งละ 64- บิต เมื่อคุณพบชุด 64 บิตซึ่งตั้งไว้อย่างน้อยหนึ่งบิตคุณสามารถใช้คำสั่งFFSเพื่อกำหนดว่าบิตใดที่ตั้งค่าไว้อย่างรวดเร็ว บิตบอกให้คุณทราบว่าคุณควรเข้าถึงดัชนีใดบ้างยกเว้นตอนนี้คุณจะได้รับดัชนีเรียงตามลำดับ
สิ่งนี้มีค่าใช้จ่ายบางส่วน แต่อาจเป็นการแลกเปลี่ยนที่คุ้มค่าในบางกรณีโดยเฉพาะอย่างยิ่งหากคุณจะวนรอบดัชนีเหล่านี้หลายครั้ง
การเข้าถึงไอเท็มนั้นไม่ง่ายอย่างนั้นมีระดับทางอ้อมเพิ่มขึ้น สิ่งนี้จะได้รับการปรับให้เหมาะสมหรือไม่ มันจะทำให้เกิดปัญหาแคชหรือไม่
ไม่ไม่สามารถปรับให้เหมาะสมได้ อย่างน้อยการเข้าถึงแบบสุ่มจะมีค่าใช้จ่ายมากขึ้นด้วยโครงสร้างนี้ บ่อยครั้งที่มันจะไม่เพิ่มแคชของคุณเนื่องจากคุณมีแนวโน้มที่จะได้รับตำแหน่งทางโลกที่สูงด้วยอาร์เรย์ของพอยน์เตอร์ไปยังบล็อก
เนื่องจากมีการเติบโตเป็นเส้นตรงหลังจากขีด จำกัด ของการตี 4M คุณจึงสามารถจัดสรรได้มากกว่าปกติ (เช่นการจัดสรรสูงสุด 250 หน่วยสำหรับหน่วยความจำ 1GB) ไม่มีหน่วยความจำเพิ่มเติมถูกคัดลอกหลังจาก 4M แต่ฉันไม่แน่ใจว่าการจัดสรรเพิ่มเติมมีราคาแพงกว่าการคัดลอกหน่วยความจำขนาดใหญ่
ในทางปฏิบัติการคัดลอกมักจะเร็วกว่าเพราะมันเป็นกรณีที่หายากเกิดขึ้นเพียงบางสิ่งบางอย่างเช่นlog(N)/log(2)
จำนวนครั้งทั้งหมดในขณะที่ลดความซับซ้อนของกรณีทั่วไปที่ราคาถูกซึ่งคุณสามารถเขียนองค์ประกอบลงในอาร์เรย์ได้หลายครั้งก่อนที่จะเต็ม ดังนั้นโดยทั่วไปคุณจะไม่ได้รับการแทรกเร็วขึ้นด้วยโครงสร้างประเภทนี้เนื่องจากงานกรณีทั่วไปมีราคาแพงกว่าแม้ว่าจะไม่ต้องจัดการกับกรณีที่หายากที่มีราคาแพงในการจัดสรรอาร์เรย์ขนาดใหญ่
การดึงดูดหลักของโครงสร้างนี้สำหรับฉันแม้จะมีข้อเสียทั้งหมดคือการใช้หน่วยความจำลดลงโดยไม่ต้องกังวลเกี่ยวกับ OOM ความสามารถในการจัดเก็บดัชนีและพอยน์เตอร์ที่ไม่ได้รับการรับรองความถูกต้อง มันเป็นเรื่องดีที่มีโครงสร้างข้อมูลที่คุณสามารถแทรกและลบสิ่งต่าง ๆ ในเวลาที่คงที่ในขณะที่มันล้างตัวเองให้กับคุณและจะไม่ทำให้ตัวชี้และดัชนีในโครงสร้างนั้นไม่ถูกต้อง