효율적인 C++ 코드 작성법, 메모리 계층과 캐시부터
원제 Writing Efficient C++ Code (2013)
118 포인트댓글 69
Key Point
메모리 캐시와 데이터 구조 선택이 성능에 미치는 영향과 구체적 최적화 방법을 배워야 하는 개발자, 특히 게임·시스템 프로그래밍을 다루는 인원에게 실제 적용 가능한 가이드를 제공한다.
핵심 요약
- C++는 고수준 기능(OOP, STL)과 저수준 제어(메모리 관리, 하드웨어 접근)를 모두 제공하는 유일한 언어로, 게임·실시간 처리처럼 성능이 중요한 분야에서 자주 선택된다.
- 많은 프로그래머가 OOP를 개념적으로만 생각하지만, 메모리 계층·캐시·포인터 앨리어싱 같은 저수준 이해 없으면 비효율적인 코드가 나온다.
- Data-Oriented Design(DOD)은 OOP와 다르지 않게, 데이터 레이아웃을 먼저 설계하고 그에 맞춘 알고리즘을 짜는 방식으로, 수많은 작은 객체의 포인터 호핑보다 배열의 순차 처리가 병렬화하기 쉽고 캐시 효율이 높다.
- CPU와 RAM의 속도 격차는 수백 사이클로 벌어져 있어, 최신 프로세서의 L1 캐시 접근은 약 1ns(3 사이클), 메인 메모리는 그 몇십 배 느리다.
- 캐시는 64바이트 라인 단위로 데이터를 읽으므로, 자주 함께 쓰는 값들을 메모리에서 인접하게 배치하면 캐시 히트율을 높일 수 있다.
- AOS(Array of Structures) 대신 SOA(Structure of Arrays)를 쓰면, 입자 위치 업데이트 같은 특정 작업에서 필요한 데이터만 메모리에 연속으로 배치되어 캐시 효율이 올라간다.
- std::set·std::map 같은 컨테이너는 힙에 개별 할당되므로 캐시 미스가 많지만, 정렬된 배열에 이진 검색을 쓰면 시간 복잡도는 같으면서 캐시 성능이 훨씬 낫다.
- 포인터 앨리어싱 문제처럼 컴파일러가 값이 언제 바뀔지 확실하지 않으면 레지스터에 유지하지 못해, __restrict나 복사로 명시적으로 해결해야 한다.
- 루프 내에서 선언한 std::string을 루프 밖으로 옮기고 clear()만 호출하는 식으로 할당·해제를 줄이면 25% 성능 향상이 가능하다.
- 메모리 할당·STL 컨테이너·예외 처리 같은 일반적 메커니즘은 좋지만, 자신의 문제에 맞춘 커스텀 할당자나 EASTL 같은 최적화 라이브러리가 더 나을 수 있다.
- Donald Knuth의 '조기 최적화는 악'이라는 말은 97%의 사소한 효율을 무시하라는 뜻이지, 설계부터 언어 습관까지 성능을 생각하지 말라는 뜻이 아니다.
- 프로파일로 찾은 병목을 최적화하는 사후 방식도 있지만, 코드 전체가 비효율적이면 한두 함수 최적화로는 부족할 수 있으므로 매일의 프로그래밍 습관이 중요하다.