แยกการอ่าน / การคำนวณ / การเขียนอย่างมีประสิทธิภาพสำหรับการประมวลผลพร้อมกันของเอนทิตีในระบบ Entity / Component


11

ติดตั้ง

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

Entity
{
    id;
    map<id_type, Attribute> attributes;
}

System
{
    update();
    vector<Entity> entities;
}

ระบบที่เพิ่งเคลื่อนที่ไปตามเอนทิตีทั้งหมดในอัตราคงที่อาจเป็น

MovementSystem extends System
{
   update()
   {
      for each entity in entities
        position = entity.attributes["position"];
        position += vec3(1,1,1);
   }
}

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

ปัญหา

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

อย่างไรก็ตามบางครั้งระบบเหล่านี้ต้องการให้เอนทิตีโต้ตอบกับ (อ่าน / เขียนข้อมูลจาก / ถึง) ซึ่งกันและกันบางครั้งอยู่ในระบบเดียวกัน แต่บ่อยครั้งระหว่างระบบต่าง ๆ ที่ขึ้นอยู่กับแต่ละระบบ

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

และก่อนที่ระบบเรนเดอร์ในเครื่องยนต์สามารถเริ่มเรนเดอร์เอนทิตีได้มันต้องรอให้ระบบอื่นทำการประมวลผลให้เสร็จ

หากเราพยายามที่จะทำให้ขนานนี้มันจะนำไปสู่สภาพการแข่งขันแบบคลาสสิกที่ระบบต่าง ๆ อาจอ่านและแก้ไขข้อมูลในเวลาเดียวกัน

จะเป็นการดีที่จะมีวิธีแก้ปัญหาที่ระบบทั้งหมดอาจอ่านข้อมูลจากเอนทิตีที่ต้องการโดยไม่ต้องกังวลเกี่ยวกับระบบอื่น ๆ ที่แก้ไขข้อมูลเดียวกันในเวลาเดียวกัน ระบบเหล่านี้ด้วยตนเอง (ซึ่งบางครั้งอาจไม่สามารถทำได้)

ในการใช้งานขั้นพื้นฐานสิ่งนี้สามารถทำได้โดยเพียงแค่การอ่านและเขียนข้อมูลทั้งหมดในส่วนที่สำคัญ (ปกป้องพวกเขาด้วย mutexes) แต่นี่จะทำให้เกิดโอเวอร์เฮดของรันไทม์จำนวนมากและอาจไม่เหมาะสำหรับแอปพลิเคชันที่มีประสิทธิภาพสูง

สารละลาย?

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

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

คำถาม

ระบบดังกล่าวจะนำไปใช้อย่างไรเพื่อให้ได้ประสิทธิภาพสูงสุด? รายละเอียดการนำไปปฏิบัติของระบบดังกล่าวคืออะไรและมีข้อกำหนดเบื้องต้นสำหรับระบบ Entity-Component ที่ต้องการใช้โซลูชันนี้อย่างไร

คำตอบ:


1

----- (ตามคำถามที่แก้ไขแล้ว)

จุดแรก: เนื่องจากคุณไม่ได้กล่าวถึงการสร้างโปรไฟล์การสร้างรันไทม์ของคุณและพบความต้องการเฉพาะฉันขอแนะนำให้คุณทำเช่นนั้นโดยเร็ว โปรไฟล์ของคุณมีลักษณะอย่างไรคุณทุบแคชด้วยเลย์เอาต์หน่วยความจำที่ไม่ดีคือแกนหลักหนึ่งที่กำหนด 100% ระยะเวลาที่สัมพันธ์กับการประมวลผล ECS ของคุณกับส่วนที่เหลือของเครื่องยนต์ ฯลฯ ...

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

นอกจากนี้เนื่องจากคุณกำลังทำการประมวลผลอย่างต่อเนื่องกฎหลักที่คุณต้องการติดตามคือมีหนึ่งเธรดต่อ CPU core ฉันคิดว่าคุณกำลังดูสิ่งนี้ในเลเยอร์ที่ไม่ถูกต้องลองดูที่ระบบทั้งหมดและไม่ใช่เอนทิตีแต่ละอัน

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

ดังนั้นสมมติว่าต้นไม้ที่พึ่งพาของคุณเป็นป่าเถื่อนของพุ่มไม้และกับดักหมีปัญหาการออกแบบ แต่เราต้องทำงานกับสิ่งที่เรามี กรณีที่ดีที่สุดคือในแต่ละระบบแต่ละเอนทิตีไม่ได้ขึ้นอยู่กับผลลัพธ์อื่นภายในระบบนั้น ที่นี่คุณแบ่งการประมวลผลข้ามเธรดได้อย่างง่ายดาย 0-99 และ 100-199 ในสองเธรดสำหรับตัวอย่างที่มีสองคอร์และ 200 เอนทิตีที่ระบบนี้เป็นเจ้าของ

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

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

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

หากการสร้างสถาปัตยกรรมคู่ขนานนั้นง่ายหรือเป็นไปได้ด้วยข้อ จำกัด ประเภทนี้วิทยาศาสตร์คอมพิวเตอร์ก็จะไม่ดิ้นรนกับปัญหาตั้งแต่ Bletchley Park

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

ดีที่สุดที่ฉันได้รับสำหรับปัญหานี้และมันก็ไม่มีอะไรมากไปกว่าการแนะนำว่าถ้ากระทบหัวของคุณบนกำแพงอิฐแล้วก็แบ่งมันเป็นกำแพงอิฐขนาดเล็ก


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

ยังไม่มีความผิดที่ตั้งใจ: P
TravisG

@TravisG มักจะมีระบบที่ขึ้นอยู่กับระบบอื่น ๆ ตามที่ Patrick ชี้ให้เห็น เพื่อหลีกเลี่ยงความล่าช้าของเฟรมหรือเพื่อหลีกเลี่ยงการอัปเดตหลายครั้งผ่านเป็นส่วนหนึ่งของขั้นตอนลอจิคัลโซลูชันที่ยอมรับคือการทำให้ลำดับของเฟสการอัพเดตรันระบบย่อยแบบขนานเป็นไปได้ ระบบย่อยที่ใช้แนวคิด parallel_for () เหมาะอย่างยิ่งสำหรับการรวมความต้องการผ่านการอัปเดตระบบย่อยและความยืดหยุ่นสูงสุด
Naros

0

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

วิธีนี้ยังลบเงื่อนไขการแย่งชิงเนื่องจากระบบทั้งหมดจะทำงานกับสถานะเก่าที่จะไม่เปลี่ยนแปลงก่อน / หลังระบบได้ดำเนินการแล้ว


นั่นคือเคล็ดลับการคัดลอกฮีปของ John Carmack ใช่ไหม? ฉันสงสัยเกี่ยวกับเรื่องนี้ แต่ก็ยังมีปัญหาเดียวกันที่หลายเธรดอาจเขียนไปยังตำแหน่งเอาต์พุตเดียวกัน มันอาจเป็นทางออกที่ดีถ้าคุณเก็บทุกอย่าง "ซิงเกิ้ลพาส" ไว้ แต่ฉันไม่แน่ใจว่าเป็นไปได้อย่างไร
TravisG

เวลาในการตอบสนองของการแสดงผลหน้าจอจะเพิ่มขึ้น 1 เวลาของเฟรมรวมถึงการตอบสนอง GUI ซึ่งอาจมีความสำคัญสำหรับเกมแอ็คชั่น / จังหวะหรือการจัดการ GUI ที่หนักหน่วงเช่น RTS ฉันชอบมันเป็นความคิดสร้างสรรค์อย่างไรก็ตาม
Patrick Hughes

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

0

ฉันรู้ว่า 3 ซอฟต์แวร์ออกแบบการจัดการการประมวลผลข้อมูลแบบขนาน:

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

นี่คือตัวอย่างบางส่วนสำหรับแต่ละวิธีที่อาจใช้ในระบบเอนทิตี:

  1. ให้คิดว่าการCollisionSystemที่คนอ่านPositionและส่วนประกอบและควรปรับปรุงRigidBody Velocityแทนที่จะจัดการกับVelocityโดยตรงCollisionSystemแทนจะใส่ในคิวการทำงานของCollisionEvent EventSystemเหตุการณ์นี้จะถูกประมวลผลแล้วตามลำดับมีการปรับปรุงอื่น ๆ Velocityเพื่อ
  2. EntitySystemกำหนดชุดขององค์ประกอบที่จะต้องมีการอ่านและการเขียน สำหรับแต่ละองค์ประกอบEntityนั้นจะได้ล็อคการอ่านสำหรับแต่ละองค์ประกอบที่ต้องการอ่านและล็อคการเขียนสำหรับแต่ละองค์ประกอบที่ต้องการอัปเดต เช่นนี้ทุกคนEntitySystemจะสามารถอ่านส่วนประกอบพร้อมกันในขณะที่การดำเนินการอัปเดตได้รับการซิงค์
  3. การเป็นตัวอย่างของการMovementSystemที่Positionองค์ประกอบไม่เปลี่ยนรูปและมีการแก้ไขจำนวน การMovementSystemอ่านPositionและVelocityส่วนประกอบอย่างชาญฉลาดและคำนวณใหม่การPositionเพิ่มจำนวนการแก้ไขการอ่านและพยายามปรับปรุงPositionองค์ประกอบ ในกรณีที่มีการปรับเปลี่ยนพร้อมกันกรอบบ่งชี้นี้ในการปรับปรุงและจะนำกลับมาในรายการของหน่วยงานที่จะต้องมีการปรับปรุงตามที่EntityMovementSystem

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

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

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