absl::btree_map — sorted·cache-friendly B-tree
#한 줄 요약
absl::btree_map은 sorted associative container를 B-tree로 구현한다. std::map의 red-black tree가 노드당 1 key + 좌우 child pointer를 강제하는 반면, B-tree 노드는 여러 key를 packed하게 들고 있어 cache line 활용도가 높다. 같은 key 수에서 lookup/insert가 빠르고 메모리도 절약된다.
#동기
std::map의 표준 구현은 거의 항상 red-black tree다. 노드마다 1 key + 2 child pointer + parent pointer + color bit. 메모리 overhead가 크고, traversal마다 노드 jump → cache miss.
RB-tree 노드 (대략 64-bit):[key, value, left*, right*, parent*, color, padding]overhead ≥ 32 bytes per keyB-tree는 다르다. 한 노드가 수십 개 key를 담는다. cache line(64B)에 여러 key가 들어간다.
B-tree 노드 (256 bytes 기준):[key, key, ..., key, child*, child*, ..., child*]overhead per key: 노드 헤더 / NAbseil의 btree_map은 노드 크기를 cache line의 배수에 맞춰 설계해 modern CPU에서 std::map보다 2~4배 빠른 lookup을 보인다.
두 자료구조의 차이를 그림으로 비교하면 다음과 같다.
#API와 사용법
#include "absl/container/btree_map.h"
absl::btree_map<int, std::string> m;m[1] = "one";m[2] = "two";m[3] = "three";
// 정렬된 순회for (const auto& [k, v] : m) { // k는 오름차순}
// 범위 쿼리auto lo = m.lower_bound(2);auto hi = m.upper_bound(10);for (auto it = lo; it != hi; ++it) { /*...*/ }std::map과 동일 인터페이스. lower_bound/upper_bound/equal_range, reverse iteration 모두 지원.
#std::map 대비 차이
| 항목 | std::map (RB-tree) | absl::btree_map |
|---|---|---|
| 노드 fan-out | 2 (binary) | 수십 |
| key 단위 overhead | ~32 B | ~수 B |
| lookup 캐시 친화 | 약 | 강 |
| 노드 분할 비용 | 없음 (rotation) | 있음 (드뭄) |
| pointer/iterator 안정 (insert) | O | X — 노드 분할 시 무효화 |
| pointer/iterator 안정 (erase) | O | X — 노드 merge 시 무효화 |
| 메모리 사용 | 큼 | 작음 |
| 평균 lookup | baseline |
가장 큰 트레이드오프는 iterator/pointer 안정성이다. std::map은 노드 단위 인디렉션이 강해서 안정적이고, btree_map은 노드 분할/병합으로 무효화될 수 있다.
#내부 구현
btree_map의 노드 헤더는 다음과 비슷하다.
// absl/container/internal/btree.h (요약)template <typename Params>class btree_node { using slot_type = typename Params::slot_type;
btree_node* parent_; field_type position_; // 부모에서의 위치 field_type count_; // 현재 key 개수 field_type max_count_; // 노드 최대 key 개수 slot_type slots_[kMaxSlots]; // leaf가 아니면 child pointer 배열도};노드 크기는 target_node_size 매개변수(기본 256B)에 맞춰 컴파일 타임에 결정된다. 작은 key type이면 한 노드에 수십 개 key가 들어간다.
lookup은 노드 안에서 binary search(또는 linear search — small N에서 더 빠름)로 위치를 찾고, 필요하면 child 노드로 내려간다. 트리 높이는 log_N이지만 N이 크므로 실제 깊이는 매우 얕다.
10M 원소:
- std::map: ~23 level (log_2)
- btree_map: ~4 level (log_64)
#코드 리뷰 포인트
1. std::map → btree_map
대부분의 sorted map은 btree_map이 더 빠르다. 단, pointer 안정성을 가정하는 코드를 검수.
// std::map의 관용구 — btree에서는 위험auto* p = &m[key];m.emplace(other_key, ...); // 노드 분할 가능 → p dangling*p = ...;값을 직접 보관하지 말고 매번 find 또는 unique_ptr 보관.
2. std::set → absl::btree_set
absl::btree_set도 같은 트레이드오프로 제공된다. 정렬된 unique 집합이 필요하면 btree_set.
3. multimap / multiset 대응
absl::btree_multimap, absl::btree_multiset도 제공된다. std::multimap보다 빠르고 메모리 효율도 높다.
4. iteration이 hot path
range-for iteration 또한 cache-friendly하다. 노드 내부 순회는 인접 메모리, 노드 간 점프만 cache miss.
// 정렬된 순회 자주 — btree_map이 적합for (const auto& [k, v] : sorted_index) Process(k, v);#안티패턴
hash로 충분한 곳에 btree
정렬·범위 쿼리·lower_bound가 필요하지 않으면 hash map이 압도적으로 빠르다.
| 워크로드 | 추천 |
|---|---|
| 키 lookup only | flat_hash_map |
| 정렬 순회·범위 쿼리 | btree_map |
| stable pointer 필요 + 정렬 | std::map 또는 btree_map + indirection |
pointer 보관
// 회피auto* p = &m[k];m.emplace(...);use(p); // UB 가능
// Goodm[k] = ...; // operator[] 마지막에또는 value를 unique_ptr로.
iterator 캐시
auto it = m.find(k);m.emplace(...); // 노드 분할 → it 무효화 가능use(it);iterator도 보관 금지.
#node_handle (C++17 호환)
auto node = m.extract(key);node.key() = new_key; // key 수정 후m.insert(std::move(node));absl::btree_map은 C++17의 node_handle API를 지원해 key를 수정할 수 있다. 일반 m[k]로는 불가능한 동작.
#정리
btree_map은 B-tree 기반 sorted map. cache 친화, 메모리 효율.std::map대비 2~4배 빠른 lookup, 노드 fan-out 수십.- iterator/pointer가 노드 분할/병합으로 무효화될 수 있다.
- 정렬·범위 쿼리가 필요 없으면
flat_hash_map이 더 빠르다. - multi 변형(
btree_multimap,btree_multiset)도 제공.
#다음 편
Part 5-05 — FixedArray에서 동적 크기지만 stack 할당을 노리는 배열을 본다.
#관련 항목
Abseil Code Review · 31 of 79
- 1 Abseil Code Review — Google production-grade C++ 라이브러리 분석
- 2 Abseil 개요 — Google이 std를 보완한 이유
- 3 Abseil 설계 철학 — std 호환과 추가 기능의 균형
- 4 Abseil 빌드와 의존성 — Bazel vs CMake
- 5 Abseil LTS vs HEAD 릴리스 모델 분석
- 6 Abseil Versioning과 ABI 호환성 정책
- 7 Abseil 매크로 — ABSL_HAVE_*·ABSL_ATTRIBUTE_*
- 8 Abseil ABSL_PREDICT_TRUE/FALSE — branch hint
- 9 absl::LogSeverity — 로그 레벨 타입
- 10 Abseil type_traits — negation·conjunction·void_t
- 11 Abseil Conformance·Policy 분석
- 12 Abseil Memory utilities 분석
- 13 Abseil raw_logging — heap-free 로깅
- 14 Abseil thread_annotations — clang TSA 통합
- 15 absl::Status — exception-free error handling
- 16 absl::StatusOr<T> — 값 또는 에러
- 17 absl status_macros — ASSIGN_OR_RETURN·RETURN_IF_ERROR
- 18 absl::Status payload — 구조화된 에러 컨텍스트
- 19 absl::Status ↔ exception 변환 패턴
- 20 absl::string_view — non-owning 문자열 참조
- 21 absl::string_view 함정 — dangling·c_str·임시 객체
- 22 absl::StrCat — 가변 인자 문자열 연결과 AlphaNum
- 23 absl::StrSplit — Delimiter·Predicate·컨테이너 변환
- 24 absl::StrJoin — 컨테이너 결합과 Formatter
- 25 absl::StrFormat — type-safe printf·FormatSpec
- 26 Abseil ASCII 함수 — locale-free 분류·대소문자 변환
- 27 Abseil Escape — CEscape·HexEscape·Base64
- 28 absl::flat_hash_map — Swiss Table 기반 hash map
- 29 absl::flat_hash_set — set 버전 Swiss Table
- 30 absl::node_hash_map — stable pointer가 필요할 때
- 31 absl::btree_map — sorted·cache-friendly B-tree
- 32 absl::FixedArray — 런타임 크기 stack 배열
- 33 absl::InlinedVector — small buffer optimization
- 34 Abseil Swiss Table internals — control byte·SIMD probing
- 35 absl::Mutex — reader-writer·fairness·deadlock 검출
- 36 absl::Mutex Conditional Critical Section — Await로 cv 없애기
- 37 absl::Notification — once-only signal
- 38 absl::BlockingCounter·Barrier — 다중 thread 조율
- 39 absl::Mutex annotations — clang thread-safety로 race를 컴파일 타임에
- 40 absl::Time·Duration 분석 — 단단한 type
- 41 absl::Time Format·Parse
- 42 absl::CivilTime 분석
- 43 absl::time_zone 분석
- 44 absl::Time mocking — 테스트 친화 시간
- 45 absl::BitGen — 모던 난수 생성기
- 46 Abseil Random Distributions — Uniform·Exponential
- 47 Abseil Mocking Random — 테스트 결정성
- 48 Abseil Random Seeding·Entropy
- 49 absl::int128·uint128 분석
- 50 absl::bits — popcount·countl_zero
- 51 absl::optional vs std::optional
- 52 absl::variant 분석
- 53 absl::span 분석
- 54 absl::any 분석
- 55 absl::compare — three-way 비교
- 56 Abseil utility — apply·in_place
- 57 Abseil AbslHashValue 분석
- 58 Abseil HashState chaining
- 59 Abseil Custom hashable 구현
- 60 Abseil LOG·VLOG·CHECK 분석
- 61 Abseil LogSink 분석
- 62 Abseil LogEntry·structured logging
- 63 Abseil Stack trace·failure_signal_handler
- 64 ABSL_FLAG 정의 분석
- 65 Abseil ParseCommandLine 동작
- 66 Abseil Flag introspection·validation
- 67 Google 스타일의 Abseil 사용 패턴
- 68 Abseil 자주 보는 anti-pattern
- 69 std → absl 마이그레이션 전략
- 70 absl::Cleanup — 함수 종료 시 실행 보장
- 71 Abseil algorithm container 확장 — c_sort·c_find_if·c_count_if
- 72 absl::function_ref와 any_invocable — 함수 객체 전달의 두 축
- 73 absl::bind_front와 Overload — 함수 객체 보조 도구
- 74 absl::Cord — 분산 시스템용 대용량 문자열
- 75 absl::from_chars·SimpleAtoi — 빠른 숫자 변환
- 76 absl::Cord vs std::string — 선택 기준과 메모리 프로파일
- 77 absl::GetStackTrace와 Symbolize — crash 시 readable stack
- 78 absl::ComputeCrc32c — 하드웨어 가속 체크섬
- 79 absl::PeriodicSampler — 적응형 샘플링·jitter 회피
관련 글
Abseil algorithm container 확장 — c_sort·c_find_if·c_count_if
absl::c_* algorithm wrapper — container 전체를 받아 begin/end 자동 처리, STL algorithm의 한 줄 boilerplate를 제거.
같은 시리즈에서 이어 읽기
absl::InlinedVector — small buffer optimization
Part 5-06: absl::InlinedVector — std::vector + 작으면 stack, 커지면 heap. SBO 패턴의 표준 도구.
같은 시리즈에서 이어 읽기
absl::FixedArray — 런타임 크기 stack 배열
Part 5-05: absl::FixedArray — 런타임 결정 크기지만 작으면 stack, 크면 heap. VLA의 안전한 대체.
같은 시리즈에서 이어 읽기