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

absl::btree_map — sorted·cache-friendly B-tree

· Hawk · 4분 읽기

#한 줄 요약

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 key

B-tree는 다르다. 한 노드가 수십 개 key를 담는다. cache line(64B)에 여러 key가 들어간다.

B-tree 노드 (256 bytes 기준):
[key, key, ..., key, child*, child*, ..., child*]
overhead per key: 노드 헤더 / N

Abseil의 btree_map은 노드 크기를 cache line의 배수에 맞춰 설계해 modern CPU에서 std::map보다 2~4배 빠른 lookup을 보인다.

두 자료구조의 차이를 그림으로 비교하면 다음과 같다.

btree_map 노드 vs RB-tree

#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-out2 (binary)수십
key 단위 overhead~32 B~수 B
lookup 캐시 친화
노드 분할 비용없음 (rotation)있음 (드뭄)
pointer/iterator 안정 (insert)OX — 노드 분할 시 무효화
pointer/iterator 안정 (erase)OX — 노드 merge 시 무효화
메모리 사용작음
평균 lookupbaseline24x

가장 큰 트레이드오프는 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::mapbtree_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::setabsl::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 onlyflat_hash_map
정렬 순회·범위 쿼리btree_map
stable pointer 필요 + 정렬std::map 또는 btree_map + indirection

pointer 보관

// 회피
auto* p = &m[k];
m.emplace(...);
use(p); // UB 가능
// Good
m[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. 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 회피