มันคุ้มค่าหรือไม่ที่จะใช้สระอนุภาคในภาษาที่มีการจัดการ?


10

ฉันจะใช้พูลวัตถุสำหรับระบบอนุภาคของฉันใน Java จากนั้นฉันพบสิ่งนี้ใน Wikipedia ในการใช้ถ้อยคำใหม่กล่าวว่ากลุ่มวัตถุไม่คุ้มค่าที่จะใช้ในภาษาที่ได้รับการจัดการเช่น Java และ C # เนื่องจากการจัดสรรใช้เวลาเพียงสิบการดำเนินการเมื่อเทียบกับหลายร้อยในภาษาที่ไม่มีการจัดการเช่น C ++

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

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

คำตอบ:


14

ใช่แล้ว.

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

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

ภูมิปัญญาดั้งเดิมของการพยายามหลีกเลี่ยงการจัดสรรใด ๆ ในลูปเกมในสุดของคุณยังคงใช้แม้ในภาษาที่มีการจัดการ (โดยเฉพาะอย่างยิ่งในตัวอย่างเช่น 360 เมื่อใช้ XNA) เหตุผลของมันแตกต่างกันเล็กน้อย


+1 แต่คุณไม่ได้สัมผัสว่ามันคุ้มค่าหรือไม่เมื่อใช้ structs: โดยพื้นฐานแล้วไม่ใช่ (การรวมประเภทของค่าไม่ประสบความสำเร็จ) - คุณควรมีอาร์เรย์ (หรือชุดของชุด) หนึ่งชุดเพื่อจัดการแทน
Jonathan Dickinson

2
ฉันไม่ได้สัมผัสกับสิ่งที่เป็นโครงสร้างเนื่องจาก OP กล่าวถึงโดยใช้ Java และฉันไม่คุ้นเคยกับการทำงานของประเภท / โครงสร้างค่าในภาษานั้น

ไม่มี structs ใน Java มีเพียงคลาส (เสมอบน heap)
Brendan Long

1

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

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

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

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

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

class Particle
{
    public float x;
    public float y;
    public boolean alive;
}

ทำสิ่งที่ชอบ:

class Particles
{
    // X positions of all particles. Resize on demand using
    // 'java.util.Arrays.copyOf'. We do not use an ArrayList
    // since we want to work directly with contiguously arranged
    // primitive types for optimal memory access patterns instead 
    // of objects managed by GC.
    public float x[];

    // Y positions of all particles.
    public float y[];

    // Alive/dead status of all particles.
    public bool alive[];
}

ทีนี้เพื่อนำหน่วยความจำสำหรับอนุภาคที่มีอยู่กลับมาใช้ใหม่คุณสามารถทำได้:

class Particles
{
    // X positions of all particles.
    public float x[];

    // Y positions of all particles.
    public float y[];

    // Alive/dead status of all particles.
    public bool alive[];

    // Next free position of all particles.
    public int next_free[];

    // Index to first free particle available to reclaim
    // for insertion. A value of -1 means the list is empty.
    public int first_free;
}

ตอนนี้เมื่อnthอนุภาคตายเพื่อให้สามารถนำกลับมาใช้ใหม่ได้ให้กดไปที่รายการอิสระดังนี้:

alive[n] = false;
next_free[n] = first_free;
first_free = n;

เมื่อเพิ่มอนุภาคใหม่ดูว่าคุณสามารถป๊อปดัชนีจากรายการฟรี:

if (first_free != -1)
{
     int index = first_free;

     // Pop the particle from the free list.
     first_free = next_free[first_free];

     // Overwrite the particle data:
     x[index] = px;
     y[index] = py;
     alive[index] = true;
     next_free[index] = -1;
}
else
{
     // If there are no particles in the free list
     // to overwrite, add new particle data to the arrays,
     // resizing them if needed.
}

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

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

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


1
นี่เป็นบทความที่ดีเกี่ยวกับเรื่องนี้และหลังจากเขียนโปรแกรมจาวา 5 ปีฉันสามารถเห็นได้อย่างชัดเจน Java GC แน่นอนว่าไม่โง่ไม่ได้ถูกสร้างขึ้นมาเพื่อการเขียนโปรแกรมเกม (เนื่องจากมันไม่สนใจข้อมูลท้องถิ่นและสิ่งของ) ดังนั้นเราจึงควรเล่นตามความพอใจ: P
Gustavo Maciel
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.