Visual Studio C++ 빌드 느림 | "10분 걸리던 빌드" PCH·/MP로 2분 만들기

Visual Studio C++ 빌드 느림 | "10분 걸리던 빌드" PCH·/MP로 2분 만들기

이 글의 핵심

Visual Studio C++ 빌드 느림에 대한 실전 가이드입니다.

들어가며: “빌드 10분… 커피 한 잔”

Visual Studio에서 C++ 프로젝트를 빌드할 때, 파일이 많아질수록 컴파일 시간이 기하급수적으로 늘어납니다. 특히 헤더 파일에 템플릿·인라인 함수가 많거나, #include가 깊게 중첩되면 매번 풀 빌드에 수 분~수십 분이 걸리기도 합니다. 이 글은 Visual Studio C++ 컴파일 속도를 개선하는 10가지 실전 방법을 정리합니다.

이 글에서 다루는 것:

  • 프리컴파일 헤더(PCH)(Precompiled Header—자주 쓰는 헤더를 한 번만 파싱해 두고 재사용): 자주 쓰는 헤더를 미리 컴파일
  • 병렬 빌드(/MP): 여러 CPU 코어 활용
  • 증분 링크(변경된 오브젝트만 다시 링크해 빌드 시간 단축): 변경된 부분만 재링크
  • Forward Declaration(전방 선언—타입을 정의하지 않고 선언만 해 include를 줄이는 기법): 불필요한 include 제거
  • C++20 모듈: import 방식으로 헤더 대체
  • ccache: 컴파일 캐시로 재빌드 가속
  • 기타 프로젝트 설정 최적화

요구 환경: Visual Studio 2019/2022(Windows). PCH·/MP·증분 링크는 VS 프로젝트/CMake 설정에서 적용. C++20 모듈·ccache는 선택 사항이며, 해당 항목만 해당 도구 기준으로 안내합니다.


목차

  1. 프리컴파일 헤더(PCH) 사용
  2. 병렬 빌드 활성화 (/MP)
  3. 증분 링크 활성화 (/INCREMENTAL)
  4. Forward Declaration으로 include 줄이기
  5. Unity Build (Jumbo Build)
  6. 불필요한 헤더 제거
  7. C++20 모듈 사용
  8. ccache로 컴파일 캐시
  9. 디버그 정보 최적화 (/Zi vs /Z7)
  10. SSD 사용 및 안티바이러스 예외 설정

VS C++ 빌드 시간 단축의 핵심 조합을 요약하면 아래와 같습니다.

flowchart LR
  PCH[PCH] --> MP["/MP 병렬"]
  MP --> INC[증분 링크]
  INC --> FD[Forward Decl]
  FD --> MOD[모듈/ccache]

1. 프리컴파일 헤더(PCH) 사용

PCH란?

프리컴파일 헤더(Precompiled Header, PCH)는 자주 쓰이지만 변경되지 않는 헤더(예: <iostream>, <vector>, <boost/asio.hpp>)를 미리 컴파일해 두고, 각 .cpp 파일에서 재사용하는 기법입니다. 매번 같은 헤더를 파싱·컴파일하는 시간을 절약할 수 있습니다.

왜 효과가 큰가요?
C++ 헤더 파일은 중첩 include가 많습니다. 예를 들어 <iostream>을 include하면, 내부적으로 수십 개의 다른 헤더를 include합니다. 프로젝트에 .cpp 파일이 100개 있고, 각 파일이 <iostream>을 include하면, 같은 헤더를 100번 파싱합니다. PCH를 쓰면 한 번만 파싱하고, 결과를 재사용합니다.

구체적인 효과:

  • Boost.Asio include 시: 파일당 2~3초 → PCH 사용 시 0.1초
  • Qt include 시: 파일당 5~10초 → PCH 사용 시 0.2초
  • 100개 파일 프로젝트: 5분 → 2분 (60% 단축)

주의사항:
PCH에 넣는 헤더는 거의 변경되지 않는 것만 넣어야 합니다. 자주 수정하는 프로젝트 헤더를 넣으면, PCH를 재생성해야 하므로 오히려 느려질 수 있습니다.

Visual Studio에서 설정

1. pch.h 파일 생성:

// pch.h
#ifndef PCH_H
#define PCH_H

// 자주 쓰는 표준 라이브러리
#include <iostream>
#include <vector>
#include <string>
#include <memory>
#include <algorithm>

// 외부 라이브러리
#include <boost/asio.hpp>
#include <fmt/core.h>

#endif

2. pch.cpp 파일 생성:

// pch.cpp
#include "pch.h"

3. 프로젝트 속성 설정:

  • pch.cpp: 속성 → C/C++ → 프리컴파일 헤더 → 프리컴파일 헤더 만들기(/Yc)
  • 나머지 모든 .cpp: 속성 → C/C++ → 프리컴파일 헤더 → 프리컴파일 헤더 사용(/Yu), 프리컴파일 헤더 파일: pch.h

4. 각 .cpp 파일 상단에 include:

#include "pch.h"  // ✅ 항상 첫 줄에

#include "myheader.h"
// ...

효과

프로젝트 크기에 따라 다르지만, 30~70% 컴파일 시간 단축이 가능합니다. 특히 Boost, Qt 같은 헤더가 무거운 라이브러리를 쓸 때 효과가 큽니다.


2. 병렬 빌드 활성화 (/MP)

/MP 옵션

기본적으로 Visual Studio는 한 번에 하나의 .cpp 파일만 컴파일합니다. /MP 옵션을 켜면 여러 CPU 코어를 동시에 사용해 병렬 컴파일합니다.

왜 기본값이 아닌가요?
역사적 이유입니다. 옛날에는 싱글 코어 CPU가 대부분이었고, 병렬 컴파일은 메모리를 많이 소모합니다. 요즘은 대부분 4코어 이상이므로, /MP를 켜는 것이 거의 항상 유리합니다.

얼마나 빨라지나요?
이론적으로는 코어 수만큼 빨라지지만, 실제로는:

  • 4코어: 2~3배
  • 8코어: 3~5배
  • 16코어: 4~7배

완벽하게 선형 확장되지 않는 이유는 링크 단계는 병렬화되지 않고, 일부 파일이 다른 파일보다 훨씬 오래 걸려서 병목이 생기기 때문입니다.

설정 방법

프로젝트 속성:

  • C/C++ → 일반 → 다중 프로세서 컴파일예(/MP)

또는 명령줄:

msbuild MyProject.sln /p:CL_MPCount=8

CMake:

if(MSVC)
    add_compile_options(/MP)
endif()

효과

4코어 이상 CPU에서 2~4배 빌드 속도 향상이 가능합니다. 단, 메모리 사용량도 증가하므로, RAM이 부족하면 오히려 느려질 수 있습니다.


3. 증분 링크 활성화 (/INCREMENTAL)

증분 링크란?

증분 링크(Incremental Linking)변경된 오브젝트 파일만 재링크하는 기법입니다. 전체 프로그램을 다시 링크하지 않으므로 링크 시간이 크게 줄어듭니다.

설정 방법

프로젝트 속성:

  • 링커 → 일반 → 증분 링크 사용예(/INCREMENTAL)

CMake:

if(MSVC)
    set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} /INCREMENTAL")
endif()

주의사항

  • 디버그 빌드에서만 권장합니다. 릴리스 빌드에서는 최적화를 위해 풀 링크를 하는 것이 좋습니다.
  • 가끔 증분 링크 정보가 꼬여서 이상한 에러가 날 수 있습니다. 이때는 Clean Solution 후 재빌드하세요.

4. Forward Declaration으로 include 줄이기

문제 상황

헤더(MyClass.h):

#include "HeavyClass.h"  // ❌ HeavyClass 정의 전체를 포함

class MyClass {
    HeavyClass* member;  // 포인터만 쓰는데 전체 정의가 필요 없음
};

HeavyClass.h가 무겁고, 이걸 include하는 파일이 많으면 컴파일 시간이 늘어납니다.

해결법: Forward Declaration

// MyClass.h
class HeavyClass;  // ✅ 전방 선언

class MyClass {
    HeavyClass* member;  // 포인터·참조는 전방 선언만으로 OK
};

MyClass.cpp에서만 실제 정의를 include:

// MyClass.cpp
#include "MyClass.h"
#include "HeavyClass.h"  // ✅ 구현부에서만 include

void MyClass::doSomething() {
    member->someMethod();  // 여기서는 정의가 필요
}

효과

헤더 의존성이 줄어들어 연쇄 재컴파일이 감소합니다. 특히 헤더 파일을 자주 수정하는 경우 효과가 큽니다.


5. Unity Build (Jumbo Build)

Unity Build란?

여러 .cpp 파일을 하나의 큰 .cpp로 합쳐서 컴파일하는 기법입니다. 헤더 파싱 오버헤드가 줄어들고, 컴파일러가 파일 간 최적화를 할 수 있습니다.

CMake에서 설정

set(CMAKE_UNITY_BUILD ON)
add_executable(myapp main.cpp utils.cpp math.cpp)

CMake가 자동으로 여러 .cpp를 묶어서 컴파일합니다.

주의사항

  • 전역 변수·매크로 충돌: 여러 파일이 합쳐지므로, 같은 이름의 전역 변수나 static 함수가 있으면 충돌합니다.
  • 디버깅 어려움: 에러 메시지의 줄 번호가 원본 파일과 다를 수 있습니다.
  • 증분 빌드 효과 감소: 한 파일만 수정해도 전체 Unity 파일이 재컴파일됩니다.

권장: 릴리스 빌드에서만 사용하고, 디버그 빌드에서는 끄는 것이 좋습니다.


6. 불필요한 헤더 제거

문제 상황

#include <iostream>
#include <vector>
#include <map>
#include <algorithm>
#include <boost/asio.hpp>
// ... 실제로는 iostream만 씀

원인: 복붙하거나 예전 코드에서 남은 불필요한 include가 많으면, 컴파일러가 쓸데없이 헤더를 파싱합니다.

해결법

1. Include-what-you-use (IWYU) 도구:

# Ubuntu
sudo apt install iwyu

# 사용
include-what-you-use -Xiwyu --no_fwd_decls main.cpp

2. Visual Studio 확장:

  • Include Toolbox 확장을 설치하면, 사용하지 않는 include를 자동으로 찾아 줍니다.

3. 수동 확인:

  • 각 include를 주석 처리해 보고, 컴파일이 되는지 확인합니다.

7. C++20 모듈 사용

C++20 모듈이란?

C++20 모듈#include 대신 **import**를 쓰는 새로운 방식입니다. 헤더 파일의 중복 파싱 문제를 근본적으로 해결합니다.

헤더의 근본적 문제:
#include텍스트 복사입니다. 헤더 파일 내용을 그대로 붙여넣는 것과 같습니다. 100개 파일이 <iostream>을 include하면, <iostream>의 수천 줄 코드가 100번 복사·파싱됩니다. Include Guard(#ifndef)로 중복 정의는 막지만, 파싱은 매번 일어납니다.

모듈의 혁신:
모듈은 미리 컴파일된 바이너리입니다. 한 번 컴파일하면 .ifc 파일(인터페이스 파일)이 생성되고, 이후 import는 이 바이너리를 직접 로드합니다. 파싱이 필요 없습니다.

비유:

  • 헤더: 레시피를 매번 처음부터 읽고 요리
  • 모듈: 미리 만들어 둔 반찬을 꺼내서 사용

기존 방식 (헤더):

// math.h
int add(int a, int b);

// main.cpp
#include "math.h"  // 매번 파싱

모듈 방식:

// math.ixx (모듈 인터페이스)
export module math;

export int add(int a, int b) {
    return a + b;
}

// main.cpp
import math;  // ✅ 미리 컴파일된 모듈 사용

int main() {
    int result = add(3, 5);
    return 0;
}

Visual Studio에서 설정

프로젝트 속성:

  • C/C++ → 언어 → C++ 언어 표준ISO C++20 표준(/std:c++20)
  • C/C++ → 일반 → C++ 모듈 스캔

CMake:

set(CMAKE_CXX_STANDARD 20)
add_executable(myapp main.cpp math.ixx)
target_sources(myapp PRIVATE
    FILE_SET CXX_MODULES FILES math.ixx
)

효과

모듈은 한 번만 컴파일되고, 이후에는 바이너리 형태로 재사용됩니다. 대규모 프로젝트에서 50% 이상 컴파일 시간 단축이 가능합니다.

주의: Visual Studio 2022 이상, CMake 3.28 이상에서 안정적으로 지원됩니다.


8. ccache로 컴파일 캐시

ccache란?

ccache컴파일 결과를 캐시해서, 같은 파일을 다시 컴파일할 때 캐시를 재사용하는 도구입니다. 브랜치를 오가거나, Clean 후 재빌드할 때 극적인 속도 향상이 있습니다.

동작 원리:
ccache는 컴파일 명령과 소스 파일의 해시를 계산합니다. 같은 파일을 같은 옵션으로 컴파일하면, 이전 결과를 캐시에서 꺼내서 반환합니다. 실제로 컴파일러를 실행하지 않으므로 10~100배 빠릅니다.

언제 효과가 큰가요?:

  • 브랜치 전환: feature 브랜치에서 main으로 전환 후 빌드 → 대부분 파일이 캐시에 있음
  • Clean 후 재빌드: 실수로 Clean했을 때 → 캐시가 있으면 수 초 만에 복구
  • CI/CD: 매번 클린 빌드하는 환경에서 ccache를 쓰면 빌드 시간 대폭 단축

캐시 크기:
기본 캐시 크기는 5GB입니다. ccache -M 10G로 늘릴 수 있습니다.

설치

Linux:

sudo apt install ccache

macOS:

brew install ccache

Windows: ccache GitHub에서 다운로드.

CMake에서 설정

find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
    set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
    set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
    message(STATUS "ccache enabled")
endif()

Visual Studio에서 설정

프로젝트 속성:

  • C/C++ → 명령줄 → 추가 옵션에 /Tp"%(FullPath)" /Fo"$(IntDir)%(Filename).obj"를 수정해서 ccache를 앞에 붙일 수 있지만, 복잡합니다.
  • 더 간단한 방법: CMake + Ninja 빌드 시스템을 쓰면 ccache가 자동으로 작동합니다.

9. 디버그 정보 최적화 (/Zi vs /Z7)

/Zi vs /Z7

/Zi (기본):

  • 디버그 정보를 별도 .pdb 파일에 저장합니다.
  • 병렬 빌드 시 PDB 파일 잠금 경합이 발생해 느려질 수 있습니다.

/Z7:

  • 디버그 정보를 오브젝트 파일(.obj)에 직접 포함합니다.
  • PDB 잠금 문제가 없어서 병렬 빌드가 더 빠릅니다.
  • 단, 오브젝트 파일 크기가 커지고, 증분 링크 효과가 줄어듭니다.

설정 방법

프로젝트 속성:

  • C/C++ → 일반 → 디버그 정보 형식C7 호환(/Z7)

CMake:

if(MSVC)
    add_compile_options(/Z7)
endif()

권장

  • 병렬 빌드를 많이 쓰는 환경: /Z7
  • 증분 링크를 중시하는 환경: /Zi (기본)

10. SSD 사용 및 안티바이러스 예외 설정

SSD 사용

HDD에서 빌드하면 파일 I/O가 병목입니다. SSD로 옮기면 2~3배 빠릅니다.

안티바이러스 예외 설정

Windows Defender나 다른 안티바이러스가 컴파일러·링커 실행 파일빌드 출력 폴더를 실시간으로 검사하면 느려집니다.

예외 추가:

  • Windows 보안 → 바이러스 및 위협 방지 → 설정 관리 → 제외 → 폴더 추가
  • 추가할 폴더:
    • 프로젝트 빌드 폴더 (C:\MyProject\build\)
    • Visual Studio 설치 폴더 (C:\Program Files\Microsoft Visual Studio\)
    • CMake 캐시 폴더 (.cmake/ 등)

기타 최적화 팁

1. 링커 최적화 끄기 (디버그 빌드)

프로젝트 속성:

  • 링커 → 최적화 → 참조아니요(/OPT:NOREF)
  • 링커 → 최적화 → COMDAT 정리아니요(/OPT:NOICF)

디버그 빌드에서는 최적화를 끄면 링크가 빠릅니다.

2. 빌드 출력 경로 단축

긴 경로는 파일 시스템 오버헤드를 증가시킵니다.

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)

3. 템플릿 인스턴스화 줄이기

Extern Template:

// heavy_template.h
template <typename T>
class HeavyClass { /* ... */ };

// heavy_template.cpp
#include "heavy_template.h"
template class HeavyClass<int>;  // ✅ 명시적 인스턴스화

// 다른 .cpp 파일
extern template class HeavyClass<int>;  // ✅ 재컴파일 방지

컴파일 시간 측정

Visual Studio

도구 → 옵션 → 프로젝트 및 솔루션 → 빌드 및 실행:

  • MSBuild 프로젝트 빌드 출력의 자세한 정도자세히

빌드 출력 창에서 각 파일의 컴파일 시간을 확인할 수 있습니다.

CMake + Ninja

cmake -G Ninja ..
ninja -v  # 각 컴파일 명령과 시간 출력

Clang Build Analyzer

# Clang으로 빌드 시 타이밍 정보 수집
clang++ -ftime-trace main.cpp

# 결과 분석
ClangBuildAnalyzer --all . output.bin
ClangBuildAnalyzer --analyze output.bin

어느 헤더가 가장 오래 걸리는지 확인할 수 있습니다.


실전 최적화 프로세스

1단계: 현재 빌드 시간 측정

  • Clean Solution 후 풀 빌드 시간 기록 (예: 5분)

2단계: PCH 적용

  • pch.h에 자주 쓰는 헤더 모음
  • 재빌드 → 시간 확인 (예: 3분)

3단계: 병렬 빌드 활성화

  • /MP 옵션 추가
  • 재빌드 → 시간 확인 (예: 1분 30초)

4단계: 증분 링크 활성화

  • /INCREMENTAL 설정
  • 일부 파일만 수정 후 빌드 → 시간 확인 (예: 10초)

5단계: Forward Declaration 적용

  • 무거운 헤더를 전방 선언으로 대체
  • 재빌드 → 시간 확인

6단계: ccache 도입 (선택)

  • CMake + Ninja 환경에서 ccache 활성화
  • Clean 후 재빌드 → 두 번째 빌드부터 캐시 효과

빌드 시간 단축 효과 예상

최적화 기법예상 효과적용 난이도
PCH30~70%중간 (파일 구조 변경 필요)
/MP (병렬 빌드)2~4배쉬움 (옵션 하나)
증분 링크링크 시간 50~90%쉬움 (옵션 하나)
Forward Declaration10~30%중간 (코드 리팩터링)
Unity Build20~50%중간 (부작용 있음)
C++20 모듈50~80%어려움 (대규모 마이그레이션)
ccache2~10배 (재빌드 시)쉬움 (도구 설치)

한 줄 요약: PCH·병렬 빌드(/MP)·증분 링크만으로도 빌드 시간을 크게 줄일 수 있고, 대규모 프로젝트는 C++20 모듈까지 고려하면 좋습니다. 다음으로 CMake 입문이나 컴파일러 최적화를 읽어보면 좋습니다.


자주 묻는 질문 (FAQ)

Q. PCH를 쓰면 항상 빠른가요?

A: 아닙니다. PCH에 넣은 헤더를 자주 수정하면, PCH를 재생성해야 하므로 오히려 느려집니다. 거의 변경되지 않는 헤더(표준 라이브러리, 외부 라이브러리)만 넣으세요.

Q. /MP와 증분 링크를 동시에 쓰면?

A: 가능합니다. /MP컴파일 병렬화, 증분 링크는 링크 최적화이므로 독립적입니다. 둘 다 켜면 효과가 누적됩니다.

Q. Unity Build의 부작용을 어떻게 피하나요?

A:

  • 전역 변수·static 함수를 익명 네임스페이스로 감싸기
  • 매크로 충돌 방지: #undef로 정리
  • 디버그 빌드에서는 끄기: CMAKE_UNITY_BUILD_MODE DEBUG

Q. C++20 모듈은 언제 쓸 수 있나요?

A: Visual Studio 2022 + CMake 3.28 이상에서 안정적입니다. 하지만 외부 라이브러리 대부분이 아직 모듈을 지원하지 않으므로, 프로젝트 내부 코드부터 점진적으로 마이그레이션하는 것을 권장합니다.

Q. ccache 캐시를 지우려면?

A: ccache -C (전체 삭제) 또는 ccache -c (오래된 항목만 삭제).


관련 글

  • CMake 입문: CMake 기본 개념
  • CMake 치트시트: 빌드 설정 템플릿
  • C++20 모듈: 모듈 문법과 마이그레이션
  • C++ 가변 인자 템플릿: 템플릿 메타프로그래밍 기초

Visual Studio C++ 컴파일 속도PCH + 병렬 빌드 + 증분 링크 조합만으로도 5~10배 개선이 가능합니다. 특히 프리컴파일 헤더는 초기 설정만 해 두면 지속적으로 효과를 보므로, 프로젝트 초기에 적용하는 것을 강력히 권장합니다. 대규모 프로젝트라면 C++20 모듈 마이그레이션도 고려해 볼 만합니다. 모듈은 아직 생태계가 완전히 성숙하지 않았지만, 컴파일 시간 단축 효과는 확실합니다.

검색 시 참고 키워드: Visual Studio C++ 빌드 느림, 컴파일 속도 개선, 프리컴파일 헤더 PCH, /MP 병렬 빌드, C++20 모듈


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • C++ LNK2019 | “unresolved external symbol” 링커 에러 원인 5가지와 해결법
  • CMake 입문 | 수십 개 파일 컴파일할 때 필요한 빌드 자동화 (CMakeLists.txt 기초)
  • C++ 컴파일러 최적화 | PGO·LTO로 “느린 프로그램” 성능 30% 향상시키기