[C++]빌드 타임을 줄여보자 - Precompiled Header
※ 주의 ※ 작성된 지 오래된 글입니다(2016.6) 이전 블로그에서 작성했던 글을 재가공한 것입니다. 출처는 하단에 표기했습니다.
처음에는 “stdafx.h” 에러의 원인을 찾기 위해 시작한 조사이지만, 해당 내용이 빈 프로젝트가 아닌 프로젝트를 생성해서 생긴 에러인 것을 알게 되면서, 해당 내용에 대해 깊게 조사하다 보니 여러 내용을 찾을 수 있었다. 이에 이 내용을 정리해보려고 한다.
대규모 프로젝트를 진행하다 보면, 리빌드의 압박이 심하다. 코딩을 하다 보면 프로젝트의 몸집이 커지게 되고, 그에 비례하여 컴파일 타임도 기하급수적으로 늘어나게 된다. 단지 코드 몇줄 수정하고 추가 했을 뿐인데 매번 컴파일 타임이 수분~수십분 걸리게 되면 작업의 리듬도 깨지게 되고, 리빌드 한번 할 때 마다 능률이 저하되어 전체적인 개발속도가 느려지게 될 것이다. 그래서 빌드 타임을 줄이고자 사용하는 여러 기법들에 대해 알아보도록 하자. 해당 방법들을 적절히 사용한다면 원래 빌드타임보다 반 이상 줄어들게 될 것이다.
1. PCH(Precompiled Header)
C/C++ 컴파일러의 컴파일 단위는 무조건 c/cpp이다. 그래서 개개의 c/cpp 파일을 컴파일할 때 #include를 하는 모든 헤더를 당연히 매번 파싱해야 한다. C++ 컴파일러에서 헤더파일은 C전처리기에 의해 자동적으로 소스를 포함하게 된다. 간단한 프로그램은 괜찮은데, 이 중 일부 헤더파일의 경우 방대한 크기의 소스 코드를 포함할 수 있다. windows.h와 같이 덩치가 무지 큰 헤더파일을 #include하는 모든 파일을 매번 컴파일하면 시간이 상당히 걸린다. 그래서 VC에서는 컴파일 시간을 줄이기 위해 Precompiled Header를 이용하여, 덩치 큰 헤더는 미리 컴파일을 해놓을 수 있게 해놨다. PCH는 이런 비용을 줄이기 위해 나온 것으로 특정 헤더 파일을 미리 파싱한 결과물을 .pch 파일로 덤프시킨다. 그리고 다른 파일들은 이 PCH를 단순히 사용함으로써 중복된 파싱 작업을 없앨 수 있다. 이런 방식을 PCH라고 일컫는다. VC에서는 stdafx.h 라는 위저드를 통해 나온 PCH파일을 기본적으로 제공하고 있다. stdafx.h에 추가한 헤더는 매번 컴파일 되지 않기 때문에 빌드 타임을 줄여주게 된다.
2. PCH 사용하기
PCH가 작동하는 원리는 다음과 같다. 덩치가 크거나 거의 바뀌지 않는 정의 같은 것들을 모두 stdafx.h에 넣는다. 실제로 windows.h 같은 것을 사용하는 프로젝트에서 PCH를 쓰냐 안 쓰느냐는 컴파일 속도에 지대한 영향을 미친다. 하나 오해하지 말아야할 것은 precompiled header의 이름은 꼭 stdafx.h/cpp일 필요가 없다. 아무런 파일 이름도 된다. C/C++의 문제점 중 하나가 지저분한 헤더파일로 인한 얽히고 섥힌 의존관계로 길어지는 컴파일 시간일 것이다. 혹시 프로젝트 빌드 시간이 지나치게 길다면 한 번 중복되는 헤더파일들이 반복적으로 파싱이 되고 있지 않은지 살펴보고 이들을 다 PCH로 만들면 좋을 것이다.
<빈 프로젝트로="" 만들="" 경우="">빈>
- 우선 미리 컴파일 된 헤더로 사용될 헤더 파일(,h)과 소스 파일(.cpp)을 프로젝트에 추가한다. (여기서는 stdafx.h, stdafx.cpp로 정의하자) 그 다음 프로젝트 속성에 들어가 “미리 컴파일된 헤더를 사용(/Yu)”하겠다는 설정을 해준다.

- stdafx.cpp 파일의 속성 메뉴에 들어가 “미리 컴파일 된 헤더 만들기(/Yc)”를 설정해준다. 이를 통해 stdafx.h 파일에서 참조하고 있는 헤더 파일들을 미리 컴파일하게 된다. (어떤 곳에서는 stdafx.cpp를 “만들기”로 설정한 후에 나머지 cpp 파일들을 “사용”으로 설정해야 한다고 되어있는데, 이와 같은 방법으로 하면 이상 없이 작동하는것을 확인했다.)

-
stdafx.cpp 파일을 열고
#include "stdafx.h"
를 작성해준다. -
stdafx.h 파일을 열고 맨 윗줄에
#pragma once
라고 타이핑한다. -
모든 cpp 파일에
#include "stdafx.h"
를 입력해준다. (앞으로 생성되는 cpp에도 의무적으로 입력해주어야 한다) -
Ctrl + Alt + F7 을 눌러 프로젝트를 완전히 리빌드한다.
<빈 프로젝트가 아닌, 미리 만들어진 헤더 옵션을 설정한 프로젝트인 경우>
VC++ 2013 커뮤니티에서 테스트했을 때는, 모두 세팅되어 있었다. 모두. 해당 프로젝트명으로 생성된 cpp에 작업을 시작하거나 새로 .cpp를 생성하여 #include "stdafx.h"
를 입력해준 후 작업을 진행하면 된다.

MFC에서, 자주 사용되는 공용 소스들은 Precompiled header로 만들어 제공하기 위해 디폴트로 해당 파일에 위자드에서 자동 생성된다. stdafx.h에 포함하는 내용들을 살펴보면 윈도우 객체 생성에 필요한 기본 클래스(afx.h/afxwin.h 등), 윈도우 컨트롤(afxctl.h/afxcmn.h 등), 기본 DB 관련 클래스(afxdb.h/afxdao.h), 네트워크 관련 클래스(afxsock.h) 등등 기본적인 프레임워크 구축에 필요한 필수 헤더들이 포함되어 있다.
<stdafx.h에 어떤 헤더 파일을 컴파일 시켜야 할까?>
미리 컴파일된 헤더를 쓰면 빌드타임을 줄일 수 있다. 그렇다면 많은 헤더들을 추가하면 좋을까? 결론은 아니다. 미리 컴파일된 헤더에 포함시킬 헤더를 선정하는 것은 신중해야 한다. 예로 미리 컴파일된 헤더에 포함된 파일이 수정이 일어나면 미리 컴파일된 헤더도 새로 만들게 된다. 이렇게 되면 미리 컴파일 된 헤더를 사용하는 의미가 없어진다. MSDN의 권장사항은 다음과 같다.
- 자주 바뀌지 않고 크기가 큰 코드 본문을 항상 사용한다.
- 프로그램이 여러 개의 모듈로 구성되어 있으며, 모든 모듈이 포함 파일의 표준 집합 및 동일한 컴파일 옵션을 사용한다. 이 경우 모든 포함 파일을 미리 컴파일해서 하나의 미리 컴파일된 헤더로 만들 수 있다.
여기서 핵심은 “자주 바뀌지 않고”이다. 대표적으로 STL헤더 같은 것이 있다. 이들의 헤더 파일은 거의 바뀔일이 없다. 그 외에 프로젝트에 사용하는 서드파티 라이브러리 헤더 파일이라든가 한번 만들어두고 수정할 일 없는 헤더 파일이 있는 경우 추가해주면 좋다.
3. 그 외에…
UnityBuild
쉽게 말해 컴파일러의 압박을 줄이는 것이다. cpp파일에서 포함시키는 라이브러리 헤더라던지, 다른 헤더파일들의 중복 입력을 막기 위하여 cpp파일을 합치는 것이다.
송창규, unity build로 빌드타임 반토막내기, NDC2010
해당 링크는 NDC에서 발표한 자료이다. 해당 슬라이드 끝에 자동으로 Unity Build를 할 수 있게 해주는 툴에 대한 설명이 나와있다. UnityBuild를 구현할때 주의할 사항이 있는데, 서로 다른 cpp파일에 동일한 이름의 static 지역변수가 있을 경우에는, 바꿔줘야 한다. 또한 너무 cpp파일이 크면 에러의 가능성이 있기 때문에 1-20개의 cpp들로 묶는것이 좋다.
IncrediBuild
VC++의 느린 컴파일 속도를 개선하기 위해 만들어진 네트워크 분산 빌딩시스템이다. 큰 프로젝트를 리빌드하면 컴파일&링크 시간이 오래 걸리는데 이는 C/C++언어만이 가지고 있는 장점이자 단점이기도 하다. 이런 단점을 보완하기 위해 네트워크(TCP/IP)를 통해 한 프로젝트를 여러 PC에 분산해 컴파일&링크 하는 유틸리티이다.
Leave a comment