memory-management について
memory-management は、メモリ予算、割り当ての削減、プロファイリング、アセットストリーミングを扱うゲーム開発向けスキルです。モバイルを含むプラットフォームで、メモリ使用量の最適化やフレームのカクつき軽減に取り組む開発者を対象とします。
主な機能
- 01プラットフォーム別メモリ予算の指針
- 02オブジェクトプールのパターン
- 03ガベージコレクションの最適化
- 04メモリプロファイリング手順
- 05アセットストリーミング戦略
- 06メモリ最適化チェックリスト
活用シーン
- 01実行中のメモリ増加の抑制
- 02メモリ割り当てによるカクつきの軽減
- 03モバイルゲームのメモリ使用計画
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ pluginagentmarketplace/ custom-plugin-game-developer --skill memory-managementインストール後、Claude Code で/memory-managementを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
assets/memory_config.yamlreferences/MEMORY_GUIDE.mdscripts/memory_analyzer.py詳細
メモリ管理
メモリアーキテクチャ
┌─────────────────────────────────────────────────────────────┐
│ GAME MEMORY LAYOUT │
├─────────────────────────────────────────────────────────────┤
│ STACK (Fast, Auto-managed): │
│ ├─ Local variables │
│ ├─ Function parameters │
│ └─ Return addresses │
│ │
│ HEAP (Slower, Manual/GC-managed): │
│ ├─ Dynamic allocations (new/malloc) │
│ ├─ Game objects │
│ └─ Asset data │
│ │
│ STATIC (Fixed at compile time): │
│ ├─ Global variables │
│ ├─ Static class members │
│ └─ Constant data │
│ │
│ VRAM (GPU Memory): │
│ ├─ Textures │
│ ├─ Meshes │
│ └─ Render targets │
└─────────────────────────────────────────────────────────────┘
プラットフォームごとのメモリ予算
MEMORY BUDGET GUIDELINES:
┌─────────────────────────────────────────────────────────────┐
│ PLATFORM │ TOTAL │ GAME LOGIC │ ASSETS │ BUFFER │
├────────────────┼──────────┼────────────┼──────────┼────────┤
│ Mobile Low │ 512 MB │ 50 MB │ 350 MB │ 112 MB │
│ Mobile High │ 2 GB │ 200 MB │ 1.5 GB │ 300 MB │
│ Console │ 8 GB │ 500 MB │ 6 GB │ 1.5 GB │
│ PC Min │ 4 GB │ 300 MB │ 3 GB │ 700 MB │
│ PC High │ 16 GB │ 1 GB │ 12 GB │ 3 GB │
│ VR │ 8 GB │ 400 MB │ 6 GB │ 1.6 GB │
└────────────────┴──────────┴────────────┴──────────┴────────┘
VRAM BUDGETS:
┌─────────────────────────────────────────────────────────────┐
│ Mobile: 512 MB - 1 GB │
│ Console: 8-12 GB (shared with RAM) │
│ PC Low: 2-4 GB │
│ PC High: 8-16 GB │
└─────────────────────────────────────────────────────────────┘
オブジェクトプーリング
// ✅ Production-Ready: Generic Object Pool
public class ObjectPool<T> where T : class
{
private readonly Stack<T> _pool;
private readonly Func<T> _createFunc;
private readonly Action<T> _onGet;
private readonly Action<T> _onReturn;
private readonly int _maxSize;
public int CountActive { get; private set; }
public int CountInPool => _pool.Count;
public ObjectPool(
Func<T> createFunc,
Action<T> onGet = null,
Action<T> onReturn = null,
int initialSize = 10,
int maxSize = 100)
{
_createFunc = createFunc;
_onGet = onGet;
_onReturn = onReturn;
_maxSize = maxSize;
_pool = new Stack<T>(initialSize);
// Pre-warm pool
for (int i = 0; i < initialSize; i++)
{
_pool.Push(_createFunc());
}
}
public T Get()
{
T item = _pool.Count > 0 ? _pool.Pop() : _createFunc();
_onGet?.Invoke(item);
CountActive++;
return item;
}
public void Return(T item)
{
if (item == null) return;
_onReturn?.Invoke(item);
CountActive--;
if (_pool.Count < _maxSize)
{
_pool.Push(item);
}
// If pool is full, let GC collect the item
}
public void Clear()
{
_pool.Clear();
CountActive = 0;
}
}
// Usage Example: Bullet Pool
public class BulletManager : MonoBehaviour
{
private ObjectPool<Bullet> _bulletPool;
void Awake()
{
_bulletPool = new ObjectPool<Bullet>(
createFunc: () => Instantiate(bulletPrefab).GetComponent<Bullet>(),
onGet: bullet => bullet.gameObject.SetActive(true),
onReturn: bullet => bullet.gameObject.SetActive(false),
initialSize: 50,
maxSize: 200
);
}
public Bullet SpawnBullet(Vector3 position, Vector3 direction)
{
var bullet = _bulletPool.Get();
bullet.Initialize(position, direction);
bullet.OnDestroyed += () => _bulletPool.Return(bullet);
return bullet;
}
}
ガベージコレクションの最適化
GC SPIKE PREVENTION:
┌─────────────────────────────────────────────────────────────┐
│ AVOID IN UPDATE/HOT PATHS: │
│ ✗ new object() │
│ ✗ string concatenation ("a" + "b") │
│ ✗ LINQ queries (ToList(), Where(), etc.) │
│ ✗ Boxing value types │
│ ✗ Closures/lambdas capturing variables │
│ ✗ foreach on non-struct enumerators │
│ │
│ DO INSTEAD: │
│ ✓ Object pooling │
│ ✓ StringBuilder for strings │
│ ✓ Pre-allocated collections │
│ ✓ Struct-based data │
│ ✓ Cache delegates │
│ ✓ for loops with index │
└─────────────────────────────────────────────────────────────┘
// ✅ Production-Ready: Allocation-Free Patterns
public class AllocationFreePatterns
{
// ❌ BAD: Allocates every frame
void BadUpdate()
{
string status = "Health: " + health + "/" + maxHealth; // Allocates
var enemies = allEntities.Where(e => e.IsEnemy).ToList(); // Allocates
foreach (var enemy in enemies) { } // May allocate enumerator
}
// ✓ GOOD: Zero allocations
private StringBuilder _sb = new StringBuilder(64);
private List<Entity> _enemyCache = new List<Entity>(100);
void GoodUpdate()
{
// Reuse StringBuilder
_sb.Clear();
_sb.Append("Health: ").Append(health).Append("/").Append(maxHealth);
// Reuse list, avoid LINQ
_enemyCache.Clear();
for (int i = 0; i < allEntities.Count; i++)
{
if (allEntities[i].IsEnemy)
_enemyCache.Add(allEntities[i]);
}
// Index-based iteration
for (int i = 0; i < _enemyCache.Count; i++)
{
ProcessEnemy(_enemyCache[i]);
}
}
}
メモリプロファイリング
PROFILING WORKFLOW:
┌─────────────────────────────────────────────────────────────┐
│ 1. BASELINE: Measure memory at known state │
│ → Startup, menu, gameplay, level transition │
│ │
│ 2. MONITOR: Track over time │
│ → Memory growth indicates leaks │
│ → Spikes indicate heavy allocations │
│ │
│ 3. SNAPSHOT: Capture heap at suspicious moments │
│ → Compare snapshots to find what's growing │
│ │
│ 4. TRACE: Find allocation source │
│ → Stack trace shows where allocation happened │
│ → Identify hot paths │
│ │
│ 5. FIX: Apply optimization │
│ → Pool, cache, or eliminate allocation │
│ │
│ 6. VERIFY: Confirm fix worked │
│ → Re-profile same scenario │
└─────────────────────────────────────────────────────────────┘
PROFILING TOOLS:
┌─────────────────────────────────────────────────────────────┐
│ Unity: │
│ • Memory Profiler (Package) │
│ • Profiler window (Memory section) │
│ • Deep Profile mode │
│ │
│ Unreal: │
│ • Unreal Insights │
│ • memreport command │
│ • stat memory │
│ │
│ Native: │
│ • Valgrind (Linux) │
│ • Instruments (macOS) │
│ • Visual Studio Diagnostic Tools │
└─────────────────────────────────────────────────────────────┘
アセットストリーミング
STREAMING STRATEGY:
┌─────────────────────────────────────────────────────────────┐
│ STREAMING ZONES │
│ │
│ [Loaded] [Loading...] [Unloaded] [Unloaded] │
│ ▲ ▲ │ │ │
│ │ │ │ │ │
│ ──────●───────────●────────────●────────────●────── │
│ Player Trigger Far Zone Very Far │
│ Position │
│ │
│ STREAMING RULES: │
│ • Load when player approaches (predictive) │
│ • Unload when player far away + timeout │
│ • Priority: Visible > Nearby > Background │
│ • Async loading to avoid hitches │
└─────────────────────────────────────────────────────────────┘
// ✅ Production-Ready: Asset Streaming Manager
public class StreamingManager : MonoBehaviour
{
[SerializeField] private float loadDistance = 50f;
[SerializeField] private float unloadDistance = 100f;
[SerializeField] private float unloadDelay = 5f;
private Dictionary<string, StreamingZone> _zones = new();
private Queue<AsyncOperation> _loadQueue = new();
void Update()
{
Vector3 playerPos = Player.Position;
foreach (var zone in _zones.Values)
{
float distance = Vector3.Distance(playerPos, zone.Center);
if (distance < loadDistance && !zone.IsLoaded)
{
StartCoroutine(LoadZoneAsync(zone));
}
else if (distance > unloadDistance && zone.IsLoaded)
{
StartCoroutine(UnloadZoneDelayed(zone, unloadDelay));
}
}
}
private IEnumerator LoadZoneAsync(StreamingZone zone)
{
zone.State = ZoneState.Loading;
var operation = SceneManager.LoadSceneAsync(zone.SceneName, LoadSceneMode.Additive);
operation.allowSceneActivation = false;
while (operation.progress < 0.9f)
{
yield return null;
}
operation.allowSceneActivation = true;
zone.State = ZoneState.Loaded;
}
}
🔧 トラブルシューティング
┌─────────────────────────────────────────────────────────────┐
│ PROBLEM: Memory keeps growing over time (leak) │
├─────────────────────────────────────────────────────────────┤
│ DEBUG: │
│ → Take memory snapshots at intervals │
│ → Compare snapshots to find growing objects │
│ → Check for missing unsubscribes (events) │
│ → Look for collections that never clear │
├─────────────────────────────────────────────────────────────┤
│ SOLUTIONS: │
│ → Implement IDisposable pattern │
│ → Use weak references for caches │
│ → Clear collections when changing scenes │
│ → Unsubscribe from events in OnDestroy │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ PROBLEM: GC causing frame spikes │
├─────────────────────────────────────────────────────────────┤
│ DEBUG: │
│ → Profile with GC.Alloc markers │
│ → Look for allocations in Update/FixedUpdate │
│ → Check for string operations in hot paths │
├─────────────────────────────────────────────────────────────┤
│ SOLUTIONS: │
│ → Implement object pooling │
│ → Use structs instead of classes where possible │
│ → Pre-allocate collections with known capacity │
│ → Spread GC across frames (incremental GC) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ PROBLEM: Out of memory on mobile │
├─────────────────────────────────────────────────────────────┤
│ DEBUG: │
│ → Check texture memory usage │
│ → Look for duplicate assets │
│ → Monitor during level transitions │
├─────────────────────────────────────────────────────────────┤
│ SOLUTIONS: │
│ → Reduce texture resolution │
│ → Implement aggressive streaming │
│ → Unload unused assets (Resources.UnloadUnusedAssets) │
│ → Use compressed texture formats │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ PROBLEM: Hitches during level loading │
├─────────────────────────────────────────────────────────────┤
│ SOLUTIONS: │
│ → Use async loading (LoadSceneAsync) │
│ → Spread instantiation across frames │
│ → Pre-warm object pools during loading screen │
│ → Stream assets instead of loading all at once │
└─────────────────────────────────────────────────────────────┘
メモリ最適化チェックリスト
| 領域 | テクニック | 効果 | 工数 |
|---|---|---|---|
| オブジェクト | オブジェクトプーリング | 高 | 中 |
| 文字列 | StringBuilder | 中 | 低 |
| コレクション | 事前割り当て | 中 | 低 |
| アセット | ストリーミング | 高 | 高 |
| テクスチャ | 圧縮 | 高 | 低 |
| GC | インクリメンタル GC | 中 | 低 |
| イベント | 弱参照 | 低 | 中 |
このスキルを使用する場面:メモリ使用量の最適化、フレームのカクつきの軽減、またはモバイルプラットフォームへの対応を行う場合。
コメント
Markdown 対応