อาร์เรย์ที่ไม่ต่อเนื่องกันมีประสิทธิภาพหรือไม่


12

ใน C # เมื่อผู้ใช้สร้างList<byte>และเพิ่มไบต์มันมีโอกาสที่จะหมดพื้นที่และจำเป็นต้องจัดสรรพื้นที่เพิ่มเติม มันจัดสรรสองเท่า (หรือตัวคูณอื่น ๆ ) ขนาดของอาร์เรย์ก่อนหน้าคัดลอกไบต์ไปและทิ้งการอ้างอิงไปยังอาร์เรย์เก่า ฉันรู้ว่ารายการเติบโตแบบทวีคูณเพราะการจัดสรรแต่ละครั้งมีราคาแพงและนี่เป็นการ จำกัด การO(log n)จัดสรรโดยที่การเพิ่ม10รายการพิเศษทุกครั้งจะส่งผลให้เกิดการO(n)จัดสรร

อย่างไรก็ตามสำหรับอาเรย์ขนาดใหญ่อาจมีพื้นที่ว่างจำนวนมากบางทีเกือบครึ่งอาเรย์ เพื่อลดหน่วยความจำฉันเขียนคลาสที่คล้ายกันNonContiguousArrayListซึ่งใช้List<byte>เป็นแบ็คอัพสโตร์หากมีน้อยกว่า 4MB ในรายการจากนั้นก็จะจัดสรรอาร์เรย์ไบต์ 4MB เพิ่มเติมตามNonContiguousArrayListขนาดที่เพิ่มขึ้น

ต่างจากList<byte>อาร์เรย์เหล่านี้ที่ไม่ต่อเนื่องกันดังนั้นจึงไม่มีการคัดลอกข้อมูลรอบ ๆ เพียงแค่จัดสรร 4M เพิ่มเติม เมื่อรายการถูกค้นหาดัชนีจะถูกหารด้วย 4M เพื่อรับดัชนีของอาร์เรย์ที่มีรายการจากนั้นโมดูโล 4M เพื่อรับดัชนีภายในอาร์เรย์

คุณช่วยชี้ปัญหาเกี่ยวกับวิธีนี้ได้ไหม? นี่คือรายการของฉัน:

  • อาร์เรย์ที่ไม่ต่อเนื่องกันไม่มีตำแหน่งแคชซึ่งส่งผลให้ประสิทธิภาพไม่ดี อย่างไรก็ตามขนาดบล็อก 4M ดูเหมือนว่าจะมีพื้นที่เพียงพอสำหรับการแคชที่ดี
  • การเข้าถึงไอเท็มนั้นไม่ง่ายอย่างนั้นมีระดับทางอ้อมเพิ่มขึ้น สิ่งนี้จะได้รับการปรับให้เหมาะสมหรือไม่ มันจะทำให้เกิดปัญหาแคชหรือไม่
  • เนื่องจากมีการเติบโตเป็นเส้นตรงหลังจากขีด จำกัด ของการตี 4M คุณจึงสามารถจัดสรรได้มากกว่าปกติ (เช่นการจัดสรรสูงสุด 250 หน่วยสำหรับหน่วยความจำ 1GB) ไม่มีหน่วยความจำเพิ่มเติมถูกคัดลอกหลังจาก 4M แต่ฉันไม่แน่ใจว่าการจัดสรรเพิ่มเติมมีราคาแพงกว่าการคัดลอกหน่วยความจำขนาดใหญ่

8
คุณได้ทฤษฏีทฤษฎี (นำแคชไปพิจารณาแล้วกล่าวถึงความซับซ้อนเชิงซ้อน) สิ่งที่เหลือคือการเสียบพารามิเตอร์ (ที่นี่ 4M รายการต่อรายการย่อย) และอาจปรับให้เหมาะสมขนาดเล็ก ขณะนี้เป็นเวลาที่จะสร้างมาตรฐานเนื่องจากไม่ต้องแก้ไขฮาร์ดแวร์และการนำไปใช้งานมีข้อมูลน้อยเกินไปที่จะหารือเกี่ยวกับประสิทธิภาพต่อไป

3
หากคุณทำงานกับองค์ประกอบมากกว่า 4 ล้านรายการในคอลเล็กชันเดียวฉันคาดว่าการเพิ่มประสิทธิภาพคอนเทนเนอร์ขนาดเล็กเป็นสิ่งที่คุณกังวลน้อยที่สุดเกี่ยวกับประสิทธิภาพ
Telastyn

2
สิ่งที่คุณอธิบายคล้ายกับรายการลิงค์ที่ไม่ได้ควบคุม (ที่มีโหนดขนาดใหญ่มาก) การยืนยันว่าพวกเขาไม่มีที่ตั้งแคชนั้นผิดเล็กน้อย อาร์เรย์จำนวนมากเท่านั้นที่ควรอยู่ในบรรทัดแคชเดียว สมมุติว่า 64 ไบต์ ดังนั้นทุก 64 ไบต์คุณจะพลาดแคช ตอนนี้ให้พิจารณารายการที่ลิงก์ที่ไม่ได้ควบคุมซึ่งโหนดนั้นมีขนาดใหญ่หลาย 64 ไบต์ (รวมถึงส่วนหัวของวัตถุสำหรับการรวบรวมขยะ) คุณยังคงได้รับเพียงแคชเดียวพลาดทุก 64 ไบต์และมันจะไม่สำคัญว่าโหนดไม่ได้อยู่ติดกันในหน่วยความจำ
Doval

@Doval ไม่ใช่รายการที่เชื่อมโยงที่ไม่ได้ควบคุมเนื่องจากชิ้นส่วน 4M ถูกเก็บไว้ในอาร์เรย์ดังนั้นการเข้าถึงองค์ประกอบใด ๆ คือ O (1) ไม่ใช่ O (n / B) โดยที่ B คือขนาดบล็อก

2
@ user2313838 หากมีหน่วยความจำ 1,000MB และอาร์เรย์ 350MB หน่วยความจำที่จำเป็นในการขยายอาร์เรย์จะมีค่า 1050MB ซึ่งสูงกว่าที่มีอยู่นั่นคือปัญหาหลักขีด จำกัด ประสิทธิผลของคุณคือ 1 ใน 3 ของพื้นที่ทั้งหมดของคุณ TrimExcessจะช่วยได้ก็ต่อเมื่อมีการสร้างรายการแล้วและยังต้องการพื้นที่เพียงพอสำหรับการคัดลอก
noisecapella

คำตอบ:


5

ในระดับที่คุณพูดถึงความกังวลแตกต่างจากที่คุณพูดถึงอย่างสิ้นเชิง

ตำแหน่งแคช

  • มีแนวคิดที่เกี่ยวข้องสองประการ:
    1. Locality การใช้ซ้ำข้อมูลบนแคชบรรทัดเดียวกัน (ตำแหน่งเชิงพื้นที่) ที่เพิ่งเข้าชมเมื่อเร็ว ๆ นี้ (local temporal)
    2. การดึงข้อมูลแคชอัตโนมัติ (สตรีมมิ่ง)
  • ในระดับที่คุณกล่าวถึง (ร้อย MB ถึงกิกะไบต์ในหน่วย 4MB) ทั้งสองปัจจัยมีส่วนเกี่ยวข้องกับรูปแบบการเข้าถึงองค์ประกอบข้อมูลของคุณมากกว่ารูปแบบหน่วยความจำ
  • การทำนายของฉัน (clueless) คือสถิติอาจมีความแตกต่างของประสิทธิภาพไม่มากไปกว่าการจัดสรรหน่วยความจำขนาดยักษ์ที่ต่อเนื่องกัน ไม่มีกำไรไม่มีการสูญเสีย

รูปแบบการเข้าถึงองค์ประกอบข้อมูล

  • บทความนี้แสดงให้เห็นว่ารูปแบบการเข้าถึงหน่วยความจำจะส่งผลต่อประสิทธิภาพอย่างไร
  • ในระยะสั้นเพียงแค่เก็บไว้ในใจว่าถ้าอัลกอริทึมของคุณมีอยู่แล้ว bottlenecked โดยแบนด์วิดธ์หน่วยความจำวิธีเดียวที่จะปรับปรุงประสิทธิภาพการทำงานคือการทำงานที่เป็นประโยชน์มากขึ้นด้วยข้อมูลที่มีการโหลดแล้วในแคช
  • ในคำอื่น ๆ แม้ว่าYourList[k]และYourList[k+1]มีความน่าจะเป็นสูงที่จะติดต่อกัน (หนึ่งในสี่ล้านโอกาสที่จะไม่ได้) ความจริงนั้นจะไม่ช่วยประสิทธิภาพหากคุณเข้าถึงรายการของคุณอย่างสมบูรณ์แบบสุ่มหรือในขั้นตอนที่คาดเดาไม่ได้เช่นwhile { index += random.Next(1024); DoStuff(YourList[index]); }

การโต้ตอบกับระบบ GC

  • ในความคิดของฉันนี่คือที่ที่คุณควรเน้นมากที่สุด
  • อย่างน้อยที่สุดเข้าใจการออกแบบของคุณจะโต้ตอบกับ:
  • ฉันไม่มีความรู้ในหัวข้อเหล่านี้ดังนั้นฉันจะปล่อยให้คนอื่นมีส่วนร่วม

การคำนวณออฟเซ็ตค่าโสหุ้ยที่อยู่

  • โดยทั่วไปโค้ด C # กำลังทำการคำนวณออฟเซ็ตจำนวนมากอยู่แล้วดังนั้นค่าใช้จ่ายเพิ่มเติมจากแบบแผนของคุณจะไม่เลวร้ายไปกว่าโค้ด C # ทั่วไปที่ทำงานกับอาเรย์เดี่ยว
    • โปรดจำไว้ว่ารหัส C # ทำการตรวจสอบช่วงอาร์เรย์ด้วย และข้อเท็จจริงนี้ไม่ได้ป้องกัน C # จากการเข้าถึงประสิทธิภาพการประมวลผลอาร์เรย์ที่เทียบเท่ากับรหัส C ++
    • เหตุผลก็คือประสิทธิภาพการทำงานส่วนใหญ่เกิดจากคอขวดแบนด์วิดธ์ของหน่วยความจำ
    • เคล็ดลับในการเพิ่มยูทิลิตี้จากแบนด์วิดธ์หน่วยความจำคือการใช้คำแนะนำ SIMD สำหรับการดำเนินการอ่าน / เขียนหน่วยความจำ C # ทั่วไปและ C ++ ทั่วไปไม่ทำสิ่งนี้ คุณต้องหันไปใช้ห้องสมุดหรือส่วนเสริมภาษา

เพื่ออธิบายสาเหตุ:

  • ทำการคำนวณที่อยู่
  • (ในกรณีของ OP ให้โหลดที่อยู่พื้นฐานอันหนึ่ง (ซึ่งอยู่ในแคชแล้ว) จากนั้นทำการคำนวณที่อยู่เพิ่มเติม)
  • อ่านจาก / เขียนไปยังที่อยู่องค์ประกอบ

ขั้นตอนสุดท้ายยังคงใช้เวลาส่วนแบ่งของสิงโต

ข้อเสนอแนะส่วนบุคคล

  • คุณสามารถให้CopyRangeฟังก์ชั่นซึ่งจะประพฤติเช่นArray.Copyฟังก์ชั่น แต่จะดำเนินการระหว่างสองกรณีของคุณหรือระหว่างหนึ่งตัวอย่างและปกติอีกNonContiguousByteArray byte[]ฟังก์ชั่นเหล่านี้สามารถใช้ประโยชน์จากรหัส SIMD (C ++ หรือ C #) เพื่อเพิ่มการใช้แบนด์วิดธ์หน่วยความจำให้สูงสุดจากนั้นโค้ด C # ของคุณสามารถทำงานในช่วงที่คัดลอกได้โดยไม่ต้องคำนวณค่าใช้จ่าย

ความกังวลเกี่ยวกับการใช้งานและการทำงานร่วมกัน

  • เห็นได้ชัดว่าคุณไม่สามารถใช้สิ่งนี้NonContiguousByteArrayกับไลบรารี C #, C ++ หรือภาษาต่างประเทศใด ๆ ที่คาดว่าจะมีอาร์เรย์ไบต์ที่ต่อเนื่องกันหรืออาร์เรย์ไบต์ที่สามารถตรึงได้
  • อย่างไรก็ตามหากคุณเขียนไลบรารีการเร่งความเร็ว C ++ ของคุณเอง (ด้วย P / Invoke หรือ C ++ / CLI) คุณสามารถส่งผ่านรายการที่อยู่พื้นฐานของบล็อก 4MB หลาย ๆ ตัวลงในโค้ดพื้นฐานได้
    • ตัวอย่างเช่นถ้าคุณต้องการที่จะให้เข้าถึงองค์ประกอบเริ่มต้นที่(3 * 1024 * 1024)และสิ้นสุดที่(5 * 1024 * 1024 - 1)หมายถึงนี้การเข้าถึงจะช่วงข้ามและchunk[0] chunk[1]จากนั้นคุณสามารถสร้างอาร์เรย์ (ขนาด 2) ของอาร์เรย์ไบต์ (ขนาด 4M) ตรึงที่อยู่ของกลุ่มข้อมูลเหล่านี้และส่งต่อไปยังรหัสอ้างอิง
  • ข้อกังวลเกี่ยวกับการใช้งานอื่นคือคุณจะไม่สามารถใช้งานIList<byte>อินเทอร์เฟซได้อย่างมีประสิทธิภาพInsertและRemoveจะใช้เวลาในการประมวลผลนานเกินไปเนื่องจากต้องใช้O(N)เวลา
    • ในความเป็นจริงดูเหมือนว่าคุณจะไม่สามารถใช้สิ่งอื่นนอกเหนือจากIEnumerable<byte>นั้นคือสามารถสแกนตามลำดับและนั่นก็คือ

2
คุณดูเหมือนจะพลาดข้อได้เปรียบหลักของโครงสร้างข้อมูลซึ่งช่วยให้คุณสร้างรายการที่มีขนาดใหญ่มากโดยไม่ต้องใช้หน่วยความจำไม่เพียงพอ เมื่อขยายรายการ <T> จะต้องมีอาร์เรย์ใหม่สองครั้งใหญ่พอ ๆ กับรายการเก่าและทั้งคู่จะต้องอยู่ในหน่วยความจำพร้อมกัน
Frank Hileman

6

เป็นที่น่าสังเกตว่า C ++ มีโครงสร้างที่เทียบเท่าโดยมาตรฐานstd::dequeแล้ว ปัจจุบันแนะนำให้เป็นตัวเลือกเริ่มต้นสำหรับต้องการลำดับการเข้าถึงแบบสุ่มของสิ่งต่าง ๆ

ความจริงก็คือหน่วยความจำที่ต่อเนื่องนั้นแทบไม่จำเป็นเลยเมื่อข้อมูลผ่านขนาดที่กำหนด - แคชบรรทัดมีขนาดเพียง 64 ไบต์และขนาดหน้าเป็นเพียง 4-8KB (ค่าทั่วไปในปัจจุบัน) เมื่อคุณเริ่มพูดคุยเกี่ยวกับบาง MB มันจะออกไปนอกหน้าต่างเป็นกังวล เช่นเดียวกับค่าใช้จ่ายในการจัดสรร ราคาของการประมวลผลข้อมูลทั้งหมด - เพียงแค่อ่านมันก็จะทำให้ราคาของการจัดสรรอยู่ดี

เหตุผลอื่นที่ต้องกังวลเกี่ยวกับเรื่องนี้คือการเชื่อมต่อกับ C APIs แต่คุณไม่สามารถรับตัวชี้ไปยังบัฟเฟอร์ของรายการได้ดังนั้นจึงไม่ต้องกังวล


นั่นเป็นเรื่องที่น่าสนใจฉันไม่รู้ว่าdequeมีการนำไปใช้ที่คล้ายกัน
noisecapella

ใครบ้างที่แนะนำ std :: deque? คุณสามารถให้แหล่งที่มาหรือไม่? ฉันคิดเสมอว่า std :: vector เป็นตัวเลือกเริ่มต้นที่แนะนำ
Teimpz

std::dequeในความเป็นจริงเป็นกำลังใจอย่างมากบางส่วนเพราะการใช้ห้องสมุดมาตรฐาน MS ไม่ดี
Sebastian Redl

3

เมื่อหน่วยความจำถูกจัดสรรที่จุดต่าง ๆ ในเวลาเช่นเดียวกับในอาร์เรย์ย่อยภายในโครงสร้างข้อมูลของคุณพวกเขาสามารถอยู่ห่างจากกันในหน่วยความจำ ไม่ว่าจะเป็นปัญหาหรือไม่นั้นขึ้นอยู่กับซีพียูและยากต่อการคาดเดาอีกต่อไป คุณต้องทดสอบมัน

นี่เป็นความคิดที่ยอดเยี่ยมและเป็นสิ่งที่ฉันเคยใช้ในอดีต แน่นอนคุณควรใช้พลังของสองสำหรับขนาดย่อยของคุณและขยับบิตสำหรับการหาร (อาจเกิดขึ้นเป็นส่วนหนึ่งของการเพิ่มประสิทธิภาพ) ฉันพบว่าโครงสร้างประเภทนี้ช้าลงเล็กน้อยในคอมไพเลอร์นั้นสามารถปรับทิศทางของอาเรย์เดียวได้ง่ายขึ้น คุณต้องทดสอบเนื่องจากการเพิ่มประสิทธิภาพประเภทนี้เปลี่ยนแปลงตลอดเวลา

ข้อได้เปรียบหลักคือคุณสามารถเรียกใช้หน่วยความจำบนขีด จำกัด สูงสุดในระบบของคุณได้ตราบใดที่คุณใช้โครงสร้างประเภทเหล่านี้อย่างสม่ำเสมอ ตราบใดที่คุณทำให้โครงสร้างข้อมูลของคุณใหญ่ขึ้นและไม่ทำให้เกิดขยะคุณต้องหลีกเลี่ยงการรวบรวมขยะพิเศษที่จะเกิดขึ้นกับรายการปกติ สำหรับรายชื่อยักษ์มันสามารถสร้างความแตกต่างใหญ่: ความแตกต่างระหว่างการทำงานต่อเนื่องและหน่วยความจำไม่เพียงพอ

การจัดสรรพิเศษเป็นปัญหาเฉพาะเมื่อชิ้นย่อยย่อยของคุณมีขนาดเล็กเนื่องจากมีหน่วยความจำโอเวอร์เฮดในการจัดสรรแต่ละอาร์เรย์

ฉันได้สร้างโครงสร้างที่คล้ายกันสำหรับพจนานุกรม (ตารางแฮช) พจนานุกรมที่จัดเตรียมโดยกรอบงาน. net มีปัญหาเช่นเดียวกับรายการ พจนานุกรมยากขึ้นในการที่คุณต้องหลีกเลี่ยงการ rehashing เช่นกัน


ตัวบีบอัดแบบกระชับอาจทำให้ชิ้นส่วนเล็กติดกัน
DeadMG

@DeadMG ฉันอ้างถึงสถานการณ์ที่สิ่งนี้ไม่สามารถเกิดขึ้นได้: มีชิ้นอื่น ๆ อยู่ระหว่างนั้นซึ่งไม่ใช่ขยะ ด้วย List <T> คุณจะได้รับการรับประกันว่าจะมีหน่วยความจำต่อเนื่องสำหรับอาเรย์ของคุณ ด้วยรายการที่ถูกแยกออกหน่วยความจำจะต่อเนื่องกันภายในก้อนเท่านั้นเว้นแต่คุณจะมีสถานการณ์ที่โชคดีที่คุณพูดถึงการกระชับ แต่การบีบอัดอาจต้องการย้ายข้อมูลจำนวนมากและอาร์เรย์ขนาดใหญ่จะเข้าสู่ Large Object Heap มันซับซ้อน.
Frank Hileman

2

ด้วยขนาดบล็อก 4M แม้แต่บล็อกเดียวก็ไม่รับประกันว่าจะต่อเนื่องกันในหน่วยความจำกายภาพ มันมีขนาดใหญ่กว่าขนาดหน้า VM ทั่วไป สถานที่นั้นไม่มีความหมายในระดับนั้น

คุณจะต้องกังวลเกี่ยวกับการแตกแฟรกเมนต์ของฮีป: หากการจัดสรรเกิดขึ้นเช่นบล็อกของคุณส่วนใหญ่ไม่ต่อเนื่องกันในฮีปแล้วเมื่อฮีปเหล่านั้นถูกเรียกคืนโดย GC คุณจะจบลงด้วยฮีปที่อาจแตกเกินไป การจัดสรรที่ตามมา มักจะเป็นสถานการณ์ที่เลวร้ายยิ่งขึ้นเนื่องจากความล้มเหลวจะเกิดขึ้นในสถานที่ที่ไม่เกี่ยวข้องและอาจบังคับให้รีสตาร์ทแอปพลิเคชัน


การกระชับ GCs นั้นไม่มีการแตกหัก
DeadMG

สิ่งนี้เป็นจริง แต่การบดอัด LOH มีให้เฉพาะใน. NET 4.5 หากฉันจำได้อย่างถูกต้อง
2313838

Listกองการบดอัดอาจต้องเสียค่าใช้จ่ายมากขึ้นกว่าพฤติกรรมการคัดลอก-on-จัดสรรมาตรฐาน
user2313838

วัตถุที่มีขนาดใหญ่พอสมควรและมีขนาดเหมาะสมนั้นไม่มีการแยกส่วนอย่างมีประสิทธิภาพ
DeadMG

2
@DeadMG: ความกังวลที่แท้จริงกับการบดอัด GC (ด้วยโครงร่าง 4MB นี้) คือการใช้เวลาในการตักเนื้อวัว 4MB เป็นผลให้มันอาจส่งผลให้หยุด GC ขนาดใหญ่ ด้วยเหตุนี้เมื่อใช้ชุดรูปแบบ 4MB นี้เป็นสิ่งสำคัญในการตรวจสอบสถิติ GC ที่สำคัญเพื่อดูว่ามันทำอะไรอยู่และเพื่อดำเนินการแก้ไข
วงเวียน

1

ฉันหมุนส่วนกลางของ codebase ของฉัน (เอ็นจิน ECS) รอบ ๆ ประเภทของโครงสร้างข้อมูลที่คุณอธิบายถึงแม้ว่ามันจะใช้บล็อกต่อเนื่องขนาดเล็ก (เช่น 4 กิโลไบต์แทนที่จะเป็น 4 เมกะไบต์)

ป้อนคำอธิบายรูปภาพที่นี่

ใช้รายการฟรีสองครั้งเพื่อให้ได้การแทรกและลบเวลาคงที่ด้วยรายการฟรีหนึ่งรายการสำหรับบล็อกฟรีที่พร้อมที่จะแทรก (บล็อกที่ไม่เต็ม) และรายการย่อยฟรีภายในบล็อกสำหรับดัชนีในบล็อกนั้น พร้อมที่จะถูกเรียกคืนเมื่อมีการแทรก

ฉันจะครอบคลุมข้อดีข้อเสียของโครงสร้างนี้ เริ่มจากข้อเสียกันก่อนเพราะมีหลายข้อ:

จุดด้อย

  1. การแทรกองค์ประกอบสองสามร้อยล้านครั้งเข้ากับโครงสร้างนี้ใช้เวลานานกว่าประมาณ 4 เท่ากว่าstd::vector(โครงสร้างที่ต่อเนื่องกันหมดจด) และฉันก็ค่อนข้างดีที่การเพิ่มประสิทธิภาพแบบไมโคร แต่มีแนวคิดที่จะต้องทำมากกว่าเพราะกรณีทั่วไปต้องตรวจสอบบล็อกฟรีที่ด้านบนสุดของรายการบล็อกฟรีก่อนจากนั้นเข้าถึงบล็อกและป๊อปดัชนีฟรีจากบล็อก รายการอิสระเขียนองค์ประกอบที่ตำแหน่งว่างจากนั้นตรวจสอบว่าบล็อกเต็มหรือไม่และแสดงป๊อปอัพจากรายการว่างของบล็อกถ้าใช่ ก็ยังคงดำเนินการอย่างต่อเนื่องเวลา std::vectorแต่มีความคงที่มากใหญ่กว่าการผลักดันกลับไป
  2. ใช้เวลาประมาณสองครั้งในการเข้าถึงองค์ประกอบโดยใช้รูปแบบการเข้าถึงแบบสุ่มที่ให้เลขคณิตพิเศษสำหรับการทำดัชนีและเลเยอร์ทางอ้อมเพิ่มเติม
  3. การเข้าถึงแบบลำดับไม่ได้แมปอย่างมีประสิทธิภาพกับการออกแบบตัววนซ้ำเนื่องจากตัววนซ้ำต้องทำการแยกสาขาเพิ่มเติมในแต่ละครั้งที่มีการเพิ่มขึ้น
  4. มีค่าใช้จ่ายหน่วยความจำเล็กน้อยโดยทั่วไปประมาณ 1 บิตต่อองค์ประกอบ 1 บิตต่อองค์ประกอบอาจไม่ฟังดูมากนัก แต่ถ้าคุณใช้สิ่งนี้เพื่อเก็บจำนวนเต็ม 16- ล้านล้านนั่นหมายความว่าการใช้หน่วยความจำเพิ่มขึ้น 6.25% มากกว่าอาร์เรย์ขนาดกะทัดรัด อย่างไรก็ตามในทางปฏิบัติสิ่งนี้มีแนวโน้มที่จะใช้หน่วยความจำน้อยกว่าstd::vectorนอกจากว่าคุณกำลังทำการกระชับข้อมูลvectorเพื่อกำจัดความจุที่เหลือที่สำรองไว้ นอกจากนี้ฉันมักจะไม่ใช้มันเพื่อจัดเก็บองค์ประกอบเล็ก ๆ

ข้อดี

  1. การเข้าถึงแบบลำดับโดยใช้for_eachฟังก์ชั่นที่ใช้ช่วงการประมวลผลการเรียกกลับขององค์ประกอบภายในบล็อกเกือบจะเทียบเคียงกับความเร็วของการเข้าถึงตามลำดับด้วยstd::vector(เช่น 10% diff เท่านั้น) ดังนั้นจึงไม่ค่อยมีประสิทธิภาพในกรณีการใช้งานที่สำคัญที่สุดสำหรับฉัน ( เวลาส่วนใหญ่ที่ใช้ในเอ็นจิน ECS อยู่ในการเข้าถึงแบบลำดับ)
  2. จะช่วยให้การลบเวลาคงที่จากตรงกลางด้วยโครงสร้าง deallocating บล็อกเมื่อพวกเขากลายเป็นที่ว่างเปล่าอย่างสมบูรณ์ ผลก็คือโดยทั่วไปค่อนข้างดีที่ทำให้แน่ใจว่าโครงสร้างข้อมูลไม่เคยใช้หน่วยความจำมากกว่าที่จำเป็น
  3. มันไม่ได้ทำให้ดัชนีขององค์ประกอบที่ไม่ได้ลบออกจากภาชนะโดยตรงเนื่องจากเป็นเพียงแค่ปล่อยให้รูอยู่ข้างหลังโดยใช้วิธีการแบบอิสระเพื่อเรียกคืนรูเหล่านั้นเมื่อทำการแทรกครั้งต่อไป
  4. คุณไม่ต้องกังวลมากเกี่ยวกับหน่วยความจำไม่เพียงพอแม้ว่าโครงสร้างนี้จะมีองค์ประกอบจำนวนมากเพราะมันเพียงแค่ร้องขอบล็อกเล็ก ๆ ที่ต่อเนื่องกันซึ่งไม่สร้างความท้าทายให้กับระบบปฏิบัติการเพื่อค้นหาสิ่งที่ไม่ได้ใช้งานจำนวนมากที่ต่อเนื่องกัน หน้า
  5. มันให้ยืมตัวเองได้ดีกับการทำงานพร้อมกันและความปลอดภัยของเธรดโดยไม่ต้องล็อคโครงสร้างทั้งหมดเนื่องจากการดำเนินการโดยทั่วไปจะทำการแปลแต่ละบล็อก

ตอนนี้หนึ่งในข้อดีที่ใหญ่ที่สุดสำหรับฉันก็คือมันกลายเป็นเรื่องไม่สำคัญที่จะทำให้โครงสร้างข้อมูลนี้เปลี่ยนแปลงไม่ได้แบบนี้:

ป้อนคำอธิบายรูปภาพที่นี่

นับตั้งแต่นั้นเป็นต้นมาก็เปิดประตูทุกประเภทเพื่อเขียนฟังก์ชั่นเพิ่มเติมโดยไม่มีผลข้างเคียงซึ่งทำให้ง่ายขึ้นมากในการบรรลุข้อยกเว้นความปลอดภัยความปลอดภัยของด้าย ฯลฯ ความไม่เปลี่ยนรูปนั้นเป็นสิ่งที่ฉันค้นพบว่าฉันสามารถทำได้ด้วย โครงสร้างข้อมูลในแบบย้อนหลังและโดยไม่ได้ตั้งใจ แต่หนึ่งในประโยชน์ที่ดีที่สุดที่มันมีเพราะมันทำให้การบำรุงรักษา codebase ง่ายขึ้นมาก

อาร์เรย์ที่ไม่ต่อเนื่องกันไม่มีตำแหน่งแคชซึ่งส่งผลให้ประสิทธิภาพไม่ดี อย่างไรก็ตามขนาดบล็อก 4M ดูเหมือนว่าจะมีพื้นที่เพียงพอสำหรับการแคชที่ดี

สถานที่อ้างอิงไม่ใช่สิ่งที่คุณต้องคำนึงถึงด้วยตัวเองที่บล็อกของขนาดนั้นปล่อยให้อยู่เพียง 4 บล็อกต่อกิโลไบต์ โดยทั่วไปแล้วบรรทัดแคชมีขนาดเพียง 64 ไบต์ หากคุณต้องการลดการพลาดของแคชคุณเพียงแค่มุ่งเน้นการจัดบล็อกเหล่านั้นให้เหมาะสม

วิธีที่รวดเร็วมากในการเปลี่ยนรูปแบบหน่วยความจำที่เข้าถึงแบบสุ่มเป็นแบบลำดับคือการใช้บิตเซ็ต สมมติว่าคุณมีดัชนีปริมาณเรือและพวกมันอยู่ในลำดับแบบสุ่ม คุณสามารถไถพรวนมันและทำเครื่องหมายบิตในบิตเซ็ต จากนั้นคุณสามารถวนซ้ำผ่านชุดบิตของคุณและตรวจสอบว่าไบต์ใดไม่เป็นศูนย์ตรวจสอบพูดครั้งละ 64- บิต เมื่อคุณพบชุด 64 บิตซึ่งตั้งไว้อย่างน้อยหนึ่งบิตคุณสามารถใช้คำสั่งFFSเพื่อกำหนดว่าบิตใดที่ตั้งค่าไว้อย่างรวดเร็ว บิตบอกให้คุณทราบว่าคุณควรเข้าถึงดัชนีใดบ้างยกเว้นตอนนี้คุณจะได้รับดัชนีเรียงตามลำดับ

สิ่งนี้มีค่าใช้จ่ายบางส่วน แต่อาจเป็นการแลกเปลี่ยนที่คุ้มค่าในบางกรณีโดยเฉพาะอย่างยิ่งหากคุณจะวนรอบดัชนีเหล่านี้หลายครั้ง

การเข้าถึงไอเท็มนั้นไม่ง่ายอย่างนั้นมีระดับทางอ้อมเพิ่มขึ้น สิ่งนี้จะได้รับการปรับให้เหมาะสมหรือไม่ มันจะทำให้เกิดปัญหาแคชหรือไม่

ไม่ไม่สามารถปรับให้เหมาะสมได้ อย่างน้อยการเข้าถึงแบบสุ่มจะมีค่าใช้จ่ายมากขึ้นด้วยโครงสร้างนี้ บ่อยครั้งที่มันจะไม่เพิ่มแคชของคุณเนื่องจากคุณมีแนวโน้มที่จะได้รับตำแหน่งทางโลกที่สูงด้วยอาร์เรย์ของพอยน์เตอร์ไปยังบล็อก

เนื่องจากมีการเติบโตเป็นเส้นตรงหลังจากขีด จำกัด ของการตี 4M คุณจึงสามารถจัดสรรได้มากกว่าปกติ (เช่นการจัดสรรสูงสุด 250 หน่วยสำหรับหน่วยความจำ 1GB) ไม่มีหน่วยความจำเพิ่มเติมถูกคัดลอกหลังจาก 4M แต่ฉันไม่แน่ใจว่าการจัดสรรเพิ่มเติมมีราคาแพงกว่าการคัดลอกหน่วยความจำขนาดใหญ่

ในทางปฏิบัติการคัดลอกมักจะเร็วกว่าเพราะมันเป็นกรณีที่หายากเกิดขึ้นเพียงบางสิ่งบางอย่างเช่นlog(N)/log(2)จำนวนครั้งทั้งหมดในขณะที่ลดความซับซ้อนของกรณีทั่วไปที่ราคาถูกซึ่งคุณสามารถเขียนองค์ประกอบลงในอาร์เรย์ได้หลายครั้งก่อนที่จะเต็ม ดังนั้นโดยทั่วไปคุณจะไม่ได้รับการแทรกเร็วขึ้นด้วยโครงสร้างประเภทนี้เนื่องจากงานกรณีทั่วไปมีราคาแพงกว่าแม้ว่าจะไม่ต้องจัดการกับกรณีที่หายากที่มีราคาแพงในการจัดสรรอาร์เรย์ขนาดใหญ่

การดึงดูดหลักของโครงสร้างนี้สำหรับฉันแม้จะมีข้อเสียทั้งหมดคือการใช้หน่วยความจำลดลงโดยไม่ต้องกังวลเกี่ยวกับ OOM ความสามารถในการจัดเก็บดัชนีและพอยน์เตอร์ที่ไม่ได้รับการรับรองความถูกต้อง มันเป็นเรื่องดีที่มีโครงสร้างข้อมูลที่คุณสามารถแทรกและลบสิ่งต่าง ๆ ในเวลาที่คงที่ในขณะที่มันล้างตัวเองให้กับคุณและจะไม่ทำให้ตัวชี้และดัชนีในโครงสร้างนั้นไม่ถูกต้อง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.