CS โ hash rehash followup
๐ 15-2 โ ํด์ ์๋ฃ๊ตฌ์กฐ์ capacityยทload factorยทrehash
15_1_vector_vs_hash_concepts.md์ ํ์ํธ. ํด์ ํ ์ด๋ธ์ด ๋ด๋ถ์ ์ผ๋ก ์ด๋ป๊ฒ ํฌ๊ธฐ๋ฅผ ๊ด๋ฆฌํ๊ณ , ์ธ์ ๋น์ผ rehash๊ฐ ์ผ์ด๋๋์ง ์ ๋ฆฌ.
1. capacity (๋ฒํท ์) โ ํด์ ํ ์ด๋ธ์ ๋ด๋ถ ๋ฐฐ์ด ํฌ๊ธฐ
ํด์ ํ
์ด๋ธ์ ๋ด๋ถ์ ๋ฒํท ๋ฐฐ์ด(bucket array) ์ ๊ฐ์ง๊ณ , ๊ฐ ํค๋ฅผ hash(key) % bucket_count ๋ก ๋ฒํท์ ๋ถ๋ฐฐํฉ๋๋ค.
1
2
3
4
5
6
7
bucket_count = 8 ์ผ ๋
idx: 0 1 2 3 4 5 6 7
[ ] [โ] [ ] [โโโ][ ] [โ] [ ] [ ]
โ โโ โ
key1 key2 key3 key4
(์ถฉ๋ โ ์ฒด์ธ)
size()โ ํ์ฌ ์ ์ฅ๋ ํค-๊ฐ ์ ๊ฐ์bucket_count()โ ๋ด๋ถ ๋ฒํท ๋ฐฐ์ด ํฌ๊ธฐ (capacity ์ญํ )
vector::capacity()๊ฐ โ์ฌํ ๋น ์์ด ๋ค์ด๊ฐ ์ ์๋ ์์ ์โ์๋ค๋ฉด, ํด์์ bucket_count()๋ โrehash ์์ด ๋ถ๋ฐฐํ ์ ์๋ ์ฌ๋กฏ ์โ ์
๋๋ค.
2. load factor โ ํ๊ท ์ถฉ๋ ์ ๋์ ์งํ
1
load_factor = size / bucket_count
load_factor = 0.5โ ํ๊ท ๋ ๋ฒํท์ ํ ์์ (์ถฉ๋ ๊ฑฐ์ ์์)load_factor = 1.0โ ํ๊ท ํ ๋ฒํท์ ํ ์์load_factor = 2.0โ ํ๊ท ํ ๋ฒํท์ ๋ ์์ (์ถฉ๋ ๋น๋ฒ)
load factor๊ฐ ๋์์๋ก ์ถฉ๋์ด ๋์ด๋ ํ ๋ฒํท์ ์ฒด์ธ์ด ๊ธธ์ด์ง๊ณ , find/insert๊ฐ ํ๊ท O(1)์์ ๋ฉ์ด์ง๋๋ค. ๊ทธ๋์ ํ์ค์ ์๊ณ๊ฐ์ ๋๊ณ ์๋์ผ๋ก bucket์ ๋๋ฆฝ๋๋ค.
1
2
3
std::unordered_map<int, int> m;
std::cout << m.max_load_factor(); // 1.0 (C++ ํ์ค ๊ธฐ๋ณธ)
m.max_load_factor(0.5); // ๋ ๋นก๋นกํ๊ฒ (๋ฉ๋ชจ๋ฆฌ โ, ์ถฉ๋ โ)
max_load_factor ๋ฅผ ์๊ฒ ์ก์ผ๋ฉด ์ถฉ๋์ด ์ค์ง๋ง ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ์ฆ๊ฐ (๊ฐ์ size์ ๋ ํฐ bucket_count). ํธ๋ ์ด๋์คํ.
3. rehash โ ์๊ณ๊ฐ ์ด๊ณผ ์ ์๋ ์ฌํด์ฑ
load_factor > max_load_factor ๊ฐ ๋๋ ์๊ฐ rehash๊ฐ ์๋ ํธ๋ฆฌ๊ฑฐ๋ฉ๋๋ค.
1
2
3
4
5
6
7
8
9
10
[insert ์์ ]
โ
load_factor ๊ณ์ฐ
โ
> max_load_factor?
โ NO โ ๊ทธ๋ฅ ์ฝ์
(ํ๊ท O(1))
โ YES โ rehash ๋ฐ๋
โ bucket_count๋ฅผ ๋ณดํต 2๋ฐฐ๋ก ์ฆ๊ฐ
โ ๋ชจ๋ ์์๋ฅผ ์ bucket_count์ ์ฌํด์ฑ
โ ๋น์ฉ: O(N) โ ํ ๋ฒ์ ๋ชจ๋ ์์ ์ด๋
rehash๋ ๋จ๋ฐ์ ์ผ๋ก O(N) ๋น์ฉ. amortized๋ก๋ ์ฌ์ ํ O(1)์ด์ง๋ง ๊ทธ ์๊ฐ ํ๋ ์์ด ํ ๋ฒ ๊ธธ์ด์ง.
1
2
3
// ์๋ rehash
m.rehash(N); // bucket_count๋ฅผ N ์ด์์ผ๋ก ์ค์
m.reserve(N); // N๊ฐ ์์ ๋ค์ด๊ฐ๋ rehash ์๊ฒ โ ๊ถ์ฅ ํจํด
reserve(N)์ ๋ด๋ถ์ ์ผ๋ก rehash(ceil(N / max_load_factor)) ํธ์ถ. ์ฌ์ฉ์๊ฐ ์์ฃผ ์ฐ๋ API.
4. rehash ๋น์ฉ โ ๊ฒ์ ๋ฃจํ์์ ์ํ
1
2
3
4
5
6
7
// ๊ฒ์ ๋ฃจํ (60fps = 16.6ms ํ๋ ์ ์์ฐ)
std::unordered_map<FName, FEnemyData> Enemies;
void Tick(float Dt) {
SpawnEnemy(...); // ๋งค ํ๋ ์ enemies ์ถ๊ฐ
// ...
}
์์ด๋ค๊ฐ ์ด๋ ํ๋ ์์์ rehash ํธ๋ฆฌ๊ฑฐ๋๋ฉด ๊ทธ ํ๋ ์๋ง ๊ฐ์๊ธฐ 5~10ms ์ฌ์ฉ โ ํ๋ ์ ๋๋กญ. ๊ฒ์ ์ฝ๋์์ ๊ฐ์ฅ ํํ hash ํจ์ .
ํด๊ฒฐ โ ์์ ์ reserve(N):
1
2
3
void BeginPlay() {
Enemies.reserve(1024); // ์์ ์ต๋์น ๋ฏธ๋ฆฌ ํ๋ณด
}
vector์ reserve(N)๊ณผ ๊ฐ์ ์ฒ ํ โ ๋ฏธ๋ฆฌ ์๋ฉด ๋ฏธ๋ฆฌ ์ก๊ณ , ๋ฐํ์ ์ค ์ฌํ ๋น/rehash ํํผ.
5. ๋ค์ด์ด๊ทธ๋จ โ ์ฝ์ ๊ณผ rehash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
์ด๊ธฐ ์ํ (bucket_count=4, max_load=1.0):
idx: 0 1 2 3
[ ] [ ] [ ] [ ]
insert(k1) size=1, load=0.25 โ ์ ์ ์ฝ์
insert(k2) size=2, load=0.5 โ ์ ์ ์ฝ์
insert(k3) size=3, load=0.75 โ ์ ์ ์ฝ์
insert(k4) size=4, load=1.0 โ ์ ์ ์ฝ์
(=์๊ณ๊ฐ)
insert(k5) size=5, load=1.25 โ โ
์๊ณ๊ฐ ์ด๊ณผ
โ
rehash ๋ฐ๋
โ
bucket_count: 4 โ 8 (๋ณดํต 2๋ฐฐ)
โ
๋ชจ๋ ์์ (k1~k4) ์ฌํด์ฑ
โ
k5 ์ ์ ์ฝ์
โ
์ด insert๋ง O(N) ๋น์ฉ
์ดํ ์ํ (bucket_count=8):
idx: 0 1 2 3 4 5 6 7
[โ] [ ] [โ] [ ] [โ] [โ] [ ] [โ]
k1 k2 k3 k4 k5
6. ์ธ๋ฆฌ์ผ TMap โ ๊ฐ์ ์๋ฆฌ, ๋ค๋ฅธ API
์ธ๋ฆฌ์ผ์ TMap<K, V>๋ ๋ด๋ถ์ ์ผ๋ก hash ํ
์ด๋ธ(sparse array + hash). ํต์ฌ API:
1
2
3
4
5
6
7
TMap<int32, FString> Map;
Map.Reserve(1024); // N๊ฐ ๋ค์ด๊ฐ๋ ์ฌํ ๋น ์๊ฒ
Map.Empty(1024); // Clear + Reserve(1024)
Map.Compact(); // ๋น ์ฌ๋กฏ ์ ๊ฑฐ (sparse ์ ๋ฆฌ)
int32 BucketCount = Map.GetMaxIndex(); // ๋ด๋ถ ์ฌ๋กฏ ์
Reserve๋ STL๊ณผ ๊ฐ์ ์๋ฏธ. Empty(N)์ clearํ๋ฉด์ capacity ์ ์ง/์กฐ์ . ๊ฒ์ ์์์ด๋ ๋ ๋ฒจ ๋ก๋ ์ ์์ ์ต๋์น๋ก Reserve ํด๋๋ ๊ฒ ํ์ค ํจํด.
์ธ๋ฆฌ์ผ์ TMap์ STL๊ณผ ๋ฌ๋ฆฌ ์ฝ์
์์๋ฅผ ๋ณด์กดํฉ๋๋ค (sparse array ๊ธฐ๋ฐ) โ ๊ทธ๋์ ์ดํฐ๋ ์ด์
์์๊ฐ ๊ฒฐ์ ์ . ๋จ, ์ญ์ ํ ์ฌ์ฝ์
์ ์์๊ฐ ๋ฌ๋ผ์ง ์ ์์.
7. ๋ฉด์ Q&A
Q1. โunordered_map๊ณผ map์ ์๊ฐ๋ณต์ก๋ ์ฐจ์ด๋?โ
std::map์ ํญ์ O(log N),std::unordered_map์ ํ๊ท O(1) ์ต์ O(N).
std::map์ Red-Black Tree(์๊ฐ ๊ท ํ ์ด์ง ๊ฒ์ ํธ๋ฆฌ). ๋ชจ๋ ์ฐ์ฐ(find/insert/erase)์ด ํธ๋ฆฌ ๋์ด์ ๋น๋กํ O(log N)์ด๊ณ , ์ต์ ๋ O(log N)์ผ๋ก ๋ณด์ฅ. ํค ์์๊ฐ ์ ๋ ฌ๋ผ์ ์ํ ์ ์ค๋ฆ์ฐจ์.
std::unordered_map์ hash table. ํ๊ท ์ O(1)์ด์ง๋ง load factor๊ฐ ๋์์ง๊ฑฐ๋ hash ์ถฉ๋์ด ์ฌํ๋ฉด ํ ๋ฒํท์ ์ฒด์ธ์ด ๊ธธ์ด์ ธ ์ต์ O(N). ๊ทธ๋ฆฌ๊ณ rehash ์ ๊ทธ ํ ๋ฒ์ insert๋ O(N) ๋น์ฉ โ amortized O(1)์ด์ง๋ง worst-case๋ ๋ค๋ฆ.๊ทธ๋์ ์ ํ ๊ธฐ์ค:
- ์์๊ฐ ์ค์ํ๊ฑฐ๋ worst-case ๋ณด์ฅ์ด ํ์ โ
std::map(RB-Tree)- ํ๊ท ๋น ๋ฅธ lookup์ด ์ฐ์ ์ด๊ณ ์์ ๋ฌด๊ด โ
std::unordered_map(hash)- ๋ฉ๋ชจ๋ฆฌ๊ฐ ๋น ๋ฏํ๊ฑฐ๋ capacity ์์ธก ๊ฐ๋ฅ โ
std::unordered_map+reserve์ค์ ๋ฒค์น๋งํฌ๋ก๋
unordered_map์ ํ๊ท ์ด ์ฝ 2~3๋ฐฐ ๋น ๋ฅธ ๊ฒฝ์ฐ๊ฐ ๋ง์ง๋ง, ์์ N(< 30)์์๋map์ด ๋ ๋น ๋ฅผ ์ ์์ต๋๋ค(cache ์นํ์ฑ). N์ด ๋งค์ฐ ํฌ๊ณ lookup์ด hot path๋ฉดunordered_map์ด ์ฐ์ธ.
Q2. โrehash๊ฐ ์ผ์ด๋๋ ์์ ๊ณผ ๋น์ฉ์?โ
์์ โ
insertํload_factor > max_load_factor๊ฐ ๋๋ ์๊ฐ. C++ ํ์ค ๊ธฐ๋ณธmax_load_factor = 1.0์ด๋ฏ๋ก, size๊ฐ bucket_count๋ฅผ ์ด๊ณผํ๋ ค๋ ์๊ฐ ํธ๋ฆฌ๊ฑฐ.๋น์ฉ โ bucket_count๋ฅผ ๋ณดํต ๋ ๋ฐฐ๋ก ๋๋ฆฌ๊ณ , ๋ชจ๋ N๊ฐ ์์๋ฅผ ์ bucket_count์ ์ฌํด์ฑ. ์๊ฐ ๋ณต์ก๋ O(N). ๋ฉ๋ชจ๋ฆฌ๋ ์ ์ ๋ ๋ฐฐ ์ฌ์ฉ(์ด์ ๋ฐฐ์ด + ์ ๋ฐฐ์ด). ํ ๋ฒ์ insert๊ฐ ๊ทธ ์๊ฐ๋ง O(N).
amortized ๋ถ์ โ N๊ฐ ์์๋ฅผ ์ฒ์๋ถํฐ insertํ๋ฉด rehash๊ฐ log N๋ฒ ์ ๋ ๋ฐ์(bucket์ด 2๋ฐฐ์ฉ ์ฆ๊ฐํ๋ฏ๋ก). ์ด ๋น์ฉ O(N log N)์ด์ง๋ง N์ผ๋ก ๋๋๋ฉด amortized O(log N / log N) = O(1). ๊ทธ๋์ ํ๊ท ์ ์ฌ์ ํ ๋น ๋ฅด์ง๋ง ๊ฐ๋ณ insert๋ ์ต์ O(N).
๊ฒ์ ์ฝ๋์์ ์ํ โ 16.6ms ํ๋ ์ ์์ฐ ์์ ๊ฐ์๊ธฐ 5~10ms rehash๊ฐ ๋ผ๋ฉด ํ๋ ์ ๋๋กญ. ๊ทธ๋์
reserve(N)์ผ๋ก ์์ ์ต๋์น ๋ฏธ๋ฆฌ ํ๋ณด๊ฐ ํ์ค. ์ธ๋ฆฌ์ผTMap๋ ๊ฐ์ ์๋ฆฌ๋กReserve/Empty(N)๊ถ์ฅ.ํ ๊ฐ์ง ์ถ๊ฐ โ rehash ํ ๋ชจ๋ ์ดํฐ๋ ์ดํฐยท์ฐธ์กฐยทํฌ์ธํฐ๊ฐ ๋ฌดํจํ๋ฉ๋๋ค. vector์ reallocation๊ณผ ๊ฐ์ ํจ์ . rehash ๊ฐ๋ฅ์ฑ์ด ์๋ ์ํ์์ ์ด์ ์ดํฐ๋ ์ดํฐ๋ฅผ ๋ณด๊ดยท์ฌ์ฉํ๋ฉด UB.
Q3. โload factor๋ฅผ ์๊ฒ ์ก์ผ๋ฉด ์ด๋ค trade-off๊ฐ ์๋์?โ
๋ฉ๋ชจ๋ฆฌ โ vs ์ถฉ๋ โ ์ ํธ๋ ์ด๋์คํ์ ๋๋ค.
max_load_factor๊ฐ ์์์๋ก(์: 0.5) ๊ฐ์ size์ ๋ ๋ง์ bucket์ ์ ์งํฉ๋๋ค. ์ถฉ๋์ด ์ค์ด ํ๊ท lookup์ด ๋นจ๋ผ์ง์ง๋ง ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ๋ ๋ฐฐ.
1 2 3 4 std::unordered_map<int, int> m; m.max_load_factor(0.5); // ๋ฉ๋ชจ๋ฆฌ 2๋ฐฐ, ์ถฉ๋ ์ ๋ฐ m.reserve(10000); // bucket_count โ 20000 (= 10000 / 0.5)๋ฐ๋๋ก ํฌ๊ฒ ์ก์ผ๋ฉด(์: 2.0) bucket์ด ์ ์ด ๋ฉ๋ชจ๋ฆฌ ์ ์ฝ. ํ ๋ฒํท์ ํ๊ท 2๊ฐ ์์๊ฐ ๋ค์ด๊ฐ ์ถฉ๋ยท์ฒด์ธ ๊ธธ์ด ์ฆ๊ฐ, lookup ๋๋ ค์ง.
C++ ํ์ค ๊ธฐ๋ณธ์ 1.0 โ ๋ฉ๋ชจ๋ฆฌยท์ฑ๋ฅ ๊ท ํ์ . ์ค๋ฌด์์๋ ๊ฑฐ์ ์ ๋ฐ๊ฟ๋๋ค.
์๊ฒ ์ก๋ ๊ฒ ์๋ฏธ ์๋ ๊ฒฝ์ฐ:
- hot path lookup์ด ๋งค์ฐ ๋น๋ฒ โ ์ถฉ๋ ํ๋ฅ ์ ๋ ๋ฎ์ถฐ ํ๊ท lookup ์๊ฐ์ ์ค์
- hash ํจ์์ ๋ถํฌ๊ฐ ์์ฌ์ค๋ฌ์ โ ์ถฉ๋์ด ๋ง์ ๊ฒ ๊ฐ์ผ๋ฉด ๋ฏธ๋ฆฌ ์ฌ์ ํ๋ณด
- ๋ฉ๋ชจ๋ฆฌ๋ ์ถฉ๋ถ, ์ง์ฐ์ด ๋ ์ค์ โ ๊ฒ์ ์๋ฒยท์ค์๊ฐ ๊ฑฐ๋ ์์คํ
ํฌ๊ฒ ์ก๋ ๊ฒ ์๋ฏธ ์๋ ๊ฒฝ์ฐ:
- ๋ฉ๋ชจ๋ฆฌ๊ฐ ๋น ๋ฏ โ ์๋ฒ ๋๋ยท๋ชจ๋ฐ์ผ
- lookup์ด ๋๋ฌผ๊ณ ๋ฉ๋ชจ๋ฆฌ ์ ๊ฐ์ด ์ฐ์ โ ๋ฐ์ดํฐ ๋ณด๊ด์ฉ map
๊ทธ๋ฌ๋ ๋ณดํต์
max_load_factor๋ฅผ ๊ฑด๋๋ฆฌ๋ ๊ฒ๋ณด๋คreserve(N)์ผ๋ก ๋ฏธ๋ฆฌ capacity ํ๋ณดํ๋ ๊ฒ ํจ์ฌ ํจ๊ณผ์ ์ ๋๋ค. rehash ์์ฒด๋ฅผ ํํผํ๋ ๊ฒ ๊ฐ์ฅ ํฐ ์ฑ๋ฅ ๊ฐ์ .
8. ํ๊ท ๋ค๋ฆฌ
| ํ์ผ | ์ฐ๊ฒฐ ์ง์ |
|---|---|
| 15_1_vector_vs_hash_concepts | ์ด ํ์ผ์ ๋ณธํธ โ hash ํจ์์ 3๋ ์กฐ๊ฑด(๊ท ๋ฑ ๋ถํฌยท์ผ๊ด์ฑยท๋น ๋ฅธ ๊ณ์ฐ) ์ ๋ฆฌ |
| 13_vector_vs_list | vector์ reallocation๊ณผ hash์ rehash๊ฐ ๊ฐ์ ํจํด โ capacity ์ด๊ณผ ์ ์ ๋ฐฐ์ด๋ก ๋ณต์ฌ. reserve(N)์ด ๋ ๋ค ํ์ค ํํผ์ฑ
|
| 14_std_map | RB-Tree ๊ธฐ๋ฐ map๊ณผ์ ๋น๊ต โ ํญ์ O(log N) vs ํ๊ท O(1)/์ต์
O(N). ์์ ๋ณด์ฅ vs ๋น ๋ฅธ ํ๊ท lookup |
| 16_stl_containers | STL ํตํฉ ์ ๋ฆฌ โ unordered_* ์ปจํ
์ด๋ 4์ข
(map/multimap/set/multiset) ๊ณตํต ํน์ฑ |
| 23_race_condition | rehash ์ค ๋ค๋ฅธ ์ค๋ ๋๊ฐ lookupํ๋ฉด UB. STL ์ปจํ ์ด๋๋ thread-safe๊ฐ ์๋๋ผ์ ๋์ ์ ๊ทผ ๋ณดํธ ํ์ (FCriticalSection ๋ฑ) |