본문으로 건너뛰기
Folly Code Review · 31/89

folly::F14NodeMap — stable pointer가 필요할 때

· Hawk · 4분 읽기

#한 줄 요약

F14NodeMap은 chunk slot에 value pointer만 두고 실제 value는 별도 heap node에 둔다. std::unordered_map처럼 reference/pointer가 rehash 후에도 유효하다. ValueMap보다 한 단계 indirection이 추가되지만 안정성을 얻는다.

#동기

F14ValueMap은 빠르지만 reference가 흔들린다. 다음 상황에서는 ValueMap을 쓸 수 없다.

  • 값이 큰 객체(MB급 buffer)라 복사 비용이 부담.
  • 다른 자료구조가 map value의 pointer를 보관 (graph node 등).
  • value가 비복사 타입 (mutex, atomic).
  • iterator가 오래 살아야 한다 (async chain 중간).

이런 use case는 std::unordered_map이 자연스럽지만 lookup 성능이 떨어진다. F14NodeMap은 F14의 SIMD probing + std-like pointer 안정성을 결합한다.

folly::F14NodeMap<int, std::mutex> mutex_pool;
auto& m = mutex_pool[key]; // mutex는 movable 아님
m.lock();
// 다른 thread가 insert 해도 &m 유효

#API & 사용법

ValueMap과 동일한 인터페이스.

#include <folly/container/F14Map.h>
folly::F14NodeMap<std::string, BigObject> m;
m.emplace("key1", BigObject{...});
// reference 안정 — rehash 통과
const auto& obj = m["key1"];
m.reserve(10000); // rehash 가능
LOG(INFO) << obj.id; // 여전히 OK
// iterator 안정 — std::unordered_map과 같음
auto it = m.find("key1");
m.emplace("key2", ...);
it->second.UpdateField(); // 여전히 OK

차이는 implementation detail이다. 사용자 코드는 ValueNode만 바꾸면 된다(보통).

#내부 구현

#Slot 구조

ValueMap slot: [ Key | Value ] in-place
NodeMap slot: [ Node* ] heap node 가리킴
[ Key | Value ] (별도 heap)

chunk 구조(14 slot + control byte)는 동일. 차이는 slot이 pointer라는 점.

// 약식 — folly/container/detail/F14Map-pre.h
template <typename Key, typename Value>
struct NodeContainerPolicy {
using Item = std::pair<Key, Value>;
using Slot = Item*; // pointer만 chunk에
static Item& itemAt(Slot& s) { return *s; }
static Slot constructSlot(Item&& v, Allocator& a) {
return new (a.allocate(1)) Item(std::move(v));
}
};

SlotItem*이므로 rehash로 chunk가 옮겨져도 node pointer 자체는 그대로. node 메모리는 처음 emplace 시 할당하고 erase 전까지 안 옮긴다.

#Insert 동작

// 약식
auto emplace(K key, V value) {
// 1. node를 먼저 heap에 할당
auto* node = allocator_.allocate(1);
new (node) Item(std::move(key), std::move(value));
// 2. chunk에 slot pointer 저장 (rehash 가능)
auto [slot, inserted] = tableEmplace(node->first, node);
if (!inserted) {
node->~Item();
allocator_.deallocate(node, 1);
}
}

allocation 한 번 extra. 큰 value면 chunk 안에 inline 저장보다 오히려 메모리가 작다(chunk가 빈 slot까지 reserve하므로).

#Rehash 비용

ValueMap rehash는 value를 새 chunk로 move. value의 move cost가 영향.

NodeMap rehash는 pointer만 복사. value는 옮기지 않으므로 rehash 비용이 작다(특히 큰 value에서).

N=10K, sizeof(V)=1KB
F14ValueMap rehash: ~10ms (move 10K개의 1KB)
F14NodeMap rehash: ~0.1ms (pointer 10K개만)

ValueMap이 빠르려면 value가 작아야 한다. value가 커지면 NodeMap의 indirection 비용 < ValueMap의 move 비용.

#std/abseil 비교

// abseil
absl::node_hash_map<K, V> m; // std-like pointer 안정
// std
std::unordered_map<K, V> std_map; // pointer 안정 (chaining)
// folly
folly::F14NodeMap<K, V> f14n;
항목std::unordered_mapabsl::node_hash_mapfolly::F14NodeMap
pointer 안정OOO
iterator 안정O (insert/erase 다른 노드는)OO
구조chainingSwiss + pointer slotSwiss + pointer slot
Lookupcache miss 많음SIMD 가속SIMD 가속
Insert allocationnode 매번node 매번node 매번

absl::node_hash_map과 사상 동일. 성능은 거의 같다. ValueMap 대비 lookup이 한 indirection만큼 (보통 10-20%) 느리다.

#코드 리뷰 포인트

// Bad — small value인데 NodeMap
folly::F14NodeMap<int, int> tiny;
// 매 entry마다 16-byte 정도 heap alloc
// Good
folly::F14ValueMap<int, int> tiny; // chunk inline

value가 작으면 NodeMap의 heap allocation 오버헤드가 일반 lookup 비용을 압도한다. ValueMap 또는 FastMap.

// Good — 큰 value, 외부 pointer 필요
folly::F14NodeMap<UserId, UserProfile> profiles;
const UserProfile* p = &profiles.at(id);
// 다른 thread/chain이 p를 가져다 쓰는 동안 insert 안전
// Bad — value가 movable이라고 ValueMap을 쓰지만 lifetime 위반
folly::F14ValueMap<int, std::string> m;
auto& s = m[1];
m.insert({2, "..."}); // rehash 가능
s += "x"; // UB
// Good — NodeMap
folly::F14NodeMap<int, std::string> mn;
auto& s = mn[1];
mn.insert({2, "..."});
s += "x"; // OK

#안티패턴

  • mutex/atomic/condition_variable 같은 비이동 타입을 ValueMap에: rehash 시 move가 컴파일 에러 또는 UB. 항상 NodeMap.
  • node hash 함수와 key compare가 같지 않은 두 map을 섞기: extract/merge 시 ABI 호환이 깨진다. 두 map 모두 동일 trait.
  • node를 직접 delete 시도: extract() API로 안전한 ownership transfer. 임의 delete는 chunk 상태 무효화.

#정리

  • F14NodeMap은 chunk에 pointer만, value는 heap node.
  • std::unordered_map 수준의 pointer/reference 안정성.
  • 큰 value, 비이동 타입, 외부 pointer 노출 시 선택.
  • 작은 value에는 overhead 크다 — ValueMap 우선.
  • absl::node_hash_map과 등가.

#다음 편

F14VectorMap은 또 다른 접근이다. value를 별도 std::vector에 두고 chunk에는 index만 둔다. cache-friendly iteration이 가능하다.

#관련 항목

Folly Code Review · 32 of 89

  1. 1 Folly Code Review — Meta의 production-grade C++ 라이브러리 코드 분석
  2. 2 Folly 개요 — Meta가 production에서 검증한 utility 모음 분석
  3. 3 Folly vs Abseil 철학 비교 — performance-first vs std-compatible
  4. 4 Folly 빌드와 fbcode 환경 — monorepo의 그림자
  5. 5 Folly API stability 정책 — 어떤 보장도 없다는 솔직함
  6. 6 Folly production validation 문화 — peta-scale에서 단련된 코드
  7. 7 folly::Future 분석 — std::future의 한계를 넘는 composable async
  8. 8 folly::Promise·makeFuture — Future를 만드는 두 길
  9. 9 folly::SemiFuture vs Future — executor binding의 명시화
  10. 10 folly::Future thenValue·thenError·thenTry — continuation 체인 분석
  11. 11 folly::collect·collectAll·collectAny — fan-in 패턴 분석
  12. 12 folly::Future retry·window·via — 제어 흐름 조합자
  13. 13 folly::fibers 분석 — M:N stackful coroutine
  14. 14 folly::InlineExecutor — 호출자 thread에서 즉시 실행
  15. 15 folly::CPUThreadPoolExecutor — CPU-bound 작업의 표준 thread pool
  16. 16 folly::IOThreadPoolExecutor — libevent 기반 I/O pool
  17. 17 folly::ManualExecutor — 결정적 테스트를 위한 수동 진행
  18. 18 folly::EventBase 분석 — libevent 이벤트 루프의 핵심
  19. 19 folly::IOBuf 분석 — zero-copy buffer chain의 기본 단위
  20. 20 folly::IOBufQueue — chain의 push/pull 추상화
  21. 21 folly::io::Cursor·RWCursor — chain 위의 stream
  22. 22 folly Zero-copy 패턴 — IOBuf로 ScatterGather I/O 표현
  23. 23 folly::IOBuf shared semantics — clone·unshare·takeOwnership
  24. 24 folly::FBString 분석 — SSO + COW 구현
  25. 25 folly의 fmt::format 통합 — 모던 포맷팅 채택
  26. 26 folly::StringPiece — string_view 호환 분석
  27. 27 folly Join·Split utilities — 문자열 분해와 결합
  28. 28 folly::to·tryTo — text↔num 변환 분석
  29. 29 folly Conv Customization — 사용자 타입 지원
  30. 30 folly Conv 성능 비교 — sprintf·stringstream 대비
  31. 31 folly::F14ValueMap vs std::unordered_map
  32. 32 folly::F14NodeMap — stable pointer가 필요할 때
  33. 33 folly::F14VectorMap — cache-friendly iteration
  34. 34 folly::F14FastMap — auto-select 동작
  35. 35 folly F14 internals — SIMD probing 메커니즘
  36. 36 folly::small_vector — inline storage 분석
  37. 37 folly::FixedString — compile-time string
  38. 38 folly::AtomicHashMap — lock-free read 분석
  39. 39 folly::ConcurrentHashMap — sharded 동시 해시 맵
  40. 40 folly::EvictingCacheMap — LRU 구현 분석
  41. 41 folly::Synchronized — lock wrapper 패턴
  42. 42 folly::SharedMutex 분석
  43. 43 folly::Baton — one-shot wait 동기화
  44. 44 folly::RWSpinLock 분석
  45. 45 folly::PicoSpinLock — 1-byte spinlock
  46. 46 folly::ProducerConsumerQueue — SPSC 큐 분석
  47. 47 folly::MPMCQueue — multi-producer multi-consumer
  48. 48 folly::UnboundedQueue — 동적 크기 lock-free
  49. 49 folly::fibers::Channel — Go-like channel
  50. 50 folly::dynamic — JSON-like dynamic type 분석
  51. 51 folly JSON conversion — toJson·parseJson
  52. 52 folly dynamic ↔ struct — manual marshaling
  53. 53 folly dynamic Visitor pattern — type별 분기
  54. 54 folly::Singleton vs Meyers/static — 왜 Folly의 Singleton인가
  55. 55 folly::SingletonVault 분석 — 등록·소멸·의존성
  56. 56 folly::Singleton try_get·try_get_fast — TLS-cached 접근
  57. 57 folly::ExceptionWrapper — type-erased exception holder
  58. 58 folly::ScopeGuard·SCOPE_EXIT — RAII cleanup
  59. 59 folly::Optional vs std::optional
  60. 60 folly::Function vs std::function
  61. 61 folly::Lazy — 지연 초기화 wrapper
  62. 62 folly Meta 스타일 code review 패턴
  63. 63 folly anti-patterns — 잘못 쓰면 std보다 느림
  64. 64 folly vs std 선택 기준 분석
  65. 65 folly::coro 개요 — production C++20 코루틴 어댑터
  66. 66 folly::coro::Task — lazy single-shot 코루틴
  67. 67 folly::coro::AsyncGenerator — 비동기 스트림
  68. 68 folly coro blockingWait·collectAll — 동기 경계와 fan-in
  69. 69 folly::coro::Baton·Mutex — 코루틴-aware 동기화
  70. 70 folly::Expected — 결과 또는 오류
  71. 71 folly::Try — Future 결과 wrapper
  72. 72 folly::Try vs Expected 선택 기준
  73. 73 folly::Range — 일반 iterator pair
  74. 74 folly::Uri — URL 파서
  75. 75 folly Fingerprint64·128 — 분산 hash
  76. 76 folly SpookyHashV2 — fast non-crypto hash
  77. 77 folly::Init — main() 부트스트랩
  78. 78 folly::Indestructible — global lifetime 패턴
  79. 79 folly::MicroLock — 1-byte 락
  80. 80 folly::MicroSpinLock — 가장 좁은 spin lock
  81. 81 folly::format — legacy formatter 분석
  82. 82 folly::demangle — typeid 디망글링
  83. 83 folly::DynamicConverter — dynamic ↔ struct
  84. 84 folly::RecordIO — append-only 로그 파일 포맷
  85. 85 folly::io::Compression — zstd·lz4·snappy wrapper
  86. 86 folly::AsyncIO — io_uring·Linux AIO
  87. 87 folly::CancellationToken — 코루틴·Future 취소 전파
  88. 88 folly::observer — hot config의 atomic refresh
  89. 89 fbcode 패턴 모음 — folly 사용의 실전