1P by neo 1달전 | favorite | 댓글 1개

{fmt} 라이브러리의 바이너리 크기 최적화

  • {fmt} 라이브러리 소개

    • {fmt}는 작은 바이너리 크기로 유명한 포맷팅 라이브러리임
    • IOStreams, Boost Format, tinyformat 등과 비교해 함수 호출당 코드 크기가 훨씬 작음
    • 타입 소거(type erasure)를 통해 템플릿 부하를 최소화함
  • 타입 소거를 통한 포맷팅

    • format 함수는 vformat 함수로 작업을 위임함
    • 출력 반복자와 다른 출력 타입도 특별히 설계된 버퍼 API를 통해 타입 소거됨
    • 템플릿 사용을 최소화하여 바이너리 크기와 빌드 시간을 줄임
  • 예제 코드

    #include <fmt/base.h>
    int main() { fmt::print("The answer is {}.", 42); }
    
    • 위 코드는 IOStreams 코드보다 훨씬 작은 크기로 컴파일됨
    • printf와 비교해도 크기가 비슷하며, 런타임 타입 안전성을 제공함
  • 바이너리 크기 최적화

    • 2020년에 라이브러리 크기를 100kB 이하로 줄이는 작업을 수행함
    • 최신 버전(11.0.2)의 바이너리 크기는 75kB임
    • 로케일 지원을 비활성화하면 크기를 71kB로 줄일 수 있음
  • Bloaty 도구를 사용한 분석

    • 숫자 포맷팅, 특히 부동 소수점 숫자 포맷팅이 큰 부분을 차지함
    • 부동 소수점 지원이 필요하지 않다면 이를 비활성화할 수 있음
  • 타입별 포맷팅 최적화

    • FMT_BUILTIN_TYPES 매크로를 0으로 설정하여 int 타입만 특별히 처리하고 나머지 타입은 확장 API를 통해 처리함
    • 이 방법으로 바이너리 크기를 31kB로 줄일 수 있음
  • 로케일 아티팩트 제거

    • FMT_USE_LOCALE 매크로를 사용하여 로케일 아티팩트를 제거하면 크기를 27kB로 줄일 수 있음
  • 속도와 크기 간의 트레이드오프

    • FMT_OPTIMIZE_SIZE 매크로를 사용하여 크기를 최적화하면 바이너리 크기를 23kB로 줄일 수 있음
  • C++ 표준 라이브러리 의존성 제거

    • 예외를 비활성화하고 -nodefaultlibs 옵션을 사용하여 C++ 런타임 의존성을 제거함
    • mallocfree를 사용하는 커스텀 할당자를 도입하여 바이너리 크기를 14kB로 줄일 수 있음
  • 결과 확인

    • ldd 명령어를 사용하여 C++ 런타임 의존성이 제거되었음을 확인함

GN⁺의 정리

  • {fmt} 라이브러리는 작은 바이너리 크기와 런타임 타입 안전성을 제공하는 포맷팅 라이브러리임
  • 타입 소거와 매크로 설정을 통해 바이너리 크기를 크게 줄일 수 있음
  • C++ 표준 라이브러리 의존성을 제거하여 임베디드 시스템에서도 효율적으로 사용할 수 있음
  • 비슷한 기능을 제공하는 라이브러리로는 IOStreams, Boost Format, tinyformat 등이 있음
Hacker News 의견
  • {fmt}는 기본적으로 로케일에 독립적임
  • 부동 소수점 형식화에 많은 코드가 필요함
    • Dragonbox 프로젝트는 최적화된 코드로 읽어볼 가치가 있음
  • C++의 기본 할당자는 malloc과 free를 사용하지 않음
    • libc++의 기본 할당자가 libc의 malloc과 free를 호출하지 않는 이유를 궁금해함
  • printf(Hello, World!\n")을 1008 바이트의 실행 파일 크기로 가능하게 하는 프로젝트가 있음
    • 직접 비교는 어렵지만 참고할 만함
  • 빈 main 함수가 있는 C 프로그램이 6kB인 시스템에서 {fmt}는 바이너리에 10kB 미만을 추가함
    • 흥미로운 테스트임
  • 작은 형식화 라이브러리가 문자열과 정수를 출력하는 데 약 50 바이트가 필요할 것이라고 기대했음
    • 문자열은 약 4개의 명령어로 구성됨
    • 정수는 약 20개의 명령어로 구성됨
    • 부동 소수점은 많은 프로그램에서 사용되지 않으므로 필요할 때만 컴파일해야 함
    • 마이크로컨트롤러 코드 공간이 2킬로바이트인 경우 14킬로바이트의 문자열 형식화 라이브러리를 포함하지 않음
  • 이러한 생각의 틀을 벗어난 최적화가 매우 즐거움
  • "14k"가 "14kB"를 의미한다는 것을 깨닫는 데 시간이 걸렸음
  • fmt는 항상 문제를 일으킴
    • .NET에서도 동일한 문제가 발생함
    • 숫자 형식화/파싱을 많이 다루면 링커가 많은 부동 소수점 및 BigInt 관련 코드를 포함하게 되어 바이너리 크기가 커짐