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주기 후에는 기억에 두십시오.