absl::Cord vs std::string — 선택 기준과 메모리 프로파일
#한 줄 요약
std::string은 연속 buffer, 자주 mutation, 작은 크기에 강하다. absl::Cord는 공유, 큰 크기, 잘라내기·합치기에 강하다. 두 타입의 cost model이 다르므로 선택은 데이터 lifecycle에 따라 정한다.
#한 장 정리
다음 표가 결정의 90%를 정한다.
| 사용 패턴 | std::string | absl::Cord |
|---|---|---|
| 크기 < 4KB | O | △ |
| 크기 > 64KB | △ | O |
| 빈번한 mutation (append/erase) | O | × |
| 한 번 만들고 다수에 전달 | △ | O |
| substring을 비동기로 전달 | × | O |
random s[i] 접근 | O | × |
연속 data() 필요 | O | × |
| RPC payload | × | O |
| log buffer | △ | O |
| mmap 흡수 | × | O |
| key for hash map | O | × |
#메모리 프로파일 비교
#std::string
libstdc++ x86_64 기준 sizeof = 32 byte.
+-------------------+| data_* (8B) | → heap 또는 inline buf| size_ (8B) || capacity_ (8B) || inline_buf_[15] | → 작은 문자열은 여기| +1 NUL |+-------------------+SSO 임계치를 넘으면 연속 heap buffer 한 개에 모든 byte. capacity 부족 시 전체 재할당 + memcpy.
#absl::Cord
sizeof = 16 byte (inline rep) 또는 16 byte (tree rep, root pointer만).
Inline (size ≤ 15B):+-------------------+| inline_buf[15] || tag (LSB=0) |+-------------------+
Tree (size > 15B):+-------------------+| CordRep* tree || reserved || tag (LSB=1) |+-------------------+큰 데이터는 외부 tree에 산다. tree 자체는 BTREE 노드 + leaf chunk들로 구성. 각 chunk는 refcount + 4KB FLAT 또는 외부 view.
#메모리 풋프린트 차이
10MB 문자열을 5개의 컴포넌트가 공유하는 경우:
std::string 5개 + 각자 복사: 10MB × 5 = 50MB
Cord 5개 + refcount 공유: 10MB × 1 + (16B × 5) = 10.00008MB거대 데이터의 공유 빈도가 높을수록 격차가 크다. 반대로 5개 컴포넌트가 각자 mutation을 한다면 결국 copy-on-write 비용을 부담해야 한다 (Cord의 mutation은 path 위 노드를 자체 복사).
#append/erase 비용 모델
| 연산 | std::string | absl::Cord |
|---|---|---|
append(view) | amortized O(n_new), reallocation 시 O(total) | O(log n + n_new) |
prepend | O(n_total) 항상 | O(log n + n_new) |
erase 중간 | O(n_total) | O(log n) (tree 분할) |
insert 중간 | O(n_total) | O(log n) |
substr | O(n_substr) alloc | O(log n) zero-copy |
std::string은 단순한 연속 buffer라 작은 mutation이 매우 빠르다. 중간 삽입이나 prepend는 비효율적. Cord는 위치 무관 O(log n)이지만 작은 mutation에도 tree 조작 비용을 항상 낸다.
#사용 사례별 권장
#1. 짧은 식별자, key
absl::flat_hash_map<std::string, User> users; // key는 std::stringhash map key는 == 비교가 잦고 메모리 locality가 필수. Cord는 불리.
#2. 큰 RPC payload
class RpcResponse { absl::Cord payload_; // 외부에서 받아 그대로 전달};받은 데이터를 내가 변형하지 않고 다른 컴포넌트로 흘려보낼 때 Cord가 적격. zero-copy 체인이 길수록 이득.
#3. log buffer 누적
// 회피 — 큰 string에 매번 appendstd::string log;for (auto& msg : msgs) log += msg; // 후반에 reallocation 폭발
// Good — Cord, 또는 std::string에 reserveabsl::Cord log;for (auto& msg : msgs) log.Append(msg);reserve로 미리 잡을 수 있다면 std::string도 OK. 크기 예측이 어려우면 Cord.
#4. file mmap 흡수
void* p = ::mmap(...);absl::Cord c = absl::MakeCordFromExternal( absl::string_view(static_cast<char*>(p), len), [fd, p, len](absl::string_view) { ::munmap(p, len); ::close(fd); });mmap 영역을 그대로 Cord로 만든다. 평탄화 복사 없음. std::string에서는 불가능한 패턴.
#5. 직렬화 buffer
protobuf의 SerializeToCord()는 Cord를 직접 만든다. 큰 메시지에서 alloc 횟수가 1/10 수준으로 줄어든다.
#잘못된 선택의 비용
#작은 데이터에 Cord
absl::Cord short_name("hello");inline rep에 들어가긴 한다(15 byte까지). 하지만 API 시그니처가 const Cord&라면 호출자가 임시 Cord를 만들어야 한다. std::string/string_view로 받았으면 그냥 view 한 번이면 끝났을 일.
#큰 데이터에 std::string 반복 prepend
std::string buf;for (auto& chunk : reverse_chunks) { buf = chunk + buf; // O(n^2) 누적}prepend는 std::string의 약점. 이런 패턴이 보이면 Cord 후보.
#Cord에 random access
absl::Cord c = ReadLargeFile();for (size_t i = 0; i < c.size(); ++i) { if (c[i] == '\n') ++lines; // O(n log n)}chunk 순회로 바꾸면 O(n).
size_t lines = 0;for (absl::string_view ch : c.Chunks()) { lines += std::count(ch.begin(), ch.end(), '\n');}#코드 리뷰 포인트
1. payload 타입 선택 근거를 PR에 적는다
std::string/Cord/string_view 중 선택은 데이터 흐름에 달려 있다. 리뷰어가 의도를 추측하지 않게 한 줄 주석.
// payload는 외부에서 mmap으로 받아 그대로 RPC로 흘림 → Cordabsl::Cord payload_;2. 함수 시그니처 const Cord& vs absl::string_view
// 호출자가 Cord 들고 있을 때만void Process(const absl::Cord& body);
// 더 일반적 — string_view, std::string, char*도 받음void Process(absl::string_view body);Cord 전용 API는 Cord의 zero-copy 이점을 살릴 때에만 정당하다. 단순 read-only면 string_view가 호출 측 유연성이 크다.
3. 변환 횟수 계측
Cord ↔ std::string 변환이 코드 경로에 반복되면 Cord의 이점이 사라진다. 변환 1회 = 전체 데이터 복사.
// 회피 — 경로 안에서 양방향 변환absl::Cord c = LoadFromDisk();std::string s(c); // 전체 복사auto result = Process(s);absl::Cord out(result); // 또 전체 복사return out;
// Good — Cord로 일관absl::Cord c = LoadFromDisk();return ProcessCord(c);4. 함수 반환 타입의 영향
// std::string 반환 — 호출자가 immediate 소비 가정std::string MakeReport();
// Cord 반환 — 호출자가 전달/저장 가정absl::Cord MakeReport();반환 타입이 그 함수의 결과 데이터 lifecycle에 대한 시그널이다. Cord 반환은 “쪼개거나 전달할 거다”라는 의도, std::string 반환은 “바로 사용할 거다”라는 의도.
#마이그레이션 체크리스트
기존 std::string 코드를 Cord로 옮길지 결정하는 빠른 체크.
| 신호 | 점수 |
|---|---|
| 평균 크기 > 64KB | +3 |
| 동일 데이터를 여러 컴포넌트가 공유 | +3 |
s.substr / prepend가 hot path | +2 |
| 외부 메모리 (mmap, RPC buf)를 흡수 | +3 |
random s[i] 접근이 잦음 | -2 |
| hash map의 key | -3 |
| 호출 빈도 극고, alloc 회피가 critical | +1 |
합산 점수 4 이상이면 Cord 검토. 3 이하면 std::string 유지.
#정리
- 결정은 크기·공유·mutation 패턴의 세 축으로.
- 작은 데이터·잦은 mutation·random access →
std::string. - 큰 데이터·공유·잘라내기 →
Cord. - hash key·짧은 식별자는 거의 항상
std::string. - 변환 횟수를 의식하고 한 데이터의 경로 전체에 일관된 타입을 쓴다.
#다음 편
Part 16에서 디버깅·CRC·프로파일링 도구를 본다. Part 16-01 — Stacktrace와 Symbolize.
#관련 항목
- Part 16-01 — Stacktrace / Symbolize
- Part 15-01 — Cord
- Part 15-02 — charconv
- Part 4-01 — string_view
- Folly Part 5-02 — IOBuf
Abseil Code Review · 76 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::Cord — 분산 시스템용 대용량 문자열
absl::Cord — tree 구조로 표현되는 immutable-ish 문자열. zero-copy concat, shared substring, Google 내부 RPC payload의 기본 표현.
같은 시리즈에서 이어 읽기
Abseil Memory utilities 분석
Part 2-06: absl::memory — make_unique polyfill, allocator_traits, RawPtr, uninitialized helpers.
같은 시리즈에서 이어 읽기
absl::PeriodicSampler — 적응형 샘플링·jitter 회피
absl::profiling_internal::PeriodicSampler — sampling rate를 동적으로 조정, geometric distribution으로 jitter 회피. 메모리 할당 추적·profiling 인프라의 기반.
같은 시리즈에서 이어 읽기