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

folly::to·tryTo — text↔num 변환 분석

· Hawk · 4분 읽기

#한 줄 요약

folly::to<T>(src)는 source/target 타입 쌍을 컴파일 타임에 dispatch해 변환을 수행한다. 실패 시 throw. folly::tryTo<T>(src)Expected<T, ConversionCode>를 반환해 throw 없이 분기한다.

#동기

C++의 변환 API는 산만하다.

  • std::stoi → 예외, locale 의존, partial parse.
  • std::from_chars → exception-free, locale 비의존이지만 C++17이고 API가 낮은 레벨.
  • std::to_string → locale 비의존이나 부동소수 출력이 부정확.
  • boost::lexical_cast → 통일된 API지만 stringstream 기반이라 느리다.

fbcode는 log 한 줄, ID parsing, query string 같은 곳에서 매 마이크로초가 보이는 경로다. 통일된 빠른 API가 필요했다.

int n = folly::to<int>("42");
std::string s = folly::to<std::string>(3.14);
double d = folly::to<double>(s);
auto r = folly::tryTo<int>("not-a-number");
if (r.hasValue()) Use(*r);

#API & 사용법

#include <folly/Conv.h>
// 1. throw-on-error
int i = folly::to<int>("42"); // OK
int j = folly::to<int>("abc"); // throws ConversionError
auto s = folly::to<std::string>(42); // "42"
auto d = folly::to<double>("3.14");
// 2. 부분 parse — view를 진행시키며 자른다
folly::StringPiece sp = "42,17";
int x = folly::to<int>(&sp); // x=42, sp=",17"
// 3. tryTo — Expected 반환
folly::Expected<int, folly::ConversionCode> r = folly::tryTo<int>("99");
if (r) { Use(*r); }
else { LOG(WARNING) << "code=" << static_cast<int>(r.error()); }
// 4. 여러 인자를 한 문자열로
std::string s2 = folly::to<std::string>("id=", id, ",v=", val);
// 5. variadic — output buffer에 append
std::string out;
folly::toAppend("count=", n, " of ", total, &out);

folly::to<std::string>(a, b, c)+ operator overload 없이 가변 인자를 받아 한 번에 만든다. 모든 인자의 size를 미리 계산해 reserve 한다.

#내부 구현

#dispatch 구조

// 약식 — folly/Conv.h
template <class Tgt, class Src>
Tgt to(const Src& src) {
if constexpr (std::is_same_v<Tgt, Src>) return src;
else return detail::Converter<Tgt, Src>::convert(src);
}

Converter는 SFINAE/if constexpr로 source-target 쌍마다 specialize 된다. 주요 분기는 다음과 같다.

Source → Target구현
integer → stringto_ascii_base10 (lookup table)
float → stringRyu(Grisu3) 또는 dtoa
string → integerdigits_to로 자릿수별 SWAR
string → floatstrtod 또는 자체 fast parser
variadic → string각 size 계산 → reserve → append

#integer → string의 자릿수 lookup

// 약식 — 2자리씩 처리
constexpr char digits[] =
"0001020304050607080910111213141516..." // "00".."99"
;
uint32_t to_ascii_base10(char* buf, uint64_t v) {
// 8자리씩 SWAR 분할
// 마지막 2-3자리만 1자리씩 처리
// ...
}

itoa 구현이 char by char 처리한다면 folly는 2-digit pair를 lookup table로 한 번에 쓴다. 10^16 까지의 정수에서 약 2배 빠르다.

#string → integer의 SWAR

// 약식 — 8 바이트를 한 word로 처리
uint64_t parse_8_digits_swar(const char* s) {
uint64_t word;
memcpy(&word, s, 8);
word -= 0x3030303030303030ULL; // '0' 8개 빼기
// multiply chain 로 자리수 결합
// ...
return result;
}

8자리 정수를 한 instruction 흐름으로 parse. fbcode의 query string·log 라인 parsing에서 큰 차이.

#tryTo의 Expected 반환

// 약식
template <class Tgt, class Src>
Expected<Tgt, ConversionCode> tryTo(const Src& src) {
Tgt result;
auto code = detail::Converter<Tgt, Src>::tryConvert(src, &result);
if (code == ConversionCode::SUCCESS) return result;
return makeUnexpected(code);
}

tryConvert는 errno 같은 thread-local 상태 없이 enum을 직접 반환한다. exception unwinding cost가 hot path에서 사라진다.

ConversionCode는 EMPTY_INPUT, INVALID_LEADING_CHAR, NON_DIGIT_CHAR, OVERFLOW, NEGATIVE_OVERFLOW 등 12개 정도. 호출자가 어떤 실패인지 분기 가능하다.

#std/abseil 비교

// std::from_chars (C++17) — 저수준이지만 가장 빠른 표준
int v;
auto r = std::from_chars(sv.data(), sv.data() + sv.size(), v);
if (r.ec == std::errc{}) { /* ok */ }
// abseil
int v2;
if (absl::SimpleAtoi("42", &v2)) { /* ok */ }
std::string s = absl::StrCat(1, ",", 2.5);
// folly
int v3 = folly::to<int>("42"); // throws
auto r3 = folly::tryTo<int>("42"); // Expected
std::string s2 = folly::to<std::string>(1, ",", 2.5);
항목std::from_charsabsl::SimpleAtoifolly::tryTo
Exception없음없음tryTo는 없음, to는 throw
Returnfrom_chars_resultboolExpected<T, Code>
FloatC++17 (libstdc++ 늦게)SimpleAtodtryTo<double>
Locale비의존비의존비의존
가변 to_stringXStrCat, StrAppendto<std::string>(...), toAppend

세 라이브러리 모두 locale 비의존. 표준이 from_chars를 늦게 도입한 탓에 folly와 abseil이 더 풍부한 API를 제공한다.

#코드 리뷰 포인트

// Bad — hot path에서 throw 의존
for (auto& s : tokens) {
try {
Process(folly::to<int>(s));
} catch (...) { Skip(); }
}
// Good — tryTo로 분기
for (auto& s : tokens) {
if (auto r = folly::tryTo<int>(s)) Process(*r);
else Skip();
}

throw는 happy path가 throw 비율이 낮으면 빠르지만, throw 비율이 높으면 수백 배 느려진다. 외부 입력 parsing은 항상 tryTo.

// Bad — std::string 누적
std::string s;
for (int i = 0; i < N; ++i) {
s += folly::to<std::string>(i) + ","; // 매번 임시 할당
}
// Good — toAppend로 in-place
std::string s;
for (int i = 0; i < N; ++i) {
folly::toAppend(i, ",", &s); // s에 직접 append
}

toAppend는 임시 string을 만들지 않고 target buffer에 바로 쓴다.

#안티패턴

  • std::to_string(double)을 fbcode에 섞기: locale 의존 출력이라 다른 노드에서 다른 결과. folly::to<std::string>(double) 또는 fmt::format로 통일.
  • tryToExpected 검사를 value(): error case에서 throw. if (r) *r 또는 r.value_or(default).
  • 부분 parse에 view 진행 미적용: folly::to<int>(&sp)는 sp를 진행시킨다. 같은 입력을 두 번 parse 하면 안 된다.

#정리

  • to<T>(src)는 통일된 변환 API, 실패 시 throw.
  • tryTo<T>(src)Expected<T, ConversionCode> 반환, 외부 입력에 적합.
  • variadic to<std::string>(...), toAppend(...)로 한 번에 누적.
  • 내부적으로 lookup table, SWAR로 std::stoi/std::to_string 대비 2-5배 빠름.
  • locale 비의존. std::from_chars보다 high-level이고 풍부.

#다음 편

다음은 사용자 정의 타입을 folly::to 변환 가능하게 만드는 customization point를 본다.

#관련 항목

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