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

absl::flat_hash_set — set 버전 Swiss Table

· Hawk · 3분 읽기

#한 줄 요약

absl::flat_hash_setflat_hash_map의 set 대응이다. value 자체가 key 역할을 하며, Swiss Table 구조와 성능 특성, iterator 무효화 규칙은 동일하다. dedup, membership test, set 연산에서 std::unordered_set 대비 같은 성능 우위를 누린다.

#동기

flat_hash_setflat_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 alloc
if (words.contains("hello")) { /*...*/ } // no alloc

std::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_mapstd::unordered_map과 동일하다.

항목std::unordered_setabsl::flat_hash_set
storage노드 chainopen-addressing flat array
lookup cache miss2~3회0~1회
iterator 안정성insert/erase에 stablerehash·erase에서 무효화
heterogeneous lookupC++20 부분처음부터 강력
평균 lookupbaseline~2-3x

#내부 구현

flat_hash_setflat_hash_map<T, void>의 type alias가 아니다(value type이 없는 별도 instantiation). 다만 Swiss Table 코어를 공유하므로 Part 5-07에서 설명할 control byte / SIMD 스캔 / probing 전략이 그대로 적용된다.

#코드 리뷰 포인트

1. countcontains

// 회피
if (s.count(x)) { /*...*/ }
// Good
if (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 시 대량 이동
// Good
absl::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_setflat_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. 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 회피