absl::flat_hash_set — set 버전 Swiss Table
#한 줄 요약
absl::flat_hash_set은 flat_hash_map의 set 대응이다. value 자체가 key 역할을 하며, Swiss Table 구조와 성능 특성, iterator 무효화 규칙은 동일하다. dedup, membership test, set 연산에서 std::unordered_set 대비 같은 성능 우위를 누린다.
#동기
flat_hash_set은 flat_hash_map<K, void>로 생각하면 된다. (key, value)가 아니라 key만. 사용 시나리오:
- 중복 제거 (dedup)
- 빠른 membership test
- 작은 집합 연산(intersection, union)
absl::flat_hash_set<std::string> seen;for (const auto& line : input) { if (seen.insert(line).second) { Process(line); // 첫 등장만 처리 }}insert(x).second가 처음 삽입했는가를 알려준다. 이는 표준 set의 관용구다.
#API와 사용법
#include "absl/container/flat_hash_set.h"
absl::flat_hash_set<int> s = {1, 2, 3};
s.insert(4);s.emplace(5);
if (s.contains(3)) { /*...*/ }if (s.count(3) > 0) { /*...*/ } // 동일if (auto it = s.find(3); it != s.end()) { /*...*/ }
s.erase(1);C++20부터 std::unordered_set에도 contains가 있지만, Abseil은 처음부터 제공했다. 가독성 측면에서 contains를 선호.
#heterogeneous lookup
absl::flat_hash_set<std::string> words;words.insert("hello");
absl::string_view query = "hello";if (words.contains(query)) { /*...*/ } // no allocif (words.contains("hello")) { /*...*/ } // no allocstd::unordered_set<std::string>::contains(string_view)는 C++20까지도 transparent 동작이 약하다. heterogeneous lookup이 자연스럽게 동작하는 점이 코드 가독성에 크다.
#set 연산
표준 set과 마찬가지로 <algorithm>의 set 연산은 정렬된 입력을 가정하므로 hash set에는 부적합하다. 직접 작성한다.
// 교집합absl::flat_hash_set<int> Intersect(const absl::flat_hash_set<int>& a, const absl::flat_hash_set<int>& b) { const auto& smaller = a.size() < b.size() ? a : b; const auto& larger = a.size() < b.size() ? b : a; absl::flat_hash_set<int> r; r.reserve(smaller.size()); for (int x : smaller) { if (larger.contains(x)) r.insert(x); } return r;}작은 쪽을 순회하고 큰 쪽에 contains로 조회하는 것이 O(min(|a|,|b|))이라 표준 코드보다 빠르다.
#std::unordered_set 비교
차이는 flat_hash_map 대 std::unordered_map과 동일하다.
| 항목 | std::unordered_set | absl::flat_hash_set |
|---|---|---|
| storage | 노드 chain | open-addressing flat array |
| lookup cache miss | 2~3회 | 0~1회 |
| iterator 안정성 | insert/erase에 stable | rehash·erase에서 무효화 |
| heterogeneous lookup | C++20 부분 | 처음부터 강력 |
| 평균 lookup | baseline | ~2-3x |
#내부 구현
flat_hash_set은 flat_hash_map<T, void>의 type alias가 아니다(value type이 없는 별도 instantiation). 다만 Swiss Table 코어를 공유하므로 Part 5-07에서 설명할 control byte / SIMD 스캔 / probing 전략이 그대로 적용된다.
#코드 리뷰 포인트
1. count → contains
// 회피if (s.count(x)) { /*...*/ }
// Goodif (s.contains(x)) { /*...*/ }count는 multiset 시절의 유산이다. flat_hash_set에서 count는 항상 0 또는 1이다. contains가 의도 표현이 더 좋다.
2. dedup 패턴
// 회피 — O(n²)std::vector<std::string> dedup;for (const auto& s : input) { if (std::find(dedup.begin(), dedup.end(), s) == dedup.end()) { dedup.push_back(s); }}
// Good — O(n) 평균absl::flat_hash_set<absl::string_view> seen;std::vector<absl::string_view> dedup;for (absl::string_view s : input) { if (seen.insert(s).second) dedup.push_back(s);}순서 보존 dedup의 표준 패턴.
3. 큰 element는 unique_ptr
// 회피absl::flat_hash_set<BigStruct> s; // rehash 시 대량 이동
// Goodabsl::flat_hash_set<std::unique_ptr<BigStruct>, ByPtrHash, ByPtrEq> s;// 또는absl::node_hash_set<BigStruct> s;flat_hash_map과 동일한 트레이드오프.
#안티패턴
ordered traversal 가정
hash set은 순서 보장이 없다. 같은 process 안에서도 hash seed가 매 실행마다 달라질 수 있다. 결정적 순서가 필요하면 absl::btree_set 또는 결과를 std::sort.
큰 value를 flat_hash_set에 직접
rehash 시 모든 원소가 이동된다. 무거운 객체는 indirection을 통한다.
#정리
flat_hash_set은flat_hash_map의 set 대응, 성능 특성과 무효화 규칙 동일.contains로 가독성 좋은 membership test.- heterogeneous lookup으로
string_view조회에 alloc 없음. - dedup, set 연산은
insert(x).second또는contains기반 패턴. - 큰 원소는
unique_ptr또는node_hash_set.
#다음 편
Part 5-03 — node_hash_map에서 stable pointer가 필요한 경우의 대안을 본다.
#관련 항목
Abseil Code Review · 29 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 회피
관련 글
absl::flat_hash_map — Swiss Table 기반 hash map
Part 5-01: absl::flat_hash_map — Swiss Table 채택 배경, std::unordered_map 대비 cache locality와 성능, pointer/iterator 안정성 트레이드오프.
같은 시리즈에서 이어 읽기
Abseil algorithm container 확장 — c_sort·c_find_if·c_count_if
absl::c_* algorithm wrapper — container 전체를 받아 begin/end 자동 처리, STL algorithm의 한 줄 boilerplate를 제거.
같은 시리즈에서 이어 읽기
Abseil Swiss Table internals — control byte·SIMD probing
Part 5-07: Swiss Table 내부 — H1/H2 해시 분할, control byte 그룹, SSE2/SSE3/NEON SIMD probing, tombstone 처리.
같은 시리즈에서 이어 읽기