C ++ 컴파일 시간을 단축하기 위해 어떤 기술을 사용할 수 있습니까?
이 질문은 Stack Overflow question C ++ programming style에 대한 의견에서 나 왔으며 어떤 아이디어가 있는지 듣고 싶습니다.
관련 질문을 보았습니다. 왜 C ++ 컴파일이 그렇게 오래 걸립니까? 하지만 많은 솔루션을 제공하지는 않습니다.
C ++ 컴파일 시간을 단축하기 위해 어떤 기술을 사용할 수 있습니까?
이 질문은 Stack Overflow question C ++ programming style에 대한 의견에서 나 왔으며 어떤 아이디어가 있는지 듣고 싶습니다.
관련 질문을 보았습니다. 왜 C ++ 컴파일이 그렇게 오래 걸립니까? 하지만 많은 솔루션을 제공하지는 않습니다.
답변:
여기 및 여기 에서 불투명 포인터 또는 핸들 클래스 라고도 하는 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 / 코어는 모두 컴파일 속도에 차이를 만듭니다.
STAPL 프로젝트에서 일하고 있습니다.이 템플릿은 C ++ 라이브러리입니다. 가끔 컴파일 시간을 줄이기 위해 모든 기술을 다시 방문해야합니다. 여기에서는 우리가 사용하는 기술을 요약했습니다. 이러한 기술 중 일부는 이미 위에 나열되어 있습니다.
심볼 길이와 컴파일 시간 사이에 입증 된 상관 관계는 없지만 평균 심볼 크기가 작을수록 모든 컴파일러의 컴파일 시간이 향상 될 수 있습니다. 따라서 첫 번째 목표는 코드에서 가장 큰 기호를 찾는 것입니다.
nm
명령을 사용하여 크기에 따라 심볼을 나열 할 수 있습니다 .
nm --print-size --size-sort --radix=d YOUR_BINARY
이 명령에서 --radix=d
크기를 10 진수로 볼 수 있습니다 (기본값은 16 진). 이제 가장 큰 기호를보고 해당 클래스를 중단 할 수 있는지 확인하고 기본 클래스에서 템플릿 화되지 않은 부분을 고려하거나 클래스를 여러 클래스로 분할하여 클래스를 다시 디자인 해보십시오.
일반 nm
명령을 실행 하여 원하는 스크립트 ( AWK , Python 등)로 파이프 하여 길이 에 따라 기호를 정렬 할 수 있습니다. 우리의 경험을 바탕으로,이 방법은 방법 1보다 후보를 더 잘 만드는 가장 큰 문제를 식별합니다.
" Templight 는 템플릿 인스턴스화의 시간과 메모리 소비를 프로파일 링하고 대화 형 디버깅 세션을 수행하여 템플릿 인스턴스화 프로세스에 대한 내성을 확보 하는 Clang 기반 도구입니다."
LLVM 및 Clang ( 지침 ) 을 확인 하고 Templight 패치를 적용하여 Templight를 설치할 수 있습니다 . LLVM 및 Clang의 기본 설정은 디버그 및 어설 션에 있으며 컴파일 시간에 크게 영향을 줄 수 있습니다. Templight에 둘 다 필요한 것처럼 보이므로 기본 설정을 사용해야합니다. LLVM 및 Clang 설치 프로세스는 약 1 시간 정도 걸립니다.
패치를 적용한 후 templight++
설치시 지정한 빌드 폴더에있는 코드를 사용하여 코드를 컴파일 할 수 있습니다.
templight++
PATH에 있는지 확인하십시오 . 이제 컴파일하려면 CXXFLAGS
Makefile 또는 명령 줄 옵션에 다음 스위치를 추가하십시오 .
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 { };
그리고 모두 T
와 U
10 가지 옵션을 가질 수 있습니다, 당신은이 추상적에 다른 클래스에 코드의 공통 부분 해결하기 위해 100 가지 방법으로이 클래스의 가능한 템플릿 인스턴스화를 증가하고있다. 다른 방법은 상속 계층 (클래스 계층 구조 역전)을 사용하는 것이지만이 기술을 사용하기 전에 디자인 목표가 손상되지 않는지 확인하십시오.
이 기술을 사용하면 공통 섹션을 한 번 컴파일하고 나중에 다른 TU (번역 단위)와 연결할 수 있습니다.
클래스의 가능한 모든 인스턴스화를 알고 있으면이 기술을 사용하여 모든 경우를 다른 변환 단위로 컴파일 할 수 있습니다.
예를 들면 다음과 같습니다.
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
.
이 기술에는 두 가지 문제가 있습니다.
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가에 표시되지 않습니다 ( foo1
및 foo4
표시됩니다). 당신이 컴파일하는 경우 -visibility=hidden
만 foo1
표시됩니다. 비록 foo4
숨겨져있을 것입니다.
GCC wiki에서 가시성에 대해 자세히 읽을 수 있습니다 .
"Indie 게임 디자인 및 프로그래밍 게임"에서 다음 기사를 추천합니다.
사실, 그것들은 꽤 오래되었습니다. 사실적인 결과를 얻으려면 최신 버전 (또는 사용 가능한 버전)으로 모든 것을 다시 테스트해야합니다. 어느 쪽이든, 그것은 아이디어의 좋은 원천입니다.
과거에 나를 위해 잘 작동 한 기술 : 여러 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
<core-count> + N
있는 곳에서 병렬로 컴파일 된 하위 목록으로 분할 할 수 있습니다 N
(시스템 메모리 및 시스템 사용 방법에 따라 다름).
이 주제에 대한 전체 책이 있으며 제목은 Large-Scale C ++ Software Design (John Lakos가 작성)입니다.
이 책은 템플릿보다 오래된 것이므로, 그 책의 내용에 "템플릿을 사용하면 컴파일러가 느려질 수있다"고 덧붙인다.
나는 다른 대답에 링크 할 것입니다 : 어떻게 컴파일 시간을 줄이고 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
여기 몇 가지가 있습니다 :
make -j2
좋은 예).-O1
보다 빠릅니다 ).-O2
-O3
-j12
주변 -j18
보다 훨씬 빠릅니다 -j8
. 나는 메모리 대역폭이 제한 요인이되기 전에 당신이 ... 할 수 있습니다 얼마나 많은 코어 궁금하네요
-j
실제 코어 수의 2 배입니다.
위의 모든 코드 트릭 (포워드 선언, 퍼블릭 헤더의 헤더 포함을 최소화하고 Pimpl을 사용 하여 구현 파일 내부의 대부분의 세부 정보를 밀어 넣음 )을 적용하고 다른 언어로 얻을 수있는 것이 없다면 빌드 시스템을 고려하십시오. . Linux를 사용하는 경우 distcc (분산 컴파일러) 및 ccache (캐시 컴파일러) 사용을 고려하십시오.
첫 번째 distcc는 전 처리기 단계를 로컬로 실행 한 다음 네트워크에서 사용 가능한 첫 번째 컴파일러로 출력을 보냅니다. 네트워크의 모든 구성된 노드에서 동일한 컴파일러 및 라이브러리 버전이 필요합니다.
후자 인 ccache는 컴파일러 캐시입니다. 전처리기를 다시 실행 한 다음 전 처리기 파일이 동일한 컴파일러 매개 변수로 이미 컴파일되었는지 내부 데이터베이스 (로컬 디렉토리에 보유)를 확인합니다. 그렇다면 바이너리를 팝업하고 컴파일러의 첫 번째 실행에서 출력합니다.
둘 다 동시에 사용할 수 있으므로 ccache에 로컬 사본이 없으면 distcc를 사용하여 네트를 통해 다른 노드로 보내거나 추가 처리없이 솔루션을 주입 할 수 있습니다.
더 많은 RAM.
누군가 다른 대답으로 RAM 드라이브에 대해 이야기했습니다. 나는 80286 과 Turbo C ++ (연령 표시) 로이 작업을 수행 했으며 그 결과는 놀랍습니다. 머신이 충돌했을 때 데이터가 손실 된 것과 같습니다.
가능하면 앞으로 선언을 사용하십시오. 클래스 선언이 포인터 또는 형식에 대한 참조 만 사용하는 경우이를 선언하고 구현 파일에 형식의 헤더를 포함시킬 수 있습니다.
예를 들면 다음과 같습니다.
// 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
}
포함이 적을수록 전처리기에 충분한 작업을 수행 할 수 있습니다.
사용하다
#pragma once
헤더 파일의 맨 위에 있으므로 번역 단위에 두 번 이상 포함 된 경우 헤더의 텍스트는 한 번만 포함되고 구문 분석됩니다.
완전성을 위해 : 빌드 시스템이 어리 석고 컴파일러가 작업을 수행하는 데 오랜 시간이 걸리기 때문에 빌드가 느려질 수 있습니다.
유닉스 환경에서이 주제에 대한 토론은 재귀 적 유해한 것으로 간주 (PDF)를 읽으 십시오 .
RAM 드라이브 사용에 대한 아이디어가있었습니다 . 내 프로젝트의 경우 결국 그다지 큰 차이를 만들지 않는 것으로 나타났습니다. 그러나 그들은 여전히 작습니다. 시도 해봐! 나는 그것이 얼마나 도움이되었는지에 관심이 있습니다.
동적 연결 (.so)은 정적 연결 (.a)보다 훨씬 빠릅니다. 특히 네트워크 드라이브 속도가 느린 경우. .a 파일에 모든 코드가 있으므로 처리하고 작성해야합니다. 또한 훨씬 더 큰 실행 파일을 디스크에 기록해야합니다.
Linux (및 다른 * NIX)에서 출력을 유지하지 않고 다른 TTY로 변경하여 컴파일 속도를 높일 수 있습니다 .
여기에 실험이 있습니다 : printf 는 프로그램을 느리게합니다
"기술"은 아니지만 많은 소스 파일을 가진 Win32 프로젝트가 "Hello World"빈 프로젝트보다 빠르게 컴파일되는 방법을 알 수 없었습니다. 따라서, 이것이 저와 같은 사람에게 도움이되기를 바랍니다.
Visual Studio에서 컴파일 시간을 늘리는 한 가지 옵션은 증분 연결 ( / INCREMENTAL )입니다. / LTCG (Link-Time Code Generation)와 호환되지 않으므로 릴리스 빌드를 수행 할 때 증분 링크를 비활성화해야합니다.
/INCREMENTAL
디버그 모드에서만