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는 선택 사항이며, 해당 항목만 해당 도구 기준으로 안내합니다.
목차
- 프리컴파일 헤더(PCH) 사용
- 병렬 빌드 활성화 (/MP)
- 증분 링크 활성화 (/INCREMENTAL)
- Forward Declaration으로 include 줄이기
- Unity Build (Jumbo Build)
- 불필요한 헤더 제거
- C++20 모듈 사용
- ccache로 컴파일 캐시
- 디버그 정보 최적화 (/Zi vs /Z7)
- 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 후 재빌드 → 두 번째 빌드부터 캐시 효과
빌드 시간 단축 효과 예상
| 최적화 기법 | 예상 효과 | 적용 난이도 |
|---|---|---|
| PCH | 30~70% | 중간 (파일 구조 변경 필요) |
| /MP (병렬 빌드) | 2~4배 | 쉬움 (옵션 하나) |
| 증분 링크 | 링크 시간 50~90% | 쉬움 (옵션 하나) |
| Forward Declaration | 10~30% | 중간 (코드 리팩터링) |
| Unity Build | 20~50% | 중간 (부작용 있음) |
| C++20 모듈 | 50~80% | 어려움 (대규모 마이그레이션) |
| ccache | 2~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% 향상시키기