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

folly::IOBuf 분석 — zero-copy buffer chain의 기본 단위

· Hawk · 4분 읽기

한 줄 요약: IOBuf는 ref-counted byte buffer의 연결 리스트다. 네트워크 코드에서 복사 없이 buffer를 분할/결합해 protocol layer를 쌓는다.

#동기 — 왜 std::vector<uint8_t>로는 안 되는가

네트워크 코드는 buffer를 조합/분할한다.

HTTP Response = [header bytes] + [chunked body] + [trailer]

std::vector<uint8_t>로 표현하면 연결할 때마다 복사가 일어난다. 100KB body에 1KB header를 prepend하면 101KB copy다. 10K connection에 매번 이러면 매초 1GB의 memcpy다.

IOBuf는 다른 접근이다. 각 조각을 별도 buffer로 두고 linked list로 연결한다. prepend는 새 buffer를 list 앞에 잇는 것이고 copy가 없다.

header IOBuf ──▶ body IOBuf ──▶ trailer IOBuf
[128B] [102400B] [16B]

write 시에는 scatter-gather I/O(writev)로 각 buffer를 그대로 socket에 보낸다. 끝까지 zero-copy다.

#할당 모델 — 왜 arena가 자연스러운가

IOBuf 노드 자체와 그 옆의 짧은 lifetime 객체(parser 상태, decoder context 등)는 request 단위로 묶여 만들어지고 응답이 나가면 한꺼번에 사라진다. 이런 패턴엔 arena가 정확히 맞다.

Arena allocator userspace

bump pointer로 노드 alloc은 O(1)이고, request 끝에 arena를 reset해 모든 노드를 한 번에 free한다. per-node free list 탐색이나 단편화가 없다.

#메모리 레이아웃

IOBuf circular chain

// folly/io/IOBuf.h (요약)
class IOBuf {
uint8_t* data_; // 현재 valid data 시작
uint64_t length_; // valid data 크기
uint8_t* buf_; // 전체 buffer 시작
uint64_t capacity_; // 전체 buffer 크기
IOBuf* next_; // chain 다음
IOBuf* prev_; // chain 이전
SharedInfo* shared_; // ref-count
uint64_t flagsAndSharedInfo_;
};

핵심 관계.

IOBuf Memory Layout

  • headroomdata_ - buf_. 앞쪽 여유. prepend 시 사용.
  • tailroomcapacity_ - (data_ - buf_) - length_. 뒤쪽 여유. append 시 사용.

이 구조 덕에 header를 복사 없이 앞에 붙일 수 있다. data_를 앞으로 옮기고 그 자리에 header를 쓴다.

#기본 사용

#include <folly/io/IOBuf.h>
// 1) 새 IOBuf 생성
auto buf = folly::IOBuf::create(1024); // capacity 1024
buf->append(100); // length를 100으로
// 2) 데이터 쓰기
std::memcpy(buf->writableTail() - 100, "hello", 5);
// 3) headroom 활용
buf->reserve(64, 0); // 앞에 64B 여유
buf->prepend(10); // length가 10 늘어남, data_가 앞으로
// 4) chain 연결
auto buf2 = folly::IOBuf::create(2048);
buf2->append(500);
buf->prependChain(std::move(buf2)); // buf ← buf2
// 5) 전체 길이
size_t total = buf->computeChainDataLength();

#ref-counted shared buffer

auto a = folly::IOBuf::create(1024);
a->append(100);
auto b = a->clone(); // ref-count 증가, data 공유
// a와 b는 같은 buffer를 가리킴 — copy 없음
a->writableData()[0] = 'x';
// b->data()[0] 도 'x' — 의도하지 않은 mutation 가능

clone()SharedInfo ref-count만 늘린다. 공유 buffer의 mutation은 위험하다. unshare()write 전 분리하거나, read-only로만 다룬다.

auto b = a->clone();
b->unshare(); // 자기 카피 생성, ref-count 분리
b->writableData()[0] = 'x'; // 안전

#takeOwnership — 외부 메모리 wrap

uint8_t* external = ...; // 외부 할당
auto buf = folly::IOBuf::takeOwnership(external, length, [external] {
free(external);
});

takeOwnership외부 메모리를 IOBuf로 wrap한다. ref-count가 0이 되면 lambda(free)가 호출된다. mmap-ed buffer, kernel-allocated buffer 등을 IOBuf chain에 섞을 수 있다.

#wrapBuffer — read-only wrap

const char* msg = "hello";
auto buf = folly::IOBuf::wrapBuffer(msg, 5);
// buf는 msg를 가리킴, ref-count 없음, free 안 함

wrapBuffer수명 관리 없이 외부 buffer를 가리키는 IOBuf를 만든다. lifetime은 caller가 보장해야 한다.

#chain 순회

auto current = buf.get();
do {
std::cout << "len=" << current->length() << "\n";
current = current->next();
} while (current != buf.get());

chain은 circular doubly-linked list다. next()가 head로 돌아오면 한 바퀴 돈 것이다.

#std와 비교

작업std::vectorfolly::IOBuf
prepend 1KBO(N) copyO(1) chain
splitsubstring copysplitAtMost() O(1)
chain serializeconcatenatewritev scatter-gather
sharecopyclone (ref-count)
외부 memory wrap불가takeOwnership

trade-off는 명확하다. IOBuf는 flat byte buffer가 아니다. random access가 chain 순회를 요구한다. scan/parse는 folly::io::Cursor(Part 4-03)로 추상화한다.

#적합한 사용

IOBuf 권장:

  • network 송수신 buffer
  • protocol header/body 조합
  • streaming pipe (file → network)
  • zero-copy I/O 가 필요한 곳

std::vector 권장:

  • 임의 접근이 빈번
  • buffer 크기가 작고 복사 비용이 무시 가능
  • 외부 라이브러리 인터페이스 (byte array as flat)

#코드 리뷰 포인트

  • 이 buffer를 prepend/append가 잦은가? IOBuf 후보.
  • buffer를 두 곳에서 share하는가? clone + ref-count.
  • 외부 메모리를 wrap하는가? takeOwnership(소유 인계) vs wrapBuffer(read-only).
  • chain 순회에서 head를 빠뜨리지 않았는가? circular list이므로 do-while로 head 포함.

#자주 보는 안티패턴

// 1. clone 후 mutation
auto b = a->clone();
b->writableData()[0] = 'x'; // a의 데이터도 바뀜 — unshare() 먼저
// 2. wrapBuffer + lifetime 미준수
auto buf = folly::IOBuf::wrapBuffer(tempStr.data(), tempStr.size());
return buf; // tempStr 소멸 — buf는 dangling
// 3. chain 길이를 매번 computeChainDataLength
for (...) {
if (buf->computeChainDataLength() > N) ... // O(chain length) — 매번
}
// 캐시하거나 IOBufQueue로
// 4. IOBuf chain 위에서 직접 parse
parse(buf->data(), buf->length()); // chain의 첫 노드만 본다 — 잘못
// → Cursor 사용

#정리

  • IOBuf는 ref-counted byte buffer의 circular doubly-linked list다.
  • headroom/tailroom 덕에 prepend/append가 복사 없이 가능하다.
  • clone()은 ref-count를 늘리고, unshare()로 분리한다.
  • takeOwnership/wrapBuffer로 외부 메모리를 chain에 섞는다.
  • random access는 Cursor로, queue 관리는 IOBufQueue로 추상화한다.
  • 네트워크 코드의 zero-copy 패턴의 기본 단위다.

#다음 편

Part 4-02: IOBufQueue에서 chain 관리 헬퍼를 본다.

#관련 항목

Folly Code Review · 19 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 사용의 실전