C ++ 컴파일 시간을 단축하기 위해 어떤 기술을 사용할 수 있습니까?


249

C ++ 컴파일 시간을 단축하기 위해 어떤 기술을 사용할 수 있습니까?

이 질문은 Stack Overflow question C ++ programming style에 대한 의견에서 나 왔으며 어떤 아이디어가 있는지 듣고 싶습니다.

관련 질문을 보았습니다. 왜 C ++ 컴파일이 그렇게 오래 걸립니까? 하지만 많은 솔루션을 제공하지는 않습니다.


1
상황을 알려주 시겠어요? 아니면 일반적인 답변을 찾고 있습니까?
Pyrolistical

1
이 질문과 매우 유사합니다 : stackoverflow.com/questions/364240/…
Adam Rosenfield

일반적인 답변. 많은 사람들이 작성한 큰 코드 기반이 있습니다. 그것을 공격하는 방법에 대한 아이디어는 좋을 것입니다. 또한 새로 작성된 코드의 컴파일을 빠르게 유지하기위한 제안도 흥미로울 것입니다.
Scott Langham

종종 빌드 시간의 관련 부분은 컴파일러가 아니라 빌드 스크립트에 의해 사용됩니다.
thi gg

1
이 페이지를 감추고 측정에 대한 언급을 보지 못했습니다. 수신하는 각 입력 줄에 타임 스탬프를 추가하는 작은 쉘 스크립트를 작성했기 때문에 'make'호출에 파이프 할 수 있습니다. 타임 스탬프를 비교하여 가장 비싼 대상, 총 컴파일 또는 링크 시간 등을 확인할 수 있습니다. 이 방법을 시도하면 병렬 빌드에서 타임 스탬프가 정확하지 않을 수 있습니다.
John P

답변:


257

언어 기술

핌프 숙어

여기여기 에서 불투명 포인터 또는 핸들 클래스 라고도 하는 Pimpl 관용구를 살펴보십시오 . 컴파일 속도를 향상시킬뿐만 아니라 비 투척 스왑 기능 과 결합하면 예외 안전성도 향상 됩니다. Pimpl 관용구를 사용하면 헤더 간의 종속성을 줄이고 수행해야하는 재 컴파일 양을 줄일 수 있습니다.

전달 선언

가능하면 앞으로 선언을 사용하십시오 . 컴파일러 SomeIdentifier가 구조체 또는 포인터 또는 그 밖의 것만 알고 있으면 전체 정의를 포함시키지 말고 컴파일러가 필요한 것보다 많은 작업을 수행하도록하십시오. 이것은 계단식 효과를 낼 수 있으며,이 방법을 필요 이상으로 느리게 만듭니다.

I / O의 스트림은 특히 빌드 둔화에 대한 알려져있다. 헤더 파일에 필요한 경우 구현 파일 의 헤더 만 #include <iosfwd>대신 #include 를 시도 하십시오. 헤더는 앞으로 선언 만 보유하고 있습니다. 불행히도 다른 표준 헤더에는 각각의 선언 헤더가 없습니다.<iostream><iostream><iosfwd>

함수 시그니처에서 값을 기준으로 전달하는 것을 선호합니다. 이렇게하면 헤더 파일에 각 유형 정의를 #include 할 필요가 없으며 유형을 전달하기 만하면됩니다. 물론 불분명 한 버그를 피하기 위해 비 const 참조에 대한 const 참조를 선호하지만 이것은 또 다른 질문의 문제입니다.

가드 조건

보호 조건을 사용하여 단일 변환 단위에 헤더 파일이 두 번 이상 포함되지 않도록하십시오.

#pragma once
#ifndef filename_h
#define filename_h

// Header declarations / definitions

#endif

pragma와 ifndef를 모두 사용하면 일반 매크로 솔루션의 이식성과 일부 컴파일러가 pragma once지시문 이있을 때 수행 할 수있는 컴파일 속도 최적화를 얻을 수 있습니다 .

상호 의존성 감소

일반적으로 코드 디자인이 모듈화되고 상호 의존성이 낮을수록 모든 것을 다시 컴파일해야하는 횟수가 줄어 듭니다. 또한 추적해야 할 내용이 적기 때문에 컴파일러가 개별 블록에 대해 동시에 수행해야하는 작업량을 줄일 수 있습니다.

컴파일러 옵션

미리 컴파일 된 헤더

이들은 많은 번역 단위에 대해 포함 된 헤더의 공통 섹션을 한 번 컴파일하는 데 사용됩니다. 컴파일러는이를 한 번 컴파일하고 내부 상태를 저장합니다. 그런 다음 동일한 헤더 세트로 다른 파일을 컴파일 할 때 헤드 상태를 신속하게로드 할 수 있습니다.

미리 컴파일 된 헤더에 거의 변경되지 않은 항목 만 포함 시키거나 필요 이상으로 전체 재구성을 더 자주 수행 할 수 있습니다. 이것은위한 좋은 장소입니다 STL의 헤더와 다른 라이브러리 파일이 포함됩니다.

ccache 는 캐싱 기술을 활용하여 작업 속도를 높이는 또 다른 유틸리티입니다.

병렬 처리 사용

많은 컴파일러 / IDE는 동시에 여러 개의 코어 / CPU를 사용하여 컴파일을 지원합니다. 에서 GNU 제조사 (일반적으로 GCC와 함께 사용) 사용 -j [N]옵션을 선택합니다. Visual Studio에는 환경 설정에 여러 프로젝트를 병렬로 빌드 할 수있는 옵션이 있습니다. 또한 프로젝트 레벨 병렬 처리 대신 파일 레벨 병렬 처리 /MP옵션 을 사용할 수도 있습니다 .

다른 병렬 유틸리티 :

더 낮은 최적화 수준 사용

컴파일러가 최적화하려고하면할수록 더 어려워집니다.

공유 라이브러리

덜 자주 수정 된 코드를 라이브러리로 옮기면 컴파일 시간이 단축 될 수 있습니다. 공유 라이브러리를 사용하여 (.so 또는 .dll) 하면 연결 시간도 줄일 수 있습니다.

더 빠른 컴퓨터를 얻으십시오

더 많은 RAM, 더 빠른 하드 드라이브 (SSD 포함) 및 더 많은 CPU / 코어는 모두 컴파일 속도에 차이를 만듭니다.


11
사전 컴파일 된 헤더는 완벽하지 않습니다. 그것들을 사용하는 부작용은 모든 컴파일 유닛이 동일한 사전 컴파일 된 헤더를 사용하기 때문에 필요한 것보다 많은 파일을 포함한다는 것입니다. 명심해야 할 것.
jalf

8
최신 컴파일러에서 #ifndef는 #pragma만큼 빠릅니다 (포함 가드가 파일의 맨 위에있는 한). 컴파일 속도의 관점에서 #pragma에는 한 번의 이점이 없습니다.
jalf

7
2008이 아닌 VS 2005 만있는 경우에도 컴파일 옵션에 / MP 스위치를 추가하여 .cpp 수준에서 병렬 빌드를 수행 할 수 있습니다.
macbirdie

6
SSD는이 답변을 작성할 때 엄청나게 비쌌지만 오늘날 C ++을 컴파일 할 때 최선의 선택입니다. 컴파일 할 때 많은 작은 파일에 액세스합니다. 이를 위해서는 SSD가 제공하는 많은 IOPS가 필요합니다.
MSalters

14
함수 시그니처에서 값을 기준으로 전달하는 것을 선호합니다. 이것은 헤더 파일에 각 유형 정의를 #include 할 필요가 없습니다. 이것은 잘못 되었습니다. 값으로 전달되는 함수를 선언하기 위해 전체 유형이 필요하지 않습니다. 해당 기능 을 구현 하거나 사용 하려면 전체 유형 만 필요합니다. 하지만 대부분의 경우 (전화를 전달하지 않는 한) 어쨌든 해당 정의가 필요합니다.
David Rodríguez-dribeas

43

STAPL 프로젝트에서 일하고 있습니다.이 템플릿은 C ++ 라이브러리입니다. 가끔 컴파일 시간을 줄이기 위해 모든 기술을 다시 방문해야합니다. 여기에서는 우리가 사용하는 기술을 요약했습니다. 이러한 기술 중 일부는 이미 위에 나열되어 있습니다.

가장 시간이 많이 걸리는 섹션 찾기

심볼 길이와 컴파일 시간 사이에 입증 된 상관 관계는 없지만 평균 심볼 크기가 작을수록 모든 컴파일러의 컴파일 시간이 향상 될 수 있습니다. 따라서 첫 번째 목표는 코드에서 가장 큰 기호를 찾는 것입니다.

방법 1-크기를 기준으로 기호 정렬

nm명령을 사용하여 크기에 따라 심볼을 나열 할 수 있습니다 .

nm --print-size --size-sort --radix=d YOUR_BINARY

이 명령에서 --radix=d크기를 10 진수로 볼 수 있습니다 (기본값은 16 진). 이제 가장 큰 기호를보고 해당 클래스를 중단 할 수 있는지 확인하고 기본 클래스에서 템플릿 화되지 않은 부분을 고려하거나 클래스를 여러 클래스로 분할하여 클래스를 다시 디자인 해보십시오.

방법 2-길이를 기준으로 기호 정렬

일반 nm명령을 실행 하여 원하는 스크립트 ( AWK , Python 등)로 파이프 하여 길이 에 따라 기호를 정렬 할 수 있습니다. 우리의 경험을 바탕으로,이 방법은 방법 1보다 후보를 더 잘 만드는 가장 큰 문제를 식별합니다.

방법 3-Templight 사용

" Templight 는 템플릿 인스턴스화의 시간과 메모리 소비를 프로파일 링하고 대화 형 디버깅 세션을 수행하여 템플릿 인스턴스화 프로세스에 대한 내성을 확보 하는 Clang 기반 도구입니다."

LLVM 및 Clang ( 지침 ) 을 확인 하고 Templight 패치를 적용하여 Templight를 설치할 수 있습니다 . LLVM 및 Clang의 기본 설정은 디버그 및 어설 션에 있으며 컴파일 시간에 크게 영향을 줄 수 있습니다. Templight에 둘 다 필요한 것처럼 보이므로 기본 설정을 사용해야합니다. LLVM 및 Clang 설치 프로세스는 약 1 시간 정도 걸립니다.

패치를 적용한 후 templight++설치시 지정한 빌드 폴더에있는 코드를 사용하여 코드를 컴파일 할 수 있습니다.

templight++PATH에 있는지 확인하십시오 . 이제 컴파일하려면 CXXFLAGSMakefile 또는 명령 줄 옵션에 다음 스위치를 추가하십시오 .

CXXFLAGS+=-Xtemplight -profiler -Xtemplight -memory -Xtemplight -ignore-system

또는

templight++ -Xtemplight -profiler -Xtemplight -memory -Xtemplight -ignore-system

컴파일이 완료되면 같은 폴더에 .trace.memory.pbf 및 .trace.pbf가 생성됩니다. 이러한 추적을 시각화하기 위해 Templight 도구 를 사용하여 다른 형식으로 변환 할 수 있습니다. templight-convert를 설치하려면 다음 지시 사항 을 따르십시오 . 우리는 일반적으로 callgrind 출력을 사용합니다. 프로젝트가 작은 경우 GraphViz 출력을 사용할 수도 있습니다.

$ templight-convert --format callgrind YOUR_BINARY --output YOUR_BINARY.trace

$ templight-convert --format graphviz YOUR_BINARY --output YOUR_BINARY.dot

생성 된 callgrind 파일은 kcachegrind 를 사용하여 열 수 있으며 , 가장 많은 시간 / 메모리를 소비하는 인스턴스화를 추적 할 수 있습니다.

템플릿 인스턴스화 횟수 감소

템플릿 인스턴스화 수를 줄이는 정확한 솔루션은 없지만 다음과 같은 몇 가지 지침이 있습니다.

둘 이상의 템플릿 인수로 클래스 리팩터링

예를 들어 수업이 있으면

template <typename T, typename U>
struct foo { };

그리고 모두 TU10 가지 옵션을 가질 수 있습니다, 당신은이 추상적에 다른 클래스에 코드의 공통 부분 해결하기 위해 100 가지 방법으로이 클래스의 가능한 템플릿 인스턴스화를 증가하고있다. 다른 방법은 상속 계층 (클래스 계층 구조 역전)을 사용하는 것이지만이 기술을 사용하기 전에 디자인 목표가 손상되지 않는지 확인하십시오.

템플릿이 아닌 코드를 개별 번역 단위로 리팩토링

이 기술을 사용하면 공통 섹션을 한 번 컴파일하고 나중에 다른 TU (번역 단위)와 연결할 수 있습니다.

extern 템플릿 인스턴스화 사용 (C ++ 11부터)

클래스의 가능한 모든 인스턴스화를 알고 있으면이 기술을 사용하여 모든 경우를 다른 변환 단위로 컴파일 할 수 있습니다.

예를 들면 다음과 같습니다.

enum class PossibleChoices = {Option1, Option2, Option3}

template <PossibleChoices pc>
struct foo { };

이 클래스에는 세 가지 가능한 인스턴스가있을 수 있습니다.

template class foo<PossibleChoices::Option1>;
template class foo<PossibleChoices::Option2>;
template class foo<PossibleChoices::Option3>;

위의 내용을 번역 단위에 넣고 클래스 정의 아래의 헤더 파일에서 extern 키워드를 사용하십시오.

extern template class foo<PossibleChoices::Option1>;
extern template class foo<PossibleChoices::Option2>;
extern template class foo<PossibleChoices::Option3>;

이 기법을 사용하면 공통 인스턴스화 세트로 다른 테스트를 컴파일 할 때 시간을 절약 할 수 있습니다.

참고 : MPICH2는이 시점에서 명시 적 인스턴스화를 무시하고 항상 모든 컴파일 단위에서 인스턴스화 된 클래스를 컴파일합니다.

화합 빌드 사용

유니티 빌드의 기본 아이디어는 하나의 파일에 사용하는 모든 .cc 파일을 포함하고 해당 파일을 한 번만 컴파일하는 것입니다. 이 방법을 사용하면 다른 파일의 공통 섹션을 다시 인스턴스화하지 않고 프로젝트에 많은 공통 파일이 포함 된 경우 디스크 액세스에도 저장할 수 있습니다.

예를 들어,의 당신이 세 개의 파일이 있다고 가정하자 foo1.cc, foo2.cc, foo3.cc그들은 모두 포함 tuple에서 STL . foo-all.cc다음과 같은 모양을 만들 수 있습니다 .

#include "foo1.cc"
#include "foo2.cc"
#include "foo3.cc"

이 파일을 한 번만 컴파일하면 세 파일 간의 공통 인스턴스가 줄어 듭니다. 개선이 중요한지 아닌지를 일반적으로 예측하기는 어렵습니다. 그러나 한 가지 분명한 사실은 빌드에서 병렬 처리 가 손실 된다는 것입니다 (더 이상 세 파일을 동시에 컴파일 할 수 없음).

또한 이러한 파일 중 하나라도 많은 메모리를 사용하는 경우 컴파일이 끝나기 전에 실제로 메모리가 부족할 수 있습니다. GCC 와 같은 일부 컴파일러에서는 메모리 부족으로 인해 컴파일러에 ICE (Internal Compiler Error)가 발생할 수 있습니다. 따라서 모든 장단점을 알고 있지 않으면이 기술을 사용하지 마십시오.

미리 컴파일 된 헤더

PCH (사전 컴파일 된 헤더)는 헤더 파일을 컴파일러가 인식 할 수있는 중간 표현으로 컴파일하여 컴파일 시간을 크게 절약 할 수 있습니다. 사전 컴파일 된 헤더 파일을 생성하려면 일반 컴파일 명령으로 헤더 파일 만 컴파일하면됩니다. 예를 들어 GCC에서 :

$ g++ YOUR_HEADER.hpp

이것은이 생성됩니다 YOUR_HEADER.hpp.gch file( .gch같은 폴더에 GCC에서 PCH 파일의 확장자입니다). 즉 YOUR_HEADER.hpp, 다른 파일에 포함하면 컴파일러가 이전에 동일한 폴더 YOUR_HEADER.hpp.gch대신 사용합니다 YOUR_HEADER.hpp.

이 기술에는 두 가지 문제가 있습니다.

  1. 미리 컴파일 된 헤더 파일이 안정적이며 변경되지 않는지 확인해야합니다 ( 언제나 makefile을 변경할 수 있습니다 )
  2. 컴파일 장치 당 하나의 PCH 만 포함 할 수 있습니다 (대부분의 컴파일러에서). 즉, 미리 컴파일 할 헤더 파일이 두 개 이상인 경우 하나의 파일 (예 :)에 포함시켜야합니다 all-my-headers.hpp. 그러나 이것은 모든 장소에 새로운 파일을 포함시켜야한다는 것을 의미합니다. 다행히도 GCC는이 문제에 대한 해결책을 가지고 있습니다. 사용 -include하고 그것을 새로운 헤더 파일을 제공합니다. 이 기술을 사용하여 쉼표로 다른 파일을 분리 할 수 ​​있습니다.

예를 들면 다음과 같습니다.

g++ foo.cc -include all-my-headers.hpp

이름이 없거나 익명 인 네임 스페이스 사용

익명 네임 스페이스 ( 명명없는 네임 스페이스)는 생성 된 이진 크기를 크게 줄일 수 있습니다. 명명되지 않은 네임 스페이스는 내부 연결을 사용하므로 해당 네임 스페이스에서 생성 된 심볼은 다른 TU (번역 또는 컴파일 단위)에 표시되지 않습니다. 컴파일러는 일반적으로 명명되지 않은 네임 스페이스에 대해 고유 한 이름을 생성합니다. 이는 foo.hpp 파일이있는 경우 다음을 의미합니다.

namespace {

template <typename T>
struct foo { };
} // Anonymous namespace
using A = foo<int>;

그리고이 파일을 두 개의 TU (2 개의 .cc 파일과 별도로 컴파일)에 포함시킵니다. 두 개의 foo 템플릿 인스턴스는 동일하지 않습니다. 이는 하나의 정의 규칙 (ODR)을 위반합니다 . 같은 이유로 이름없는 네임 스페이스를 사용하는 것은 헤더 파일에서 사용하지 않는 것이 좋습니다. .cc이진 파일에 기호가 표시되지 않도록 파일 에서 자유롭게 사용 하십시오. 경우에 따라 .cc파일의 모든 내부 세부 정보를 변경 하면 생성 된 이진 크기가 10 % 감소한 것으로 나타났습니다.

가시성 옵션 변경

최신 컴파일러에서는 동적 공유 객체 (DSO)에서 심볼이 표시되거나 보이지 않도록 선택할 수 있습니다. 가시성을 변경하면 컴파일러 성능, 링크 시간 최적화 (LTO) 및 생성 된 이진 크기를 개선 할 수 있습니다. GCC에서 STL 헤더 파일을 보면 널리 사용되는 것을 볼 수 있습니다. 가시성 선택을 가능하게하려면 함수, 클래스, 변수 및 더 중요하게는 컴파일러마다 코드를 변경해야합니다.

가시성의 도움으로 생성 된 공유 객체에서 개인으로 간주되는 심볼을 숨길 수 있습니다. GCC에서는 -visibility컴파일러 옵션에 기본값을 숨기거나 숨겨서 기호의 가시성을 제어 할 수 있습니다 . 이것은 이름이없는 네임 스페이스와 비슷하지만보다 정교하고 방해가되는 방식입니다.

사례 당 가시성을 지정하려면 함수, 변수 및 클래스에 다음 속성을 추가해야합니다.

__attribute__((visibility("default"))) void  foo1() { }
__attribute__((visibility("hidden")))  void  foo2() { }
__attribute__((visibility("hidden")))  class foo3   { };
void foo4() { }

GCC의 기본 가시성은 공유 라이브러리 (로 위의 컴파일하는 경우 즉, 기본 (공개)이다 -shared) 방법, foo2및 클래스가 foo3다른 TU가에 표시되지 않습니다 ( foo1foo4표시됩니다). 당신이 컴파일하는 경우 -visibility=hiddenfoo1표시됩니다. 비록 foo4숨겨져있을 것입니다.

GCC wiki에서 가시성에 대해 자세히 읽을 수 있습니다 .


33

"Indie 게임 디자인 및 프로그래밍 게임"에서 다음 기사를 추천합니다.

사실, 그것들은 꽤 오래되었습니다. 사실적인 결과를 얻으려면 최신 버전 (또는 사용 가능한 버전)으로 모든 것을 다시 테스트해야합니다. 어느 쪽이든, 그것은 아이디어의 좋은 원천입니다.


17

과거에 나를 위해 잘 작동 한 기술 : 여러 C ++ 소스 파일을 독립적으로 컴파일하지 말고 다음과 같이 다른 모든 파일을 포함하는 하나의 C ++ 파일을 생성하십시오.

// myproject_all.cpp
// Automatically generated file - don't edit this by hand!
#include "main.cpp"
#include "mainwindow.cpp"
#include "filterdialog.cpp"
#include "database.cpp"

물론 이것은 소스가 변경 될 경우 포함 된 모든 소스 코드를 다시 컴파일해야하기 때문에 종속성 트리가 악화됩니다. 그러나 하나의 번역 단위로 여러 소스 파일을 컴파일하는 것이 더 빠르며 (적어도 MSVC 및 GCC 실험에서는 ) 더 작은 바이너리를 생성합니다. 또한 컴파일러가 최적화 가능성이 더 높다고 생각합니다 (한 번에 더 많은 코드를 볼 수 있기 때문에).

이 기술은 다양한 경우에 실패합니다. 예를 들어, 둘 이상의 소스 파일이 동일한 이름의 전역 함수를 선언하는 경우 컴파일러가 구제됩니다. 그래도 다른 답변에 설명 된이 기술을 찾을 수 없으므로 여기에서 언급합니다.

가치있는 것을 위해, KDE 프로젝트 는 1999 년 이후이 같은 기법을 사용하여 최적화 된 바이너리를 빌드 할 수있었습니다 (아마도 릴리스 가능). 빌드 구성 스크립트로의 전환을 호출했습니다 --enable-final. 고고 학적 관심에서이 기능을 발표 한 게시물을 발굴했습니다 : http://lists.kde.org/?l=kde-devel&m=92722836009368&w=2


2
그것이 실제로 같은지 확실하지 않지만 VC ++에서 "전체 프로그램 최적화"( msdn.microsoft.com/en-us/library/0zza0de8%28VS.71%29.aspx )를 켜야 한다고 생각합니다. 제안하는 것과 런타임 성능에 동일한 영향을 미칩니다. 그러나 컴파일 시간은 분명히 당신의 접근 방식에서 더 좋을 수 있습니다!
Philipp

1
@ Frerich : OJ의 답변에 언급 된 Unity 빌드를 설명하고 있습니다. 또한 대량 빌드 및 마스터 빌드라고도합니다.
idbrii

그렇다면 UB는 WPO / LTCG와 어떻게 비교됩니까?
paulm

이것은 편집, 빌드 및 테스트 사이를 순환하는 개발 중에가 아니라 일회성 컴파일에만 유용합니다. 현대 세계에서 4 개의 코어가 표준이며, 아마도 2 년 후에 코어 수가 훨씬 더 많습니다. 컴파일러와 링커가 여러 스레드를 사용할 수없는 경우 파일 목록을 적절한 정수가 <core-count> + N있는 곳에서 병렬로 컴파일 된 하위 목록으로 분할 할 수 있습니다 N(시스템 메모리 및 시스템 사용 방법에 따라 다름).
FooF

15

이 주제에 대한 전체 책이 있으며 제목은 Large-Scale C ++ Software Design (John Lakos가 작성)입니다.

이 책은 템플릿보다 오래된 것이므로, 그 책의 내용에 "템플릿을 사용하면 컴파일러가 느려질 수있다"고 덧붙인다.


이 책은 종종 이런 종류의 주제에서 언급되지만 저에게는 정보가 부족했습니다. 기본적으로 앞으로 선언을 최대한 사용하고 종속성을 분리한다고 명시되어 있습니다. pimpl 관용구를 사용하면 런타임 단점이 있다는 것 외에는 분명한 내용이 있습니다.
gast128

@ gast128 나는 점진적으로 다시 컴파일 할 수있는 코딩 관용구를 사용하는 것이 중요하다고 생각합니다. 즉, 소스를 약간 변경하면 모든 것을 다시 컴파일 할 필요가 없습니다.
ChrisW

15

나는 다른 대답에 링크 할 것입니다 : 어떻게 컴파일 시간을 줄이고 Visual C ++ 프로젝트 (기본 C ++)의 시간을 연결합니까? . 추가하고 싶은 또 다른 요점은 종종 문제를 일으키는 사전 컴파일 된 헤더를 사용하는 것입니다. 그러나 GUI 툴킷 헤더와 같이 거의 변경되지 않는 부분에만 사용하십시오. 그렇지 않으면 결국 비용을 절약하는 것보다 더 많은 시간이 소요됩니다.

또 다른 옵션은 GNU make로 작업 할 때 옵션을 켜는 것입니다 -j<N>.

  -j [N], --jobs[=N]          Allow N jobs at once; infinite jobs with no arg.

나는 3이중 코어를 가지고 있기 때문에 보통 그것을 가지고 있습니다. 그런 다음 다른 번역 단위에 대해 컴파일러가 병렬로 실행됩니다. 모든 객체 파일을 연결하는 링커 프로세스는 하나뿐이므로 링크를 병렬로 수행 할 수 없습니다.

그러나 링커 자체는 스레드 될 수 있으며 이것이 ELF 링커가하는 일입니다. ELF 객체 파일을 이전보다 훨씬 빠르게 링크 하고 실제로 binutils에 포함 되었다고하는 스레드 C ++ 코드에 최적화 되어 있습니다 .GNU gold ld


알았어 검색 할 때 해당 질문이 나타나지 않았습니다.
Scott Langham

미안할 필요는 없었습니다. 그것은 Visual C ++ 용이었습니다. 귀하의 질문은 모든 컴파일러에 대한 것 같습니다. 그래서 괜찮습니다 :)
Johannes Schaub-litb

12

여기 몇 가지가 있습니다 :

  • 다중 컴파일 작업을 시작하여 모든 프로세서 코어를 사용하십시오 ( make -j2좋은 예).
  • 최적화를 끄거나 낮추십시오 (예 : GCC가 또는 -O1보다 빠릅니다 ).-O2-O3
  • 미리 컴파일 된 헤더를 사용하십시오 .

12
참고로, 일반적으로 코어보다 더 많은 프로세스를 시작하는 것이 더 빠릅니다. 예를 들어 쿼드 코어 시스템에서는 일반적으로 -j4가 아니라 -j8을 사용합니다. 한 프로세스가 I / O에서 차단되면 다른 프로세스가 컴파일 될 수 있기 때문입니다.
Mr Fooz

@MrFooz : 몇 년 전에 i7-2700k (4 코어, 8 스레드, 상수 승수 설정)에서 Linux 커널 (RAM 스토리지에서)을 컴파일하여 테스트했습니다. 정확한 최상의 결과는 잊어 버렸지 만 제안한 것처럼 -j12주변 -j18보다 훨씬 빠릅니다 -j8. 나는 메모리 대역폭이 제한 요인이되기 전에 당신이 ... 할 수 있습니다 얼마나 많은 코어 궁금하네요
마크 K 코완

@MarkKCowan 그것은 많은 요인에 달려 있습니다. 컴퓨터마다 메모리 대역폭이 크게 다릅니다. 요즘 고급 프로세서를 사용하면 메모리 버스를 포화시키기 위해 여러 개의 코어가 필요합니다. 또한 I / O와 CPU 사이에 균형이 있습니다. 일부 코드는 컴파일하기 매우 쉽고 다른 코드는 느릴 수 있습니다 (예 : 많은 템플릿 사용). 현재 경험상 -j실제 코어 수의 2 배입니다.
Mr Fooz

11

위의 모든 코드 트릭 (포워드 선언, 퍼블릭 헤더의 헤더 포함을 최소화하고 Pimpl을 사용 하여 구현 파일 내부의 대부분의 세부 정보를 밀어 넣음 )을 적용하고 다른 언어로 얻을 수있는 것이 없다면 빌드 시스템을 고려하십시오. . Linux를 사용하는 경우 distcc (분산 컴파일러) 및 ccache (캐시 컴파일러) 사용을 고려하십시오.

첫 번째 distcc는 전 처리기 단계를 로컬로 실행 한 다음 네트워크에서 사용 가능한 첫 번째 컴파일러로 출력을 보냅니다. 네트워크의 모든 구성된 노드에서 동일한 컴파일러 및 라이브러리 버전이 필요합니다.

후자 인 ccache는 컴파일러 캐시입니다. 전처리기를 다시 실행 한 다음 전 처리기 파일이 동일한 컴파일러 매개 변수로 이미 컴파일되었는지 내부 데이터베이스 (로컬 디렉토리에 보유)를 확인합니다. 그렇다면 바이너리를 팝업하고 컴파일러의 첫 번째 실행에서 출력합니다.

둘 다 동시에 사용할 수 있으므로 ccache에 로컬 사본이 없으면 distcc를 사용하여 네트를 통해 다른 노드로 보내거나 추가 처리없이 솔루션을 주입 할 수 있습니다.


2
distcc 에 구성된 모든 노드 에서 동일한 라이브러리 버전이 필요하다고 생각하지 않습니다 . distcc는 링크가 아닌 원격 컴파일 만 수행합니다. 또한 사전 처리 된 코드를 유선으로 보내 므로 원격 시스템에서 사용 가능한 헤더는 중요하지 않습니다.
Frerich Raabe

9

내가 대학을 졸업했을 때, 내가 본 첫 번째 프로덕션 가치가있는 C ++ 코드는 헤더가 정의 된 이들 사이에 이러한 #ifndef ... #endif 지시문이있었습니다. 나는이 중요한 것들에 대해 코드를 작성하는 사람에게 매우 순진한 방식으로 물었고 대규모 프로그래밍 세계에 소개되었습니다.

요점으로 돌아와서, 중복 헤더 정의를 방지하기 위해 지시문을 사용하는 것이 컴파일 시간을 줄이는 데 가장 먼저 배웠습니다.


1
오래되었지만 금. 때로는 명백한 것을 잊어 버렸습니다.
alcor

1
'가드 포함'
gast128

8

더 많은 RAM.

누군가 다른 대답으로 RAM 드라이브에 대해 이야기했습니다. 나는 80286Turbo C ++ (연령 표시) 로이 작업을 수행 했으며 그 결과는 놀랍습니다. 머신이 충돌했을 때 데이터가 손실 된 것과 같습니다.


DOS에서는 많은 메모리를 사용할 수 없습니다
phuclv

6

가능하면 앞으로 선언을 사용하십시오. 클래스 선언이 포인터 또는 형식에 대한 참조 만 사용하는 경우이를 선언하고 구현 파일에 형식의 헤더를 포함시킬 수 있습니다.

예를 들면 다음과 같습니다.

// T.h
class Class2; // Forward declaration

class T {
public:
    void doSomething(Class2 &c2);
private:
    Class2 *m_Class2Ptr;
};

// T.cpp
#include "Class2.h"
void Class2::doSomething(Class2 &c2) {
    // Whatever you want here
}

포함이 적을수록 전처리기에 충분한 작업을 수행 할 수 있습니다.


동일한 번역본이 여러 번역 단위에 포함 된 경우에만 문제가되지 않습니까? 템플릿이 사용되는 경우와 같이 번역 단위가 하나만 있으면 영향을 미치지 않는 것 같습니다.
AlwaysLearning

1
번역 단위가 하나만 있으면 왜 헤더에 넣는 것이 귀찮습니까? 소스 파일에 내용을 넣는 것이 더 합리적이지 않습니까? 헤더의 요점은 둘 이상의 소스 파일에 포함될 가능성이 있습니까?
Evan Teran


5

사용하다

#pragma once

헤더 파일의 맨 위에 있으므로 번역 단위에 두 번 이상 포함 된 경우 헤더의 텍스트는 한 번만 포함되고 구문 분석됩니다.


2
#pragma once는 광범위하게 지원되지만 비표준입니다. en.wikipedia.org/wiki/Pragma_once
ChrisInEdmonton

7
요즘에는 일반 포함 가드가 같은 효과를냅니다. 파일 상단에있는 한, 컴파일러는 #pragma once로 처리 할 수 ​​있습니다.
jalf


4
  • 컴퓨터 업그레이드

    1. 쿼드 코어 (또는 듀얼 쿼드 시스템) 확보
    2. 많은 RAM을 확보하십시오.
    3. RAM 드라이브를 사용하여 파일 I / O 지연을 크게 줄입니다. (하드 드라이브처럼 작동하는 IDE 및 SATA RAM 드라이브를 만드는 회사가 있습니다).
  • 그런 다음 다른 일반적인 제안이 있습니다.

    1. 가능한 경우 사전 컴파일 된 헤더를 사용하십시오.
    2. 프로젝트의 부분들 사이의 커플 링 양을 줄이십시오. 하나의 헤더 파일을 변경하면 일반적으로 전체 프로젝트를 다시 컴파일 할 필요가 없습니다.

4

RAM 드라이브 사용에 대한 아이디어가있었습니다 . 내 프로젝트의 경우 결국 그다지 큰 차이를 만들지 않는 것으로 나타났습니다. 그러나 그들은 여전히 ​​작습니다. 시도 해봐! 나는 그것이 얼마나 도움이되었는지에 관심이 있습니다.


허. 왜 누군가가 이것을 투표하지 않았습니까? 내일해볼 게요
Scott Langham

1
나는 downvote가 큰 차이를 만들지 않기 때문에 기대합니다. 사용하지 않는 RAM이 충분하면 OS는이를 지능적으로 디스크 캐시로 사용합니다.
MSalters

1
@MSalters- "충분한"양은 얼마입니까? 나는 그 이론이지만, 램 드라이브를 사용하여 몇 가지 이유로 알고 않습니다 실제로 상당한 향상을 제공합니다. Go go ...
Vilx-

1
프로젝트를 컴파일하고 입력 및 임시 파일을 캐시하기에 충분합니다. GB 단위는 프로젝트 크기에 직접적으로 좌우됩니다. 구형 OS (특히 WinXP)에서는 파일 캐시가 상당히 게으 르기 때문에 RAM을 사용하지 않습니다.
MSalters

파일이 전체 느린 IO를 먼저 수행하지 않고 이미 램에 있으면 램 드라이브가 더 빠릅니다. 그런 다음 램에 있습니까? (변경된 파일의 반복 반복-디스크에 다시 쓰기 등).
paulm

3

동적 연결 (.so)은 정적 연결 (.a)보다 훨씬 빠릅니다. 특히 네트워크 드라이브 속도가 느린 경우. .a 파일에 모든 코드가 있으므로 처리하고 작성해야합니다. 또한 훨씬 더 큰 실행 파일을 디스크에 기록해야합니다.


동적 연결은 많은 종류의 연결 시간 최적화를 방지하므로 많은 경우 출력이 느려질 수 있습니다.
phuclv

3

컴파일 시간이 아니라 빌드 시간에 대해 :

  • 빌드 파일을 작업 할 때 동일한 파일을 다시 빌드해야하는 경우 ccache를 사용하십시오.

  • make 대신 ninja-build 를 사용하십시오 . 현재 ~ 100 소스 파일로 프로젝트를 컴파일하고 있으며 모든 것이 ccache에 의해 캐시됩니다. 5 분이면 닌자가 1보다 작아야합니다.

를 사용하여 cmake에서 닌자 파일을 생성 할 수 있습니다 -GNinja.


3

어디에서 시간을 보내십니까? 당신은 CPU 바운드입니까? 메모리 바운드? 디스크 바운드? 더 많은 코어를 사용할 수 있습니까? 더 많은 RAM? RAID가 필요하십니까? 현재 시스템의 효율성을 높이고 싶습니까?

gcc / g ++에서 ccache 를 보았 습니까? make clean; make많이 하고 있다면 도움이 될 수 있습니다 .


2

빠른 하드 디스크.

컴파일러는 많은 (그리고 아마도 큰) 파일을 디스크에 씁니다. 일반적인 하드 디스크 대신 SSD를 사용하면 컴파일 시간이 훨씬 단축됩니다.



2

탐색 대기 시간이 길어 네트워크 공유가 빌드 속도를 크게 느리게합니다. 네트워크 공유 드라이브가 매우 빠르더라도 Boost와 같은 것은 나에게 큰 차이가있었습니다. 네트워크 공유에서 로컬 SSD로 전환했을 때 장난감 부스트 프로그램을 컴파일하는 시간이 약 1 분에서 1 초로 단축되었습니다.


2

다중 코어 프로세서가있는 경우 Visual Studio (2005 이상) 및 GCC 는 모두 다중 프로세서 컴파일을 지원합니다. 하드웨어가 있다면 가능하게 할 수 있습니다.


2
@Fellman, 다른 답변을 참조하십시오. -j # 옵션을 사용하십시오.
strager

1

"기술"은 아니지만 많은 소스 파일을 가진 Win32 프로젝트가 "Hello World"빈 프로젝트보다 빠르게 컴파일되는 방법을 알 수 없었습니다. 따라서, 이것이 저와 같은 사람에게 도움이되기를 바랍니다.

Visual Studio에서 컴파일 시간을 늘리는 한 가지 옵션은 증분 연결 ( / INCREMENTAL )입니다. / LTCG (Link-Time Code Generation)와 호환되지 않으므로 릴리스 빌드를 수행 할 때 증분 링크를 비활성화해야합니다.


1
Link-time Code Generation을 비활성화하는 것은 많은 최적화를 비활성화하기 때문에 좋은 제안이 아닙니다. 당신은 활성화해야합니다 /INCREMENTAL디버그 모드에서만
phuclv

1

Visual Studio 2017부터는 시간이 걸리는 것에 대한 몇 가지 컴파일러 메트릭을 사용할 수 있습니다.

프로젝트 특성 창의 C / C ++-> 명령 행 (추가 옵션)에 해당 매개 변수를 추가하십시오. /Bt+ /d2cgsummary /d1reportTime

이 게시물에서 더 많은 정보 얻을 수 있습니다 .


0

정적 링크 대신 동적 링크를 사용하면 느낄 수있는 컴파일러가 더 빨라집니다.

t Cmake를 사용하는 경우 속성을 활성화하십시오.

set(BUILD_SHARED_LIBS ON)

정적 링크를 사용하는 빌드 릴리스는 더 최적화 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.