186 lines
5.6 KiB
Markdown
186 lines
5.6 KiB
Markdown
---
|
|
id: wiki-2026-0508-object-pooling
|
|
title: Object Pooling
|
|
category: 10_Wiki/Topics
|
|
status: verified
|
|
canonical_id: self
|
|
aliases: [Object Pool, Pool Pattern, Resource Pool]
|
|
duplicate_of: none
|
|
source_trust_level: A
|
|
confidence_score: 0.9
|
|
verification_status: applied
|
|
tags: [performance, memory, gamedev, design-pattern, gc]
|
|
raw_sources: []
|
|
last_reinforced: 2026-05-10
|
|
github_commit: pending
|
|
tech_stack:
|
|
language: cpp-csharp-typescript
|
|
framework: unity-unreal
|
|
---
|
|
|
|
# Object Pooling
|
|
|
|
## 매 한 줄
|
|
> **"매 expensive-to-create object를 미리 만들어두고 재사용하여 alloc/free latency · GC pressure를 제거."**. 1990s 게임에서 bullet/particle GC spike 회피로 시작. 2026 현재 Unity `ObjectPool<T>`, Unreal pooling subsystem, .NET `ArrayPool<T>`, Netty `Recycler` 등 plat-form 표준.
|
|
|
|
## 매 핵심
|
|
|
|
### 매 동작 원리
|
|
1. Pool이 N개 instance 미리 alloc.
|
|
2. `acquire()` → free list에서 pop.
|
|
3. 사용 후 `release()` → reset 후 free list에 return.
|
|
4. Pool 부족 시 grow (또는 block / fail).
|
|
|
|
### 매 적합한 대상
|
|
- 매 alloc cost 큼 (network connection, thread, GPU buffer).
|
|
- 매 빈번한 short-lived alloc (bullet, particle, packet).
|
|
- 매 size predictable.
|
|
- 매 reset 가능 (no permanent dirty state).
|
|
|
|
### 매 응용
|
|
1. Game — bullets, enemies, particles, audio sources.
|
|
2. Networking — DB connection pool (HikariCP), HTTP client (Apache).
|
|
3. Rendering — command buffer pool, descriptor set pool (Vulkan).
|
|
|
|
## 💻 패턴
|
|
|
|
### C# Unity ObjectPool (2026 standard)
|
|
```csharp
|
|
using UnityEngine.Pool;
|
|
|
|
public class BulletSpawner : MonoBehaviour {
|
|
[SerializeField] Bullet prefab;
|
|
IObjectPool<Bullet> pool;
|
|
|
|
void Awake() {
|
|
pool = new ObjectPool<Bullet>(
|
|
createFunc: () => Instantiate(prefab),
|
|
actionOnGet: b => b.gameObject.SetActive(true),
|
|
actionOnRelease: b => b.gameObject.SetActive(false),
|
|
actionOnDestroy: b => Destroy(b.gameObject),
|
|
collectionCheck: true,
|
|
defaultCapacity: 64,
|
|
maxSize: 512);
|
|
}
|
|
public void Fire(Vector3 pos, Vector3 dir) {
|
|
var b = pool.Get();
|
|
b.Init(pos, dir, onExpire: () => pool.Release(b));
|
|
}
|
|
}
|
|
```
|
|
|
|
### C++ template pool with free-list
|
|
```cpp
|
|
template<typename T, size_t N>
|
|
class ObjectPool {
|
|
alignas(T) std::byte storage[N * sizeof(T)];
|
|
std::array<T*, N> free_list;
|
|
size_t free_top = N;
|
|
public:
|
|
ObjectPool() {
|
|
for (size_t i = 0; i < N; ++i)
|
|
free_list[i] = reinterpret_cast<T*>(storage + i * sizeof(T));
|
|
}
|
|
template<typename... Args>
|
|
T* acquire(Args&&... args) {
|
|
if (free_top == 0) return nullptr;
|
|
T* p = free_list[--free_top];
|
|
return new (p) T(std::forward<Args>(args)...);
|
|
}
|
|
void release(T* p) {
|
|
p->~T();
|
|
free_list[free_top++] = p;
|
|
}
|
|
};
|
|
```
|
|
|
|
### TypeScript pool for Web/Node
|
|
```typescript
|
|
class ObjectPool<T> {
|
|
private free: T[] = [];
|
|
constructor(
|
|
private factory: () => T,
|
|
private reset: (t: T) => void,
|
|
initial = 0,
|
|
) {
|
|
for (let i = 0; i < initial; i++) this.free.push(factory());
|
|
}
|
|
acquire(): T {
|
|
return this.free.pop() ?? this.factory();
|
|
}
|
|
release(t: T) {
|
|
this.reset(t);
|
|
this.free.push(t);
|
|
}
|
|
}
|
|
// Vector2 pool example
|
|
const v2Pool = new ObjectPool<{x:number,y:number}>(
|
|
() => ({x:0,y:0}),
|
|
v => { v.x = 0; v.y = 0; },
|
|
256);
|
|
```
|
|
|
|
### .NET ArrayPool — GC-friendly buffer reuse
|
|
```csharp
|
|
byte[] buf = ArrayPool<byte>.Shared.Rent(4096);
|
|
try {
|
|
int n = await stream.ReadAsync(buf, 0, buf.Length);
|
|
Process(buf.AsSpan(0, n));
|
|
} finally {
|
|
ArrayPool<byte>.Shared.Return(buf, clearArray: true);
|
|
}
|
|
```
|
|
|
|
### Connection pool (HikariCP idiom in Java)
|
|
```java
|
|
HikariConfig cfg = new HikariConfig();
|
|
cfg.setJdbcUrl("jdbc:postgres://...");
|
|
cfg.setMaximumPoolSize(20);
|
|
cfg.setIdleTimeout(30_000);
|
|
HikariDataSource ds = new HikariDataSource(cfg);
|
|
|
|
try (Connection c = ds.getConnection(); // ← acquire
|
|
PreparedStatement s = c.prepareStatement("...")) {
|
|
s.executeQuery();
|
|
} // ← release on close()
|
|
```
|
|
|
|
## 매 결정 기준
|
|
| 상황 | Approach |
|
|
|---|---|
|
|
| GC spike from short-lived alloc | object pool |
|
|
| Network/DB resource | connection pool |
|
|
| Render commands | per-frame pool, reset on frame end |
|
|
| Variable size buffer | ArrayPool / segregated pool |
|
|
| Single-threaded game | simple stack pool |
|
|
| Multi-threaded | ConcurrentBag / lock-free pool |
|
|
|
|
**기본값**: 매 platform 제공 pool 사용 (Unity ObjectPool, ArrayPool, HikariCP) — 매 직접 구현 회피.
|
|
|
|
## 🔗 Graph
|
|
- 부모: [[Memory_Management]] · [[Design_Patterns]]
|
|
- 변형: [[Connection_Pool]] · [[Thread_Pool]] · [[Free_List]]
|
|
- 응용: [[Game_Loop]] · [[Particle_Systems]] · [[Garbage_Collection]]
|
|
- Adjacent: [[Old_Space]] · [[Generational_Hypothesis]] · [[Memory_Leaks]]
|
|
|
|
## 🤖 LLM 활용
|
|
**언제**: GC pressure visible (frame spike), expensive resource creation, predictable churn rate.
|
|
**언제 X**: long-lived object, unique-per-instance state, alloc rate 낮음 — 매 premature opt.
|
|
|
|
## ❌ 안티패턴
|
|
- **Forget release**: 매 leak — `using`/`try-finally`/RAII.
|
|
- **Use after release**: 매 use-after-free 등가 — generational handle 사용.
|
|
- **Dirty state carry-over**: 매 reset 누락 — bug.
|
|
- **Unbounded growth**: 매 maxSize 없음 → OOM.
|
|
- **Premature pooling**: 매 GC가 충분히 빠른 경우 — measure first.
|
|
|
|
## 🧪 검증 / 중복
|
|
- Verified (Game Programming Patterns by Nystrom 2014, Unity docs 2026, .NET ArrayPool source, HikariCP).
|
|
- 신뢰도 A.
|
|
|
|
## 🕓 Changelog
|
|
| 날짜 | 변경 |
|
|
|---|---|
|
|
| 2026-05-08 | Phase 1 |
|
|
| 2026-05-10 | Manual cleanup — Object pooling pattern (4 lang impls + decision matrix) |
|