---
title: "AssetBundle 은 압축 방식에 따라 여는 비용이 다르다"
description: "LZMA 번들을 LoadFromFile 로 열면 번들 전체를 풀어 메모리에 둡니다. LZ4 와 무압축은 1ms 안에 열리고, LoadFromMemory 는 바이트 배열과 사본을 같이 들며, UnityWebRequest 에 버전을 주면 디스크 캐시를 써서 메모리가 크게 줄어듭니다."
author: "유니얼"
pubDate: 2026-10-19
category: "game-dev/unity/runtime"
tags: ["Unity", "메모리"]
canonical: https://blog.unialgames.com/posts/unity-assetbundle-load/
---
<x-repo>
<script type="application/json">
{
  "repo": "SeoBYP/AssetBundleStudy",
  "text": "AssetBundle 과 Addressables 의 문서 내용을 Windows Player 에서 직접 재서 확인한 기록입니다.",
  "chips": ["Unity 6000.6.3f1", "URP 17.6.0", "Windows Player"],
  "links": [
    { "text": "측정 코드", "path": "Assets/Measure/MeasureRunner.cs" },
    { "text": "결과 AB3 · AB4 · AB5", "path": "Measure/results" }
  ],
  "note": "압축별 비교는 5회, UnityWebRequest 는 3회의 중앙값입니다. LoadFromMemory 는 한 번 잰 값입니다."
}
</script>
</x-repo>

번들 파일을 작게 만들려고 기본값인 LZMA 로 빌드했더니, 번들을 여는 순간 프레임이 튀고 메모리가 번들 크기보다 더 늘어납니다. 같은 번들을 다른 API 로 열면 또 다른 숫자가 나옵니다.

AssetBundle 을 여는 비용은 **압축 방식과 여는 API 의 조합**으로 정해집니다. LZMA 는 통째로 풀어야 하고, LZ4 와 무압축은 필요한 부분만 읽습니다. 그리고 API 마다 원본을 어디에 두는지가 다릅니다.

같은 2048×2048 텍스처 하나(GPU 에 올리기 전 12 MiB)를 세 압축으로 빌드해서 쟀습니다.

## LZMA 는 열 때 통째로 푼다

LZMA 는 번들 전체가 하나의 압축 흐름입니다. 에셋 하나를 읽으려 해도 앞에서부터 다 풀어야 합니다. 그래서 매뉴얼은 LZMA 번들을 `LoadFromFile` 로 열면 **항상 메모리 캐시를 쓴다**, 즉 풀어 놓은 사본을 메모리에 둔다고 적습니다. LZ4 는 덩어리 단위로 나눠 압축해서 필요한 덩어리만 풉니다.

<x-compare>
<script type="application/json">
{
  "label": "같은 텍스처 번들을 LoadFromFile 로 열 때",
  "tabs": [
    { "label": "LZMA", "text": "파일은 가장 작지만, 여는 순간 전체를 풀어 메모리에 둡니다.", "stats": [["파일 크기", "8.19 MiB"], ["LoadFromFile", "276.6 ms"], ["연 직후 native", "+13.31 MiB"]], "tone": "bad" },
    { "label": "LZ4", "text": "헤더만 읽고 엽니다. 데이터는 LoadAsset 할 때 필요한 덩어리만 풉니다.", "stats": [["파일 크기", "11.38 MiB"], ["LoadFromFile", "0.82 ms"], ["연 직후 native", "+1.29 MiB"]], "tone": "good" },
    { "label": "무압축", "text": "풀 것이 없습니다. 파일이 가장 큽니다.", "stats": [["파일 크기", "12.00 MiB"], ["LoadFromFile", "0.90 ms"], ["연 직후 native", "+1.04 MiB"]], "tone": "good" }
  ]
}
</script>
</x-compare>

LZMA 는 열기만 했는데 무압축 크기만큼 메모리가 늘고 277 ms 가 걸렸습니다. 한 프레임 예산의 열 배가 넘습니다. LZ4 와 무압축은 파일 크기와 상관없이 1 ms 안쪽이었고, 실제 텍스처 데이터는 `LoadAsset` 때 읽혔습니다. `LoadAsset` 시간은 세 경우 모두 7 ms 안팎으로 비슷했습니다.

**LZMA 는 받을 때 작은 대신 열 때 비쌉니다.** 디스크에서 바로 여는 번들은 LZ4 로 빌드합니다(`BuildAssetBundleOptions.ChunkBasedCompression`).

## LoadFromMemory 는 배열과 사본을 같이 든다

복호화한 번들처럼 바이트 배열로 가진 것은 `AssetBundle.LoadFromMemory` 로 엽니다. Unity 의 가이드는 이 API 의 최대 메모리가 **번들 크기의 최소 두 배** 라고 적습니다. 넘긴 관리 배열과, API 가 만든 네이티브 사본이 동시에 있기 때문입니다. LZ4 번들(11.38 MiB)로 단계마다 쟀습니다.

<x-steps>
<script type="application/json">
{
  "steps": [
    { "tag": "1", "title": "File.ReadAllBytes", "text": "관리 힙에 배열이 생깁니다. gc +11.38 MiB." },
    { "tag": "2", "title": "LoadFromMemory", "text": "배열은 그대로 있고 네이티브 사본이 생깁니다. gc +11.39, native +12.82 MiB. 번들 크기의 두 배를 씁니다." },
    { "tag": "3", "title": "배열 null 과 GC", "text": "관리 쪽은 돌아갑니다(gc +0.02). 네이티브 사본은 그대로입니다(native +13.45 MiB)." },
    { "tag": "4", "title": "Unload(true)", "text": "번들을 내려야 사본이 빠집니다(native +1.81 MiB)." }
  ]
}
</script>
</x-steps>

배열을 놓아도 네이티브 사본은 **번들을 내릴 때까지** 남습니다. 같은 파일을 `LoadFromFile` 로 열었을 때 연 직후 native 증가가 +0.26 MiB 였던 것과 비교하면, LZ4 번들인데도 번들 전체를 상주시킨 셈입니다.

## UnityWebRequest 는 버전을 줘야 디스크에 둔다

서버에서 받는 번들은 `UnityWebRequestAssetBundle.GetAssetBundle` 로 받습니다. 매뉴얼에 따르면 **버전(또는 해시)을 넘기면 디스크 캐시**에 저장하고, 넘기지 않으면 메모리 캐시를 씁니다. 디스크 캐시에 넣을 때는 LZMA 를 LZ4 로 다시 써서, 첫 로드만 느리고 그 뒤로는 빠르다고 합니다. 로컬 HTTP 서버에서 LZMA 번들을 받았습니다.

| 경우 | 받는 시간 | 연 직후 native | 같은 프로세스 2회차 |
| --- | --- | --- | --- |
| 버전 없음 | 325.8 ms | +14.49 MiB | 303.7 ms, 다시 받음 |
| 버전 있음, 캐시 비움 | 323.1 ms | +2.47 MiB | 2.67 ms, 0 B |
| 버전 있음, 캐시 있음 | 26.3 ms, 0 B | +2.18 MiB | 3.09 ms |

```csharp
// 버전 없이: 매번 받고, 풀어 놓은 사본이 메모리에 있다
UnityWebRequestAssetBundle.GetAssetBundle(url);

// 버전을 주면: 디스크 캐시에 두고 다음부터 받지 않는다
UnityWebRequestAssetBundle.GetAssetBundle(url, version: 1, crc: 0);
```

버전이 없으면 같은 프로세스에서 두 번째로 불러도 다시 받았습니다. 버전을 주면 메모리 증가가 약 5분의 1 에서 7분의 1 로 줄었고, 캐시가 찬 뒤에는 받지 않고 3 ms 안팎에 열렸습니다. 매뉴얼은 차이를 "적어도 10배" 라고 적지만 이 번들에서는 그만큼은 아니었습니다.

## 직접 확인하는 테스트 코드

같은 에셋을 세 압축으로 빌드하고 `LoadFromFile` 직후의 메모리와 시간을 찍습니다. 메모리는 `Profiler.GetTotalAllocatedMemoryLong` 의 증가분입니다. Development Build 에서 돌립니다.

```csharp
// 에디터: 압축만 다르게 세 번 빌드한다
foreach (var (name, opt) in new[]
{
    ("lzma", BuildAssetBundleOptions.None),
    ("lz4", BuildAssetBundleOptions.ChunkBasedCompression),
    ("none", BuildAssetBundleOptions.UncompressedAssetBundle),
})
{
    var builds = new[] { new AssetBundleBuild { assetBundleName = "payload", assetNames = new[] { "Assets/Payload.png" } } };
    Directory.CreateDirectory("Builds/Bundles/" + name);
    BuildPipeline.BuildAssetBundles("Builds/Bundles/" + name, builds,
        opt | BuildAssetBundleOptions.ForceRebuildAssetBundle, BuildTarget.StandaloneWindows64);
}
```

```csharp
// Player: 여는 순간의 시간과 네이티브 메모리
IEnumerator Open(string path)
{
    GC.Collect();
    yield return null;
    long before = Profiler.GetTotalAllocatedMemoryLong();

    var sw = Stopwatch.StartNew();
    AssetBundle bundle = AssetBundle.LoadFromFile(path);
    sw.Stop();

    long after = Profiler.GetTotalAllocatedMemoryLong();
    Debug.Log($"{path}: {sw.Elapsed.TotalMilliseconds:F2} ms, native +{(after - before) / 1048576.0:F2} MiB");

    bundle.Unload(true);
}
```

LZMA 만 수백 ms 와 압축 전 크기만큼의 증가가 나오면 같은 결과입니다. 압축별로 새 프로세스에서 재야 앞 실행의 캐시가 섞이지 않습니다.

## 주의할 점

### 1. 디스크에서 바로 여는 번들을 LZMA 로 두지 않는다

**안 지키면 생기는 문제.** `LoadFromFile` 이 번들 전체를 풀어 메모리에 두고, 여는 프레임이 수백 ms 멈춥니다. 번들이 클수록 둘 다 커집니다.

**이렇게 쓴다.** `StreamingAssets` 처럼 디스크에서 여는 번들은 LZ4(`ChunkBasedCompression`)로 빌드합니다. 받는 크기를 줄이고 싶어 LZMA 로 배포했다면 받을 때 버전을 줘서 디스크 캐시에 LZ4 로 다시 쓰게 합니다.

### 2. LoadFromMemory 를 기본 경로로 쓰지 않는다

**안 지키면 생기는 문제.** 여는 순간 배열과 사본으로 번들 크기의 두 배를 쓰고, 배열을 놓아도 번들 전체 크기의 사본이 번들을 내릴 때까지 남습니다. 번들을 여러 개 열어 두는 게임에서는 그대로 쌓입니다.

**이렇게 쓴다.** 파일로 있는 번들은 `LoadFromFile` 로 엽니다. 암호화처럼 꼭 메모리에서 열어야 하면 연 직후 배열을 놓고, 그 번들은 쓰는 동안만 열어 둡니다.

### 3. UnityWebRequest 로 받을 때는 버전이나 해시를 준다

**안 지키면 생기는 문제.** 버전 없이 받으면 디스크 캐시에 남지 않아 같은 번들을 매번 다시 받고, 풀어 놓은 사본이 메모리에 올라옵니다.

**이렇게 쓴다.** 번들 매니페스트의 해시나 직접 매긴 버전을 `GetAssetBundle(url, version, crc)` 에 넘깁니다. 번들이 바뀌면 그 값도 바뀌게 해서 새 번들만 받게 합니다.

## 정리

AssetBundle 을 여는 비용은 **압축 방식과 여는 API 가 원본을 어디에 두느냐**로 정해집니다.

- LZMA 를 `LoadFromFile` 로 열면 전체를 풀어 두어 277 ms, +13.31 MiB 가 들었습니다. LZ4 와 무압축은 1 ms 안쪽입니다.
- `LoadFromMemory` 는 배열과 네이티브 사본을 같이 들고, 사본은 번들을 내릴 때까지 남습니다.
- `UnityWebRequest` 는 버전을 줘야 디스크 캐시를 쓰고, 이 번들에서는 메모리 증가가 5분의 1 에서 7분의 1 로 줄었습니다.