วิธีการได้รับประโยชน์จาก cpu แคชในเอ็นจิ้นเกมเอนทิตีคอมโพเนนต์ระบบ?


15

ฉันมักจะอ่านในเอกสารประกอบเครื่องมือเกมของ ECS ซึ่งเป็นสถาปัตยกรรมที่ดีสำหรับการใช้แคช cpu อย่างชาญฉลาด

แต่ฉันไม่สามารถคิดได้ว่าเราจะได้ประโยชน์จาก cpu cache ได้อย่างไร

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

เมื่อเราใช้ระบบพวกเขาต้องการรายการเอนทิตีซึ่งเป็นรายการเอนทิตีที่มีส่วนประกอบที่มีประเภทเฉพาะ

แต่รายการเหล่านี้ให้ส่วนประกอบในวิธีการสุ่มไม่ใช่ตามลำดับ

ดังนั้นวิธีการออกแบบ ECS เพื่อเพิ่มการเข้าชมแคชสูงสุด?

แก้ไข:

ตัวอย่างเช่นระบบ Physic ต้องการรายการเอนทิตีสำหรับเอนทิตีที่มีส่วนประกอบ RigidBody และ Transform (มีพูลสำหรับ RigidBody และพูลสำหรับคอมโพเนนต์การแปลง)

ดังนั้นลูปสำหรับการอัพเดตเอนทิตีจะเป็นดังนี้:

for (Entity eid in entitiesList) {
    // Get rigid body component
    RigidBody *rigidBody = entityManager.getComponentFromEntity<RigidBody>(eid);

    // Get transform component
    Transform *transform = entityManager.getComponentFromEntity<Transform>(eid);

    // Do something with rigid body and transform component
}

ปัญหาคือองค์ประกอบ RigidBody ของเอนทิตี 1 สามารถอยู่ที่ดัชนี 2 ของพูลและคอมโพเนนต์ Tranform ของเอนทิตี 1 ที่ดัชนี 0 ของพูลของมัน (เนื่องจากเอนทิตีบางอย่างสามารถมีคอมโพเนนต์บางตัวไม่ใช่คอมโพเนนต์อื่นและเนื่องจากการเพิ่ม / ลบเอนทิตี ส่วนประกอบแบบสุ่ม)

ดังนั้นแม้ว่าส่วนประกอบจะต่อเนื่องกันในหน่วยความจำ แต่ก็อ่านแบบสุ่มและมันจะมีแคชมากกว่านั้น

หากไม่มีวิธีในการดึงส่วนประกอบถัดไปในลูปมาก่อน


คุณสามารถแสดงให้เราเห็นว่าคุณจัดสรรแต่ละองค์ประกอบอย่างไร
concept3d

ด้วยตัวจัดสรรพูลอย่างง่ายและตัวจัดการแฮนเดอร์เพื่อให้มีการอ้างอิงส่วนประกอบเพื่อจัดการการย้ายตำแหน่งของส่วนประกอบในพูล (เพื่อให้ส่วนประกอบติดกันในหน่วยความจำ)
Johnmph

ลูปตัวอย่างของคุณสันนิษฐานว่าการอัพเดตคอมโพเนนต์ถูกอินเตอร์ลีฟต่อเอนทิตี ในหลายกรณีเป็นไปได้ที่จะอัปเดตส่วนประกอบเป็นกลุ่มตามประเภทส่วนประกอบ (เช่นอัปเดตส่วนประกอบ rigidbody ทั้งหมดก่อนจากนั้นอัปเดตการแปลงทั้งหมดด้วยข้อมูล rigidbody สำเร็จรูปจากนั้นอัปเดตข้อมูลการแสดงผลทั้งหมดด้วยการแปลงใหม่ ... ) - ใช้สำหรับการอัพเดทแต่ละองค์ประกอบ ฉันคิดว่าโครงสร้างประเภทนี้คือสิ่งที่ Nick Wiggill แนะนำไว้ด้านล่าง
DMGregory

เป็นตัวอย่างของฉันที่ไม่ดีจริง ๆ แล้วมันเป็นระบบ "ปรับปรุงการแปลงทั้งหมดด้วยข้อมูลร่างกายแข็งเสร็จ" มากกว่าระบบฟิสิกส์ แต่ปัญหายังคงเหมือนเดิมในระบบเหล่านี้ (การปรับปรุงการแปลงด้วยเนื้อหาที่เข้มงวดการแสดงการอัปเดตที่มีการแปลง ... ) เราจะต้องมีองค์ประกอบมากกว่าหนึ่งประเภทในเวลาเดียวกัน
Johnmph

ไม่แน่ใจว่าสิ่งนี้อาจเกี่ยวข้องหรือไม่ gamasutra.com/view/feature/6345/…
DMGregory

คำตอบ:


13

บทความของ Mick Westอธิบายถึงกระบวนการของการปรับข้อมูลส่วนประกอบเอนทิตีเชิงเส้นทั้งหมด มันใช้งานได้กับซีรีย์ Tony Hawk เมื่อหลายปีก่อนบนฮาร์ดแวร์ที่น่าประทับใจน้อยกว่าที่เรามีในทุกวันนี้เพื่อปรับปรุงประสิทธิภาพอย่างมาก โดยทั่วไปเขาใช้อาร์เรย์ทั่วโลกที่ได้รับการจัดสรรล่วงหน้าสำหรับข้อมูลเอนทิตีแต่ละประเภท (ตำแหน่งคะแนนและอะไรที่ไม่ได้ระบุไว้) และอ้างอิงแต่ละอาร์เรย์ในเฟสที่แตกต่างกันของupdate()ฟังก์ชันทั่วทั้งระบบของเขา คุณสามารถสันนิษฐานได้ว่าข้อมูลสำหรับแต่ละเอนทิตีจะอยู่ที่ดัชนีอาเรย์เดียวกันในแต่ละอาเรย์ระดับโลกดังนั้นตัวอย่างเช่นหากผู้เล่นถูกสร้างขึ้นก่อนมันอาจมีข้อมูลอยู่[0]ในแต่ละอาเรย์

เฉพาะเจาะจงมากขึ้นสำหรับการปรับให้เหมาะสมแคชสไลด์ของ Christer Ericssonสำหรับ C และ C ++

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

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

โปรดจำไว้ว่าแคช L2 และ L3 (หากคุณสามารถสันนิษฐานว่าสิ่งเหล่านี้สำหรับแพลตฟอร์มเป้าหมายของคุณ) ทำอะไรมากมายเพื่อบรรเทาปัญหาที่แคช L1 อาจประสบเช่นความกว้างของบรรทัดที่ จำกัด ดังนั้นแม้ใน L1 miss เหล่านี้ก็คือตาข่ายนิรภัยที่ส่วนใหญ่มักจะป้องกันข้อความเสริมไปยังหน่วยความจำหลักซึ่งเป็นคำสั่งขนาดที่ช้ากว่าคำบรรยายภาพถึงแคชในระดับใด

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


1
เพียงหมายเหตุสำหรับทุกคนที่อาจยังใหม่กับ C ++: std::vectorนั้นเป็นอาร์เรย์ที่ปรับขนาดได้แบบไดนามิกดังนั้นจึงเป็นเรื่องที่ต่อเนื่องกัน (โดยพฤตินัยในรุ่น C ++ รุ่นเก่าและรุ่นใหม่ในรุ่น C ++) การใช้งานบางอย่างของstd::dequeยัง "ต่อเนื่องกันพอ" (แม้ว่าจะไม่ใช่ของ Microsoft)
Sean Middleditch

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

1
@ Johnmph ใช่ฉันเห็นด้วยกับ Nick ทั้งหมดเกี่ยวกับวิธีการจัดเก็บในหน่วยความจำหากคุณมีเอนทิตี้ของพอยน์เตอร์ที่มีสององค์ประกอบที่อยู่ในหน่วยความจำที่อยู่ห่างไกลคุณไม่ได้อยู่ในท้องที่
concept3d

2
@ Johnmph: แน่นอนบทความของ Mick West ถือว่าการพึ่งพาซึ่งกันและกันน้อยที่สุด ดังนั้น: ลดการพึ่งพาให้น้อยที่สุด; ข้อมูลซ้ำตามเส้นแคชที่คุณไม่สามารถลดการอ้างอิงที่ ... เช่นรวมถึงการเปลี่ยนข้างทั้ง RigidBody และ Render; และเพื่อให้พอดีกับสายแคชคุณอาจต้องลดอะตอมข้อมูลของคุณให้มากที่สุดเท่าที่จะเป็นไปได้ ... สิ่งนี้สามารถทำได้โดยการเปลี่ยนจาก floating point เป็น fixed point (4 bytes vs 2 bytes) ต่อค่าทศนิยม แต่ไม่ทางใดก็ทางหนึ่งไม่ว่าคุณจะทำอย่างไรข้อมูลของคุณจะต้องพอดีกับความกว้างของบรรทัดแคชตามที่ระบุไว้ concept3d เพื่อประสิทธิภาพสูงสุด
วิศวกร

2
@Johnmph ไม่ทุกครั้งที่คุณเขียนข้อมูลการแปลงคุณเพียงเขียนข้อมูลลงในทั้งสองอาร์เรย์ ไม่ใช่สิ่งที่คุณต้องการเขียน เมื่อคุณส่งออกการเขียนก็ถือว่าทำได้ดี มันคือการอ่านในภายหลังในการอัปเดตเมื่อคุณเรียกใช้ Physics และ Renderer ที่ต้องมีการเข้าถึงข้อมูลที่เกี่ยวข้องทั้งหมดทันทีในบรรทัดแคชเดียวใกล้ชิดและเป็นส่วนตัวกับ CPU นอกจากนี้ถ้าคุณต้องการมันทั้งหมดด้วยกันคุณก็จะทำแบบจำลองต่อไปหรือให้แน่ใจว่าฟิสิกส์แปลงและเรนเดอร์ให้พอดีกับแคชบรรทัดเดียว ... 64 ไบต์เป็นเรื่องปกติและเป็นข้อมูลจำนวนมากจริงๆ! ...
วิศวกร
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.