본문으로 건너뛰기
Abseil Code Review · 27/79

absl::flat_hash_map — Swiss Table 기반 hash map

· Hawk · 4분 읽기

#한 줄 요약

absl::flat_hash_map은 Swiss Table 구조를 채택한 open-addressing hash map이다. std::unordered_map이 강제하는 노드 단위 할당과 chain-list 순회를 연속 메모리에 SIMD-friendly한 metadata로 대체해 lookup이 평균 2~3배 빠르다. 대신 모든 mutation에서 iterator·pointer가 무효화된다.

#동기

std::unordered_map의 표준 규약은 노드 기반이다. 각 (key, value) 쌍이 별도 heap node에 있고, bucket은 노드 포인터 chain을 들고 있다. 결과:

  • lookup마다 두 번 이상의 cache miss — bucket 배열 → chain head → 노드 → 노드.
  • key 비교 전 해시 비교가 별도 메모리 접근.
  • erase가 stable iterator를 요구해 노드 풀링이 강제.

flat_hash_map은 표준 규약을 일부 깨고 cache 친화 설계를 택한다.

  • 슬롯이 연속 배열에 직접 저장 (open addressing).
  • 슬롯 위 16바이트 control byte 그룹해시 partial + *상태(empty/deleted/full)*를 들고 있어 SIMD로 한 번에 16개 슬롯을 스캔한다.
  • erase가 iterator를 무효화함을 허용하는 대신 packing이 단순해진다.

Swiss Table 자체의 내부 구조는 Part 5-07 — Swiss Table internals에서 본다. 여기서는 사용 측면.

구조를 한눈에 보면 다음과 같다.

flat_hash_map 메모리 레이아웃

#API와 사용법

#include "absl/container/flat_hash_map.h"
absl::flat_hash_map<std::string, int> counts;
counts["a"] = 1;
counts.emplace("b", 2);
if (auto it = counts.find("a"); it != counts.end()) {
LOG(INFO) << it->second;
}
for (const auto& [k, v] : counts) {
// ...
}

인터페이스는 std::unordered_map과 거의 동일하다. 마이그레이션은 보통 헤더 교체와 typedef 변경으로 끝난다.

// before
std::unordered_map<K, V> m;
// after
absl::flat_hash_map<K, V> m;

#내부 구현 — open addressing

flat_hash_mapopen addressing hash table이다. node chain 대신 flat 배열에 직접 slot을 둔다.

Open addressing probing

hash 충돌 시 다음 slot으로 probe해 빈 자리를 찾는다. cache locality는 좋지만 load factor가 높을수록 probe 길이가 늘어 rehash가 필요해진다. Swiss Table은 여기에 SIMD로 16-slot 그룹을 1 cmp로 검사하는 트릭을 더해 probe 비용을 상쇄한다 (Part 5-07).

#내부 구현 — heterogeneous lookup

표준은 C++20부터 일부 heterogeneous lookup을 지원하지만, flat_hash_map은 처음부터 강력하다.

absl::flat_hash_map<std::string, int> m;
m["hello"] = 1;
// std::unordered_map: 임시 std::string 생성 후 lookup
auto it = m.find(absl::string_view("hello")); // OK — no alloc
auto it2 = m.find("hello"); // OK — no alloc

HashEq가 transparent하면(is_transparent typedef) string_view로 직접 lookup이 가능하다. Abseil은 absl::Hash/std::equal_to<>를 기본으로 transparent 처리한다.

#std::unordered_map 비교

항목std::unordered_mapabsl::flat_hash_map
storage노드 chainopen addressing flat array
lookup cache miss2~3회0~1회
iterator/pointer 안정성insert/erase에 stablerehash·erase에서 무효화
node_type (split)C++17 지원미지원 (node_hash_map 별도)
heterogeneous lookupC++20 부분처음부터 강력
value 이동 비용없음 (노드 재사용)rehash 시 모든 value 이동
평균 lookupbaseline~2-3x 빠름
메모리 사용노드 헤더 overheadctrl byte + slot

#코드 리뷰 포인트

1. value 크기 vs flat 선택

flat_hash_map은 (key, value)가 직접 배열에 저장된다. value가 크면(>32B 정도) rehash 비용이 커진다. 큰 value는 node_hash_map 또는 flat_hash_map<K, std::unique_ptr<V>>를 고려한다.

// 회피 — value 64B + rehash 비용
absl::flat_hash_map<int, BigStruct> m;
// Good
absl::flat_hash_map<int, std::unique_ptr<BigStruct>> m;
// 또는
absl::node_hash_map<int, BigStruct> m;

node_hash_mapPart 5-03에서 본다.

2. pointer/iterator 보관 금지

auto* p = &m["key"]; // 위험
m.insert({"other", 0}); // rehash 가능 — p dangling

std::unordered_map은 노드 안정성을 보장하지만 flat_hash_map은 아니다. value 주소를 보관해야 하면 node_hash_map 또는 value를 unique_ptr로.

3. reserve로 rehash 비용 절감

absl::flat_hash_map<int, int> m;
m.reserve(expected_size); // 한 번에 alloc, 이후 rehash 없음
for (...) m.emplace(...);

reserve(n)은 load factor를 고려해 충분한 슬롯을 잡는다.

4. heterogeneous lookup 활용

absl::flat_hash_map<std::string, int> m;
absl::string_view key = LookupKeyFromInput();
auto it = m.find(key); // no alloc, no string copy

std::unordered_map은 같은 코드에 임시 string alloc이 발생한다 (C++20 transparent 미지원 시).

#안티패턴

큰 value 직접 저장

absl::flat_hash_map<int, std::array<char, 4096>> m;
// rehash가 일어나면 모든 4KB value를 이동
// — std::move 가능이라도 cache 영향 큼

value 크기가 클 때는 unique_ptr<V> 또는 node_hash_map.

const_iterator로 mutate 회피 시도

flat_hash_mapiterator/const_iterator가 모두 같은 안정성을 가진다(둘 다 무효화). const_iterator를 들고 있어도 rehash 위험은 동일.

std::map처럼 사용

hash map은 순서 보장 없음이다. ordered traversal이 필요하면 absl::btree_map (Part 5-04).

#정리

  • flat_hash_map은 Swiss Table 기반 open-addressing.
  • 연속 메모리 + 16-slot SIMD 스캔으로 lookup 2~3배 빠르다.
  • iterator/pointer가 rehash·erase에서 무효화된다.
  • value가 크면 unique_ptr 또는 node_hash_map.
  • heterogeneous lookup이 처음부터 강력 — string_view로 lookup 가능.

#다음 편

Part 5-02 — flat_hash_set에서 set 대응을 짧게 본다.

#관련 항목

Abseil Code Review · 28 of 79

  1. 1 Abseil Code Review — Google production-grade C++ 라이브러리 분석
  2. 2 Abseil 개요 — Google이 std를 보완한 이유
  3. 3 Abseil 설계 철학 — std 호환과 추가 기능의 균형
  4. 4 Abseil 빌드와 의존성 — Bazel vs CMake
  5. 5 Abseil LTS vs HEAD 릴리스 모델 분석
  6. 6 Abseil Versioning과 ABI 호환성 정책
  7. 7 Abseil 매크로 — ABSL_HAVE_*·ABSL_ATTRIBUTE_*
  8. 8 Abseil ABSL_PREDICT_TRUE/FALSE — branch hint
  9. 9 absl::LogSeverity — 로그 레벨 타입
  10. 10 Abseil type_traits — negation·conjunction·void_t
  11. 11 Abseil Conformance·Policy 분석
  12. 12 Abseil Memory utilities 분석
  13. 13 Abseil raw_logging — heap-free 로깅
  14. 14 Abseil thread_annotations — clang TSA 통합
  15. 15 absl::Status — exception-free error handling
  16. 16 absl::StatusOr<T> — 값 또는 에러
  17. 17 absl status_macros — ASSIGN_OR_RETURN·RETURN_IF_ERROR
  18. 18 absl::Status payload — 구조화된 에러 컨텍스트
  19. 19 absl::Status ↔ exception 변환 패턴
  20. 20 absl::string_view — non-owning 문자열 참조
  21. 21 absl::string_view 함정 — dangling·c_str·임시 객체
  22. 22 absl::StrCat — 가변 인자 문자열 연결과 AlphaNum
  23. 23 absl::StrSplit — Delimiter·Predicate·컨테이너 변환
  24. 24 absl::StrJoin — 컨테이너 결합과 Formatter
  25. 25 absl::StrFormat — type-safe printf·FormatSpec
  26. 26 Abseil ASCII 함수 — locale-free 분류·대소문자 변환
  27. 27 Abseil Escape — CEscape·HexEscape·Base64
  28. 28 absl::flat_hash_map — Swiss Table 기반 hash map
  29. 29 absl::flat_hash_set — set 버전 Swiss Table
  30. 30 absl::node_hash_map — stable pointer가 필요할 때
  31. 31 absl::btree_map — sorted·cache-friendly B-tree
  32. 32 absl::FixedArray — 런타임 크기 stack 배열
  33. 33 absl::InlinedVector — small buffer optimization
  34. 34 Abseil Swiss Table internals — control byte·SIMD probing
  35. 35 absl::Mutex — reader-writer·fairness·deadlock 검출
  36. 36 absl::Mutex Conditional Critical Section — Await로 cv 없애기
  37. 37 absl::Notification — once-only signal
  38. 38 absl::BlockingCounter·Barrier — 다중 thread 조율
  39. 39 absl::Mutex annotations — clang thread-safety로 race를 컴파일 타임에
  40. 40 absl::Time·Duration 분석 — 단단한 type
  41. 41 absl::Time Format·Parse
  42. 42 absl::CivilTime 분석
  43. 43 absl::time_zone 분석
  44. 44 absl::Time mocking — 테스트 친화 시간
  45. 45 absl::BitGen — 모던 난수 생성기
  46. 46 Abseil Random Distributions — Uniform·Exponential
  47. 47 Abseil Mocking Random — 테스트 결정성
  48. 48 Abseil Random Seeding·Entropy
  49. 49 absl::int128·uint128 분석
  50. 50 absl::bits — popcount·countl_zero
  51. 51 absl::optional vs std::optional
  52. 52 absl::variant 분석
  53. 53 absl::span 분석
  54. 54 absl::any 분석
  55. 55 absl::compare — three-way 비교
  56. 56 Abseil utility — apply·in_place
  57. 57 Abseil AbslHashValue 분석
  58. 58 Abseil HashState chaining
  59. 59 Abseil Custom hashable 구현
  60. 60 Abseil LOG·VLOG·CHECK 분석
  61. 61 Abseil LogSink 분석
  62. 62 Abseil LogEntry·structured logging
  63. 63 Abseil Stack trace·failure_signal_handler
  64. 64 ABSL_FLAG 정의 분석
  65. 65 Abseil ParseCommandLine 동작
  66. 66 Abseil Flag introspection·validation
  67. 67 Google 스타일의 Abseil 사용 패턴
  68. 68 Abseil 자주 보는 anti-pattern
  69. 69 std → absl 마이그레이션 전략
  70. 70 absl::Cleanup — 함수 종료 시 실행 보장
  71. 71 Abseil algorithm container 확장 — c_sort·c_find_if·c_count_if
  72. 72 absl::function_ref와 any_invocable — 함수 객체 전달의 두 축
  73. 73 absl::bind_front와 Overload — 함수 객체 보조 도구
  74. 74 absl::Cord — 분산 시스템용 대용량 문자열
  75. 75 absl::from_chars·SimpleAtoi — 빠른 숫자 변환
  76. 76 absl::Cord vs std::string — 선택 기준과 메모리 프로파일
  77. 77 absl::GetStackTrace와 Symbolize — crash 시 readable stack
  78. 78 absl::ComputeCrc32c — 하드웨어 가속 체크섬
  79. 79 absl::PeriodicSampler — 적응형 샘플링·jitter 회피