Story-time: C++, bounds checking, performance, and compilers
Story-time: C++, bounds checking, performance, and compilers 를 읽고.
요약
저자는 Chandler Carruth이고 Google에서 Distinguished Software Engineer로 일하고 있다고 한다.
-
과거 경계 검사의 성능 오버헤드에 대한 오해 작성자는 초기에는 경계 검사로 인해 성능이 크게 저하될 것이라는 회의적인 입장. 이는 과거 보고서들과 간단한 실험 결과에서 비롯된 믿음이었으며, 당시 컴파일러는 경계 검사를 효과적으로 최적화하지 못했기 때문에 기본적으로 활성화하는 것이 비현실적이라고 생각. 하지만 최근 결과를 통해, 경계 검사의 성능 오버헤드가 예상보다 매우 낮다는 사실을 확인했으며, 0.3% 수준의 오버헤드로도 전체 표준 라이브러리 타입에서 경계 검사가 실행 가능하다는 점을 강조.
-
경계 검사의 필요성에 대한 전환 과거에는 경계 검사가 비용이 크다고 믿었기 때문에 이를 구현하려는 노력이 부족했지만, 현재는 안전성을 위해 필수적인 요소로 재평가. 경계 검사가 없는 상태에서 발생하는 보안 위험과 오류 가능성을 줄이기 위해, 경계 검사를 모든 코드에서 기본적으로 활성화해야 한다는 입장으로 바뀜.
-
컴파일러와 경계 검사 최적화의 발전 경계 검사의 초기 성능 문제가 컴파일러가 이를 최적화하도록 설계되지 않았기 때문이라는 점을 지적. C와 C++은 안전하지 않은 추상화에 기반하여 설계되었기 때문에, 경계 검사를 처리할 인프라가 부족했다. 그러나 최근 LLVM과 GCC 같은 컴파일러가 경계 검사를 효과적으로 최적화할 수 있도록 발전했으며, 이는 경계 검사를 안전성과 성능 모두를 만족하는 기본 옵션으로 만들 가능성을 열어주었음.
글을 읽고 궁금한 것들
경계 검사를 지원하는 언어와 도구
- 언어 차원
- Rust, Swift: 경계 검사를 기본적으로 제공하며, 안전성을 강제.
- C, C++: 경계 검사가 기본적으로 제공되지 않으나, 추가 도구나 라이브러리를 통해 구현 가능.
- 도구 및 라이브러리
- AddressSanitizer (ASan): C/C++에서 경계 초과 문제를 실행 시 감지.
- UBSan (Undefined Behavior Sanitizer): 경계 초과 접근을 포함한 정의되지 않은 동작 감지.
공간적 안전성(Spatial Safety)과 시간적 안전성(Temporal Safety)
- 공간적 안전성
- 메모리 접근의 경계(boundary)와 관련된 문제를 방지하는 것
- 이는 프로그램이 할당된 메모리 영역 내에서만 데이터를 읽거나 쓰도록 보장하는 개념
- 즉, 배열이나 포인터를 사용할 때, 할당된 범위를 넘어서는 접근을 방지하는 것이 목적
- 주요 문제
- 버퍼 오버플로, 버퍼 언더플로
- 해법
- 경계 검사, 메모리 보호 도구(Address Sanitizer), 안전한 언어
- 시간적 안전성
- 시간적 안전성은 메모리 접근의 수명(lifetime)과 관련된 문제를 방지하는 것을 말함.
- 이는 프로그램이 유효하지 않은 메모리(이미 해제되었거나 할당되지 않은 메모리)에 접근하지 않도록 보장하는 개념.
- 주요 문제
- Dangling pointer, Double free, user-after-free
- 해법
- 스마트 포인터 사용, 참조 카운팅, 동적 분석 도구(Valgrind), GC,
PGO (Profile-Guided Optimization)와 FDO (Feedback-Directed Optimization)란?
PGO와 FDO는 프로그램 최적화를 위한 방법론으로, 프로그램 실행 시 수집된 데이터를 기반으로 컴파일러가 코드를 최적화한다는 공통점을 가지고 있음. 다만, 사용하는 방식과 목적에 따라 차이점이 존재.
- PGO (Profile-Guided Optimization)
- PGO는 프로그램의 실행 프로파일(프로그램 동작 데이터를 포함)을 사전에 수집한 후, 이를 기반으로 최적화를 수행하는 방식.
- 컴파일러는 코드에서 자주 실행되는 부분(핫스팟)을 파악하고, 이러한 데이터를 활용해 성능을 극대화.
- 작동 방식
- 프로파일 데이터 수집
- 프로그램 실행 시 다양한 입력 데이터와 시나리오로 실행하여 프로파일 데이터(실행 빈도, 분기 확률 등)를 수집.
- 프로파일 데이터 분석
- 컴파일러가 수집된 데이터를 분석하여 자주 실행되는 코드와 그렇지 않은 코드(콜드 코드)를 구분.
- 최적화 컴파일
- 핫스팟(자주 실행되는 코드)은 성능을 최적화하고, 콜드 코드(거의 실행되지 않는 코드)는 공간 효율성을 높이도록 최적화.
- 프로파일 데이터 수집
- 예시
- GCC, LLVM/Clang의 -fprofile-generate 및 -fprofile-use
- FDO (Feedback-Directed Optimization)
- FDO는 PGO와 유사하지만, 실행 중(runtime) 데이터를 수집하여 실시간으로 최적화를 수행하는 방식을 포함합니다. 동적 최적화가 가능한 것이 FDO의 특징입니다.
- 작동 방식
- 실행 데이터 수집
- 프로그램 실행 중 컴파일러나 런타임 시스템이 데이터를 수집.
- 예: 분기 예측 실패, 캐시 미스 발생 빈도 등.
- 동적 최적화
- 런타임 시스템이 실시간으로 데이터를 분석하고, 적합한 최적화를 적용.
- 정적으로 컴파일된 코드도 실행 중 다시 최적화 가능.
- 실행 데이터 수집
- 예시
- Java JIT(Just-In-Time) 컴파일러, 일부 LLVM 런타임 최적화 기법
Leave a comment