관리되는 언어로 파티클 풀을 사용하는 것이 가치가 있습니까?


10

나는 자바에서 내 파티클 시스템의 오브젝트 풀을 구현하는 거라고, 그때 발견 위키 백과에. 다시 말하면, 객체 풀은 Java 및 C #과 같은 관리되는 언어에서 사용할 가치가 없다고 말합니다. C ++과 같은 관리되지 않는 언어의 수백에 비해 할당이 수십 번만 수행되기 때문입니다.

그러나 우리 모두가 알듯이 모든 지시는 게임 성능을 떨어 뜨릴 수 있습니다. 예를 들어 MMO에있는 클라이언트 풀 : 클라이언트가 풀에 너무 빨리 들어가거나 나가지 않습니다. 그러나 입자는 1 초에 수십 번 갱신 될 수 있습니다.

문제는 관리 언어에서 파티클 (특히, 빨리 죽고 빨리 재생되는 오브젝트)에 객체 풀을 사용하는 것이 가치가 있습니까?

답변:


14

그렇습니다.

할당 시간 만이 유일한 요인은 아닙니다. 할당은 가비지 수집 패스를 유도하는 등의 부작용을 일으킬 수 있으며, 이는 성능에 부정적인 영향을 줄뿐만 아니라 예기치 않은 성능에도 영향을 줄 수 있습니다. 이에 대한 구체적인 내용은 언어 및 플랫폼 선택에 따라 다릅니다.

풀링은 또한 일반적으로 풀의 객체를 모두 인접한 배열로 유지함으로써 풀의 객체에 대한 참조의 지역성을 향상시킵니다. 이는 반복의 다음 오브젝트가 이미 데이터 캐시에있는 경향이 있기 때문에 풀의 컨텐츠 (또는 적어도 라이브 부분)를 반복하는 동안 성능을 향상시킬 수 있습니다.

가장 안쪽의 게임 루프에서 할당을 피하려고하는 기존의 지혜는 관리되는 언어 (특히 XNA를 사용할 때 360 등)에서도 여전히 적용됩니다. 그 이유는 약간 다릅니다.


+1 그러나 구조체를 사용할 때 가치가 있는지 여부는 다루지 않았습니다. 기본적으로 (풀링 값 유형이 아무것도 얻지 못함) 대신 대신 단일 (또는 가능한 집합) 배열이 있어야합니다.
Jonathan Dickinson

2
OP가 Java를 사용하여 언급 한 이후 구조체에 대해서는 언급하지 않았으며 값 유형 / 구조가 해당 언어로 작동하는 방식에 익숙하지 않습니다.

Java에는 구조체가 없으며 클래스 만 항상 힙에 있습니다.
Brendan Long

1

Java의 경우 객체를 풀링하는 것이 그다지 도움이되지 않습니다. * 여전히 객체의 첫 번째 GC주기가 메모리에서 객체를 다시 섞어서 "Eden"공간 밖으로 이동하여 프로세스에서 공간적 지역성을 잃을 수 있기 때문입니다.

  • 스레드를 파괴하고 작성하는 데 비용이 많이 드는 복잡한 자원을 풀링하는 것이 모든 언어에서 항상 유용합니다. 그것들은 그것들을 생성하고 파괴하는 비용이 자원에 대한 객체 핸들과 관련된 메모리와 거의 관련이 없기 때문에 풀링 할 가치가 있습니다. 그러나 입자는이 범주에 맞지 않습니다.

Java는 객체를 Eden 공간에 빠르게 할당 할 때 순차 할당자를 사용하여 빠른 버스트 할당을 제공합니다. 순차 할당 전략은 초고속보다 빠릅니다.malloc 이미 순차적 인 방식으로 이미 할당 된 메모리를 풀링하기 때문에 C 만 개별 메모리 청크를 해제 할 수 없다는 단점이 있습니다. 예를 들어 무언가를 제거 할 필요가없는 데이터 구조에 대해 초고속으로 할당하고 모든 것을 추가 한 다음 사용하고 나중에 모든 것을 버릴 경우 C에서 유용한 트릭입니다.

개별 객체를 해제 할 수 없다는 단점으로 인해 첫 번째주기 후에 Java GC는 메모리를 허용하는 느리고보다 범용적인 메모리 할당자를 사용하여 Eden 공간에서 할당 된 모든 메모리를 새로운 메모리 영역으로 복사합니다. 다른 스레드에서 개별 청크로 해제됩니다. 그런 다음 현재 복사되어 메모리의 다른 위치에있는 개별 객체를 방해하지 않고 Eden 공간에 할당 된 메모리를 전체적으로 버릴 수 있습니다. 첫 번째 GC주기 후에 객체가 메모리에서 조각화 될 수 있습니다.

첫 번째 GC주기 후에 객체가 조각화 될 수 있기 때문에 주로 메모리 액세스 패턴 (참조의 지역성)을 개선하고 할당 / 할당 해제 오버 헤드를 줄이기 위해 객체 풀링의 이점이 크게 손실됩니다. 일반적으로 항상 새로운 입자를 할당하여 에덴 공간에서 신선하고 "구식"이되어 메모리에 흩어지기 전에 사용함으로써 더 나은 참조 지역을 얻을 수 있습니다. 그러나 Java에서 C와 비슷한 성능을 얻는 것과 같이 매우 유용 할 수있는 것은 입자에 객체를 사용하지 않고 일반 기본 데이터를 풀링하는 것입니다. 간단한 예를 들면 다음과 같습니다.

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 담당자는 또한 패딩, 반사 / 동적 디스패치에 대한 객체 메타 데이터에 대해 걱정할 필요가 없으므로 핫 필드를 콜드 필드에서 분리합니다 (예 : 데이터와 관련이있는 것은 아닙니다) 물리 통과시 입자의 색상과 같은 필드는 캐시 라인에로드하여 사용하지 않고 제거하는 것이 낭비입니다).

코드 작업을보다 쉽게하려면 float 배열, 정수 배열 및 boolean 배열을 저장하는 크기 조정이 가능한 기본 컨테이너를 작성하는 것이 좋습니다. 다시 말하지만 ArrayList연속적인 기본 데이터가 아닌 GC 관리 객체가 필요하기 때문에 제네릭을 사용할 수 없으며 (최소 마지막으로 확인한 이후로) 여기에서 사용할 수 없습니다 . int예를 들어 Integer에덴 (Eden) 공간을 떠난 후 반드시 연속적이지 않은 GC 관리 형 배열이 아닌 연속 배열을 사용하고 싶습니다 .

프리미티브 유형의 배열을 사용하면 항상 인접성이 보장되므로 매우 바람직한 참조 위치 (순차적 입자 처리를 위해 차이가 생길 수 있음)와 객체 풀링이 제공하는 모든 이점을 얻을 수 있습니다. 객체 배열을 사용하면 Eden 공간에 한 번에 모든 객체를 할당 한 것으로 가정하지만 연속적으로 객체를 가리 키기 시작하는 포인터 배열과 다소 유사하지만 GC주기 후에는 기억에 두십시오.


1
이것은 문제에 대한 훌륭한 글쓰기이며, 5 년의 자바 코딩 후에 분명히 볼 수 있습니다. Java GC는 확실히 바보가 아니며 게임 프로그래밍을 위해 만들어지지 않았으며 (데이터 지역 및 물건을 실제로 신경 쓰지 않기 때문에) 우리는 원하는대로 재생하는 것이 좋습니다 : P
Gustavo Maciel
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.