Featured image of post [Optimization(C++) 09] Modern C++ 기능

[Optimization(C++) 09] Modern C++ 기능

C++17/20/23의 성능 관련 기능인 ranges, concepts, modules 등을 정리합니다. 새 표준 기능의 비용·이점을 측정하고, Low-latency 코드에서의 활용 기준을 다루며, 컴파일러 지원과 마이그레이션 시 주의점을 제시합니다.

Modern C++ 기능이란 C++17/20/23에서 도입된 ranges, concepts, modules 등 성능·표현력에 영향을 주는 기능을 말합니다. 본 챕터에서는 이들의 비용과 이점을 정리하고, Low-latency 코드에서 활용할 때 측정·기준을 다룹니다.

이 장을 읽기 전에

완전한 초보자? 이 장은 08장: 템플릿/constexpr의 컴파일 타임 개념을 전제로 합니다. C++17/20/23이라는 표준 버전 구분과 ranges·concepts가 “표현력을 위한 기능"이라는 정도만 알면 충분합니다.

이 장의 깊이: 이 장은 중급~전문가를 포괄합니다. ranges·concepts·modules의 성능 특성을 정리하는 것부터 시작해, 전문가 구간에서는 ranges 파이프라인이 인라인되는지, concepts가 런타임 비용 없이 제약만 거는지 등 “표현력 vs 런타임 비용"을 구분하는 기준을 다룹니다. 다루지 않는 것: 모듈의 빌드 시스템 통합 세부(Tr.02 컴파일러 트랙)와 코루틴(10장)입니다.

당신의 수준에 맞는 경로

수준읽을 부분핵심 목표
초보자“Ranges (C++20)” ~ “Concepts (C++20)”기능의 목적과 런타임 비용 유무 이해
중급자“Modules (C++20)” ~ “기타 C++17/20/23 기능 (성능 관점)”핫패스에 쓸지 측정으로 판단
전문가“판단 기준” ~ “비판적 시각”표현력과 성능의 트레이드오프 선택

C++17/20/23 성능 관련 기능 (역사·배경)

Ranges는 C++20에서 표준에 들어왔고, 범위 기반 연산과 뷰·지연 평가를 제공합니다. Concepts도 C++20에서 도입되어 템플릿 제약을 타입 시스템으로 표현합니다. Modules는 C++20에서 표준화되어 헤더 대신 모듈 단위로 인터페이스를 나누어 컴파일 시간과 노출 범위를 줄입니다. 각 기능은 “표현력·안전성·빌드"에 이점이 있으나, Low-latency에서는 런타임 오버헤드가 있는지(예: ranges 파이프라인 인라인, concepts의 런타임 비용 없음)를 구분해 핫패스에 쓸지 결정해야 합니다.

“A range is a sequence of elements that can be iterated. A view is a non-owning range that is O(1) to copy and move.” — cppreference (std::ranges). 뷰는 복사가 저렴하고 지연 평가되므로, 파이프라인으로 연결해도 중간 컨테이너 할당을 줄일 수 있습니다.

Ranges (C++20)

Ranges 라이브러리는 **범위(range)**를 인자로 두고, **뷰(view)**를 파이프 |로 연결해 연산을 표현합니다. 많은 뷰는 지연 평가되어 최종적으로 소비(순회·수집)될 때만 연산이 실행됩니다. 뷰는 대체로 복사 없이 원본 범위를 참조하므로 중간 컨테이너 할당을 줄일 수 있습니다.

뷰를 파이프 |로 연결하면 중간 결과 없이 최종 소비 시점에만 연산이 실행됩니다.

1
2
3
4
5
6
7
8
#include <ranges>
#include <vector>

std::vector<int> v = {1, 2, 3, 4, 5};
auto r = v | std::views::filter([](int x) { return x % 2 == 0; })
           | std::views::transform([](int x) { return x * 2; });
// r은 뷰; 순회할 때만 filter·transform 실행. 중간 vector 할당 없음.
for (int x : r) { /* 4, 8 */ }

ranges vs 수동 루프: 위 파이프라인은 다음 for 루프와 동일한 결과를 냅니다.

1
2
3
4
5
for (int x : v) {
  if (x % 2 != 0) continue;   // filter
  int y = x * 2;              // transform
  // use y
}

선언적 파이프라인은 가독성이 좋지만, 구현체·최적화 수준에 따라 어댑터 객체와 반복자 래핑이 충분히 인라인되지 않으면 µs 경로에서 오버헤드가 보일 수 있습니다. 핫패스에서는 두 버전을 같은 알고리즘으로 벤치마크해 오버헤드가 허용 범위인지 확인한 뒤 사용하는 것이 안전합니다.

Concepts (C++20)

Concepts는 템플릿 파라미터에 제약을 걸어, 컴파일 타임에 타입이 요구사항을 만족하는지 검사합니다. 에러 메시지가 명확해지고 오버로드 해석이 더 잘 짜이며, 만족하는 타입에 대해서는 기존 템플릿과 동일하게 인라인·최적화되어 추가 런타임 오버헤드가 없습니다.

아래는 template<std::integral T> 단축 표기와 requires 절을 함께 보여 줍니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <concepts>

template <std::integral T>          // T는 정수 타입이어야 함
constexpr T clamp_low(T v, T lo) {
  return v < lo ? lo : v;
}

template <typename T>
  requires std::floating_point<T>   // requires 절로도 동일하게 제약
constexpr T half(T v) { return v / T{2}; }

static_assert(clamp_low(3, 5) == 5);
// clamp_low(3.0, 5.0);  // 컴파일 오류: double은 std::integral을 만족하지 않음

성능 트랙에서는 “concepts는 컴파일 타임 제약일 뿐 런타임 비용이 없다"는 점을 인지하고, 표현력·검사용으로 활용합니다.

Modules (C++20)

Modules는 번역 단위 단위로 인터페이스와 구현을 나누어, 헤더를 반복 파싱하지 않고 컴파일 시간을 줄이고 빌드 캐시를 활용할 수 있게 합니다. 모듈에서 export한 선언만 보이므로 노출 범위가 작아져 인라인·최적화 경계가 더 예측 가능해질 수 있습니다.

1
2
3
4
// math.ixx — 모듈 인터페이스 단위(interface unit)
export module math;

export int add(int a, int b) { return a + b; }
1
2
3
4
// main.cpp — 모듈 사용 측
import math;             // 헤더 #include 대신 import

int main() { return add(2, 3); }

다만 아직 구현체·빌드 시스템·도구 지원이 플랫폼마다 다르므로(모듈 인터페이스 확장자·빌드 플래그가 컴파일러마다 상이함), 도입 시 빌드 설정과 성능을 프로젝트에서 측정해 보는 것이 좋습니다.

기타 C++17/20/23 기능 (성능 관점)

  • if constexpr: 컴파일 타임에 분기가 결정되므로 선택되지 않은 경로의 코드는 생성되지 않고 런타임 분기도 없습니다. 템플릿 안에서 타입·상수에 따라 다른 구현을 택할 때 유용합니다(챕터 08 참고).
  • 구조화된 바인딩: 튜플·구조체 멤버를 바인딩할 때 복사/참조를 명시할 수 있어 불필요한 복사를 줄이기 쉽습니다.
  • [[likely]] / [[unlikely]]: 분기 예측 힌트로, CPU가 해당 경로를 선호하도록 할 수 있습니다. 효과는 플랫폼·컴파일러에 따라 다르므로 중요한 경로에만 쓰고 벤치마크로 확인합니다.
  • std::format, constexpr 확장: 포맷·문자열 처리와 constexpr 가능 범위가 넓어져, 상수 문자열 생성·포맷을 컴파일 타임에 처리할 수 있는 경우가 늘었습니다.

평가 기준 (학습 성과 목표)

  • Ranges의 지연 평가·뷰(non-owning) 특성을 설명하고, 핫패스에서 벤치마크 후 오버헤드가 허용되는지 판단할 수 있다.
  • Concepts가 런타임 오버헤드 없이 컴파일 타임 제약만 추가함을 설명하고, template<std::integral T>·requires 절을 작성할 수 있다.
  • Modules가 컴파일 시간·노출 범위에 미치는 영향을 설명하고, 도입 시 빌드·성능을 측정할 수 있다.
  • if constexpr, 구조화된 바인딩, [[likely]]/[[unlikely]] 등이 성능에 미치는 맥락을 구분할 수 있다.

판단 기준 (언제 쓸고 언제 피할지)

상황권장비권장
ranges 파이프라인벤치마크 후 오버헤드 허용 시µs 경로에 무검증 도입
템플릿 제약concepts로 가독성·에러 메시지 개선런타임 비용 기대 (없음)
빌드 시간·모듈화modules 실험·측정 후 도입도구 미지원 환경에서 강제

적용 체크리스트: (1) 핫패스에 ranges 넣기 전 동일 알고리즘으로 벤치마크. (2) concepts는 표현력·검사용, 런타임 비용 없음 인지. (3) [[likely]]/[[unlikely]]는 중요 경로에만, 효과는 측정으로 확인.

비판적 시각: 한계와 트레이드오프

  • Ranges: 구현체·최적화에 따라 파이프라인 오버헤드가 있을 수 있다. “무조건 for 루프"가 아니라, 가독성과 성능을 벤치마크로 맞보는 것이 합리적이다.
  • Modules: 빌드 시스템·컴파일러 지원이 아직 불균일하므로, 프로젝트별로 도입 가능 여부와 이득을 평가해야 한다.

핵심 요약

항목요약
Ranges지연 평가·뷰, 중간 할당 감소; 핫패스는 벤치마크 후 사용
Concepts컴파일 타임 제약, 런타임 오버헤드 없음
Modules컴파일 시간·노출 감소, 도구 지원 확인 필요
기타if constexpr·구조화 바인딩·[[likely]] 등 상황별 활용

용어 정리

용어설명
range순회 가능한 요소 시퀀스; C++20 std::ranges
viewnon-owning, 복사 O(1), 지연 평가되는 range
concept템플릿 파라미터 제약; 컴파일 타임 검사, 런타임 비용 없음
module헤더 대체; export한 선언만 노출, 빌드 시간·노출 감소

벤치마크 결과 해석 가이드

관찰해석·다음 단계
ranges 파이프라인이 for 루프보다 느림구현체·컴파일러에 따라 다름; 동일 알고리즘으로 벤치마크 후 도입
concepts 도입 후 런타임 차이 없음정상; concepts는 컴파일 타임 제약, 런타임 오버헤드 없음
modules 도입 후 빌드 시간 감소컴파일 시간 절감; 도구 지원 확인

자주 묻는 질문 (FAQ)

Q: ranges가 항상 for 루프보다 느리나요?
A: 구현체·최적화에 따라 다릅니다. 동일 알고리즘으로 벤치마크한 뒤, 허용 가능한 오버헤드일 때만 핫패스에 도입합니다.

Q: concepts는 런타임 비용이 있나요?
A: 없습니다. 컴파일 타임 제약만 추가되므로, 가독성·에러 메시지 개선용으로 사용합니다.

Q: modules를 지금 도입해야 하나요?
A: 빌드 시스템·컴파일러 지원이 프로젝트마다 다릅니다. 실험·측정 후 도입 가능 여부를 평가합니다.

적용 체크리스트 (실무용)

  • 핫패스에 ranges를 넣기 전 동일 알고리즘으로 벤치마크했는가?
  • concepts는 런타임 비용 없음을 인지하고 가독성·에러 메시지용으로 썼는가?
  • modules 도입 시 도구 지원·빌드 시간을 확인했는가?
  • [[likely]]/[[unlikely]]는 효과를 측정한 뒤 적용했는가?
  • 변경 후 벤치마크·빌드 시간으로 회귀 검증했는가?

진단 도구 요약

목적도구·방법
ranges vs for 성능격리 벤치마크(동일 알고리즘)
concepts런타임 비용 없음; 컴파일 시간만 확인
modules빌드 시간 측정, 도구 지원 확인

자주 하는 실수

  • ranges를 핫패스에 무검증 도입: 벤치마크 후 오버헤드가 허용될 때만 도입합니다.
  • concepts에 런타임 비용 기대: concepts는 컴파일 타임만; 런타임 비용 없음.
  • modules를 도구 미지원 환경에서 강제: 지원 여부와 빌드 시간 이득을 먼저 확인합니다.

리팩토링 시 주의

기존 for 루프를 ranges로 바꿀 때 동일 알고리즘인지 확인하고, 벤치마크로 회귀가 없는지 검증합니다. modules 도입 시 include 순서·헤더 노출이 바뀌므로 빌드·테스트를 충분히 수행합니다.

추가 읽기 및 관련 챕터

  • 챕터 08 (템플릿/constexpr): 컴파일 타임 전략과 연계.
  • 챕터 10 (코루틴): C++20 비동기 기능.
  • 챕터 14 (span과 뷰): 뷰 패턴·non-owning.

다음 장에서는

이전 장: 템플릿/constexpr (챕터 08)

코루틴 성능을 다룹니다. C++20 코루틴의 suspend/resume 비용, 프레임 할당, 저지연 경로에서의 활용 기준을 정리합니다. → 코루틴 성능 (챕터 10)