C ++ 컴파일이 왜 그렇게 오래 걸립니까?
C ++ 및 Java와 비교할 때 C ++ 파일을 컴파일하는 데 시간이 오래 걸립니다. 일반적인 크기의 Python 스크립트를 실행하는 것보다 C ++ 파일을 컴파일하는 데 훨씬 오래 걸립니다. 현재 VC ++을 사용하고 있지만 모든 컴파일러와 동일합니다. 왜 이런거야?
내가 생각할 수있는 두 가지 이유는 헤더 파일을로드하고 전처리기를 실행하는 것이었지만 왜 그렇게 오래 걸리는지 설명하지 않는 것 같습니다.
몇 가지 이유
헤더 파일
모든 단일 컴파일 단위에는 수백 또는 수천 개의 헤더가 (1)로드되고 (2) 컴파일되어야합니다. 전처리 기는 헤더 컴파일 결과가 모든 컴파일 단위마다 다를 수 있기 때문에 일반적으로 모든 컴파일 단위마다 다시 컴파일해야합니다 . (매크로는 헤더의 내용을 변경하는 하나의 컴파일 단위로 정의 될 수있다).
이것은 아마도 가 모든 컴파일 단위에 대해 컴파일하는 코드의 엄청난 양을 필요로하며, 또한, 모든 헤더는 (를 포함 모든 컴파일 단위에 대해 한 번) 여러 번 컴파일해야합니다으로, 주된 이유.
연결
일단 컴파일되면 모든 객체 파일이 서로 연결되어야합니다. 이것은 기본적으로 모 놀리 식 프로세스로, 병렬화가 잘되지 않으며 전체 프로젝트를 처리해야합니다.
파싱
구문은 구문 분석하기가 매우 복잡하고 컨텍스트에 따라 크게 다르며 명확하게하기가 매우 어렵습니다. 시간이 많이 걸립니다.
템플릿
C #에서는 List<T>
프로그램에 List의 인스턴스화 횟수에 관계없이 컴파일되는 유일한 유형입니다. C ++에서와는 vector<int>
완전히 분리 된 유형 vector<float>
이며 각 유형은 별도로 컴파일해야합니다.
여기에 템플릿이 컴파일러가 해석해야하는 완전한 Turing-complete "하위 언어"를 구성한다는 것은 엄청나게 복잡해질 수 있습니다. 비교적 단순한 템플릿 메타 프로그래밍 코드조차도 수십 및 수십 개의 템플릿 인스턴스화를 생성하는 재귀 템플릿을 정의 할 수 있습니다. 템플릿은 또한 이름이 엄청나게 긴 매우 복잡한 유형으로 인해 링커에 많은 추가 작업을 추가 할 수 있습니다. (많은 기호 이름을 비교해야하며 이러한 이름이 수천 자로 커질 수 있다면 상당히 비쌀 수 있습니다).
물론 템플릿은 일반적으로 헤더로 정의해야하기 때문에 헤더 파일의 문제점을 악화시킵니다. 즉, 모든 컴파일 단위에 대해 훨씬 더 많은 코드를 구문 분석하고 컴파일해야합니다. 일반 C 코드에서 헤더에는 일반적으로 정방향 선언 만 포함되지만 실제 코드는 거의 없습니다. C ++에서는 거의 모든 코드가 헤더 파일에있는 경우가 드물지 않습니다.
최적화
C ++는 매우 극적인 최적화를 허용합니다. C # 또는 Java는 클래스를 완전히 제거 할 수는 없지만 (반사 목적을 위해 있어야 함) 간단한 C ++ 템플릿 메타 프로그램조차도 수십 또는 수백 개의 클래스를 쉽게 생성 할 수 있습니다.이 클래스는 모두 최적화에서 다시 인라인되고 제거됩니다. 단계.
또한 C ++ 프로그램은 컴파일러에서 완전히 최적화해야합니다. AC # 프로그램은로드시 추가 최적화를 수행하기 위해 JIT 컴파일러에 의존 할 수 있으며 C ++은 이러한 "두 번째 기회"를 얻지 못합니다. 컴파일러가 생성하는 것은 최대한 최적화됩니다.
기계
C ++는 바이트 코드 Java 또는 .NET 사용 (특히 x86의 경우)보다 다소 복잡한 기계 코드로 컴파일됩니다. (이것은 주석 등에서 언급 되었기 때문에 완전성에서 언급됩니다. 실제로이 단계는 전체 컴파일 시간의 작은 부분 이상을 차지하지 않을 것입니다).
결론
이러한 요소의 대부분은 C 코드와 공유되며 실제로 상당히 효율적으로 컴파일됩니다. 구문 분석 단계는 C ++에서 훨씬 더 복잡하며 시간이 훨씬 더 걸릴 수 있지만 주범은 아마도 템플릿 일 것입니다. 그것들은 유용하고 C ++를 훨씬 더 강력한 언어로 만들지 만 컴파일 속도 측면에서 많은 비용을 듭니다.
속도 저하가 컴파일러와 반드시 같을 필요는 없습니다.
델파이 또는 Kylix를 사용하지 않았지만 MS-DOS 시절에는 Turbo Pascal 프로그램이 거의 즉각적으로 컴파일되는 반면 동등한 Turbo C ++ 프로그램은 크롤링됩니다.
두 가지 주요 차이점은 매우 강력한 모듈 시스템과 단일 패스 컴파일을 허용하는 구문이었습니다.
컴파일 속도가 C ++ 컴파일러 개발자에게는 우선 순위가 아닐 수도 있지만 C / C ++ 구문에는 처리를 더욱 어렵게하는 몇 가지 고유 한 문제가 있습니다. (저는 C의 전문가는 아니지만 Walter Bright는 다양한 상용 C / C ++ 컴파일러를 빌드 한 후 D 언어를 만들었습니다. 그의 변경 중 하나 는 문맥을 분석하지 않는 문법을 적용하여 언어를 쉽게 구문 분석하는 것이 었습니다 .)
또한 일반적으로 Makefile은 모든 파일이 C로 개별적으로 컴파일되도록 설정되므로 10 개의 소스 파일이 모두 같은 포함 파일을 사용하는 경우 포함 파일이 10 번 처리됩니다.
파싱과 코드 생성은 실제로 다소 빠릅니다. 실제 문제는 파일을 열고 닫는 것입니다. 포함 가드를 사용하더라도 컴파일러는 여전히 .H 파일을 열고 각 행을 읽은 다음 무시합니다.
친구는 한 번 (직장에서 지루한 동안) 회사의 응용 프로그램을 가져 와서 모든 소스 및 헤더 파일을 하나의 큰 파일에 넣었습니다. 컴파일 시간이 3 시간에서 7 분으로 단축되었습니다.
또 다른 이유는 선언을 찾기 위해 C 전처리기를 사용하기 때문입니다. 헤더 가드를 사용하더라도 .h는 포함될 때마다 계속해서 구문 분석해야합니다. 일부 컴파일러는이를 지원할 수있는 사전 컴파일 된 헤더를 지원하지만 항상 사용되는 것은 아닙니다.
참조 : C ++ 질문과 대답
C ++는 머신 코드로 컴파일됩니다. 따라서 전 처리기, 컴파일러, 최적화 기 및 마지막으로 어셈블러가 모두 실행되어야합니다.
Java 및 C #은 바이트 코드 / IL로 컴파일되고 Java Virtual Machine / .NET Framework는 실행 전에 실행됩니다 (또는 JIT는 머신 코드로 컴파일).
파이썬은 해석 된 언어이며 바이트 코드로 컴파일됩니다.
이것에 대한 다른 이유도 있다고 확신하지만 일반적으로 기본 기계 언어로 컴파일 할 필요가 없으므로 시간이 절약됩니다.
가장 큰 문제는 다음과 같습니다.
1) 무한 헤더 재분석. 이미 언급했습니다. #pragma once와 같은 완화는 일반적으로 빌드 단위가 아니라 컴파일 단위마다 만 작동합니다.
2) 툴 체인이 종종 여러 바이너리 (예 : make, 전 처리기, 컴파일러, 어셈블러, 아카이버, impdef, 링커 및 dlltool)로 분리되어 각 호출마다 항상 모든 상태를 다시 초기화하고 다시로드해야한다는 사실 ( 컴파일러, 어셈블러) 또는 모든 파일 (아카이버, 링커 및 dlltool).
comp.compilers에 대한 다음 토론도 참조하십시오. http://compilers.iecc.com/comparch/article/03-11-078 특별히 이것 :
http://compilers.iecc.com/comparch/article/02-07-128
comp.compilers의 중재자 인 John이 동의하는 것 같습니다. 이는 툴체인을 완전히 통합하고 사전 컴파일 된 헤더를 구현하는 경우 C에서도 비슷한 속도를 달성 할 수 있음을 의미합니다. 많은 상용 C 컴파일러가 어느 정도 이것을 수행합니다.
모든 것을 별도의 바이너리로 분해하는 유닉스 모델은 프로세스 생성 속도가 느린 Windows의 최악의 모델입니다. 특히 make / configure 시스템이 정보를 얻기 위해 일부 프로그램을 호출하는 경우 Windows와 * nix 간의 GCC 빌드 시간을 비교할 때 매우 주목할 만합니다.
C / C ++ 구축 : 실제로 일어나는 일과 왜 그렇게 오래 걸립니까
소프트웨어 개발 시간의 비교적 많은 부분은 코드를 작성, 실행, 디버깅 또는 디자인하는 데 소비되지 않지만 컴파일이 완료되기를 기다리는 데 소요됩니다. 작업을 빠르게하려면 먼저 C / C ++ 소프트웨어를 컴파일 할 때 무슨 일이 일어나고 있는지 이해해야합니다. 단계는 대략 다음과 같습니다.
- 구성
- 빌드 도구 시작
- 의존성 검사
- 편집
- 연결
이제 각 단계를 더 빨리 만드는 방법에 중점을 두어 자세히 살펴 보겠습니다.
구성
이것은 구축을 시작할 때의 첫 번째 단계입니다. 일반적으로 configure 스크립트 또는 CMake, Gyp, SCons 또는 기타 도구를 실행하는 것을 의미합니다. 매우 큰 Autotools 기반 구성 스크립트의 경우 1 초에서 몇 분 정도 걸릴 수 있습니다.
이 단계는 비교적 드물게 발생합니다. 구성을 변경하거나 빌드 구성을 변경할 때만 실행하면됩니다. 빌드 시스템을 변경하지 않으면이 단계를 더 빨리 수행하기 위해 수행 할 작업이 많지 않습니다.
빌드 도구 시작
IDE에서 빌드 아이콘 (보통 make의 별칭)을 클릭하거나 make를 실행할 때 발생합니다. 빌드 도구 바이너리는 구성 파일과 빌드 구성을 시작하고 읽으며 일반적으로 동일한 구성입니다.
빌드의 복잡성과 크기에 따라 1 초에서 몇 초까지 걸릴 수 있습니다. 그 자체로는 그렇게 나쁘지 않을 것입니다. 불행하게도 대부분의 make 기반 빌드 시스템은 make가 모든 빌드마다 수십에서 수백 번 호출되도록합니다. 일반적으로 make의 재귀 적 사용 (나쁜)으로 인해 발생합니다.
Make가 너무 느린 이유는 구현 버그가 아닙니다. Makefile의 구문에는 구현이 거의 불가능한 단점이 있습니다. 이 문제는 다음 단계와 결합 될 때 더욱 두드러집니다.
의존성 검사
빌드 도구가 구성을 읽은 후에는 어떤 파일이 변경되었으며 어떤 파일을 다시 컴파일해야하는지 결정해야합니다. 구성 파일에는 빌드 종속성을 설명하는 방향성 비순환 그래프가 포함되어 있습니다. 이 그래프는 일반적으로 구성 단계에서 작성됩니다. 빌드 도구 시작 시간 및 종속성 스캐너는 모든 빌드마다 실행됩니다. 이들의 결합 된 런타임은 편집 컴파일-디버그주기의 하한을 결정합니다. 소규모 프로젝트의 경우이 시간은 보통 몇 초 정도입니다. 이것은 참을 수 있습니다. 대안이 있습니다. 가장 빠른 것은 Google 엔지니어가 Chromium을 위해 만든 Ninja입니다. CMake 또는 Gyp을 사용하여 빌드하는 경우 해당 Ninja 백엔드로 전환하십시오. 빌드 파일 자체에서 아무것도 변경할 필요가 없으며 속도 향상을 즐기십시오. 그러나 Ninja는 대부분의 배포판에 패키지되어 있지 않습니다.
편집
이 시점에서 마침내 컴파일러를 호출합니다. 일부 모서리를 자르면 대략적인 단계가 있습니다.
- 병합 포함
- 코드 파싱
- 코드 생성 / 최적화
대중적인 믿음과는 달리 C ++를 컴파일하는 것이 실제로 그렇게 느린 것은 아닙니다. STL은 느리고 C ++를 컴파일하는 데 사용되는 대부분의 빌드 도구는 느립니다. 그러나 언어의 느린 부분을 완화하는 더 빠른 도구와 방법이 있습니다.
그것들을 사용하면 약간의 팔꿈치 그리스가 필요하지만 이점은 부인할 수 없습니다. 더 빠른 빌드 시간은 더 행복한 개발자, 더 민첩성 및 궁극적으로 더 나은 코드로 이어집니다.
컴파일 된 언어는 항상 해석되는 언어보다 더 큰 초기 오버 헤드가 필요합니다. 또한 C ++ 코드를 잘 구성하지 않았을 수 있습니다. 예를 들면 다음과 같습니다.
#include "BigClass.h"
class SmallClass
{
BigClass m_bigClass;
}
다음보다 훨씬 느리게 컴파일됩니다.
class BigClass;
class SmallClass
{
BigClass* m_bigClass;
}
더 큰 C ++ 프로젝트에서 컴파일 시간을 줄이는 쉬운 방법은 프로젝트의 모든 cpp 파일을 포함하는 * .cpp 포함 파일을 만들어 컴파일하는 것입니다. 이렇게하면 헤더 폭발 문제가 한 번으로 줄어 듭니다. 이것의 장점은 컴파일 오류가 여전히 올바른 파일을 참조한다는 것입니다.
예를 들어 a.cpp, b.cpp 및 c.cpp.가 있다고 가정합니다. everything.cpp :
#include "a.cpp"
#include "b.cpp"
#include "c.cpp"
그런 다음 모든 것을 만들어서 프로젝트를 컴파일하십시오.
몇 가지 이유는 다음과 같습니다.
1) C ++ 문법은 C # 또는 Java보다 복잡하며 구문 분석하는 데 더 많은 시간이 걸립니다.
2) (더 중요한) C ++ 컴파일러는 기계 코드를 생성하고 컴파일하는 동안 모든 최적화를 수행합니다. C #과 Java는 반쯤 진행되어이 단계를 JIT에 맡깁니다.
당신이 얻는 단점은 프로그램이 조금 더 빨리 실행된다는 것입니다. 개발하는 동안 냉담한 편이지만 개발이 완료되고 사용자가 프로그램을 실행하면 큰 문제가 될 수 있습니다.
C ++에서 컴파일 타임에 한 번만 수행되는 작업 수행 비용으로 인해 C #이 항상 느리게 실행된다는 점에서 대부분의 대답이 불분명합니다.이 성능 비용은 런타임 종속성으로 인해 영향을받습니다 (더 많은로드 할 수있는 것들) C # 프로그램은 항상 더 높은 메모리 공간을 차지하므로 성능은 사용 가능한 하드웨어 기능과 더 밀접한 관련이 있습니다. VM에서 해석되거나 의존하는 다른 언어도 마찬가지입니다.
내가 생각할 수있는 두 가지 문제가 C ++의 프로그램이 컴파일되는 속도에 영향을 줄 수 있습니다.
가능한 문제 # 1-헤더 컴파일 : (이것은 이미 다른 답변이나 의견으로 해결되었을 수도 있고 아닐 수도 있습니다.) Microsoft Visual C ++ (AKA VC ++)는 미리 컴파일 된 헤더를 지원하므로 적극 권장합니다. 새 프로젝트를 만들고 만들고있는 프로그램 유형을 선택하면 설정 마법사 창이 화면에 나타납니다. 맨 아래에있는 "다음>"단추를 누르면 창에 여러 기능 목록이있는 페이지가 나타납니다. “사전 컴파일 된 헤더”옵션 옆의 상자가 선택되어 있는지 확인하십시오. (참고 : 이것은 C ++의 Win32 콘솔 응용 프로그램에 대한 나의 경험 이었지만 C ++의 모든 종류의 프로그램에는 해당되지 않을 수 있습니다.)
가능한 문제 # 2-완성 된 위치 : 올 여름, 저는 프로그래밍 과정을 밟았으며 실험실에서 컴퓨터가 매일 밤 자정에 지워지면서 모든 프로젝트를 8GB 플래시 드라이브에 저장해야했습니다. 그것은 우리의 모든 일을 지 웠을 것입니다. 이식성 / 보안 등을 위해 외부 저장 장치로 컴파일하는 경우 시간이 오래 걸릴 수 있습니다프로그램이 컴파일하는 데 걸리는 시간 (위에서 미리 컴파일 된 헤더조차도), 특히 상당히 큰 프로그램 인 경우. 이 경우에 대한 조언은 사용중인 컴퓨터의 하드 드라이브에서 프로그램을 작성하고 컴파일하는 것입니다. 어떤 이유로 든 프로젝트 작업을 중단하고 싶을 때마다 외부로 전송하십시오. 저장 장치를 선택한 다음 "하드웨어 안전하게 제거 및 미디어 꺼내기"아이콘을 클릭합니다.이 아이콘은 흰색 확인 표시가있는 작은 녹색 원 뒤에 작은 플래시 드라이브로 표시되어 연결을 끊습니다.
이것이 도움이되기를 바랍니다. 그럴 경우 알려주세요! :)
참고 URL : https://stackoverflow.com/questions/318398/why-does-c-compilation-take-so-long
'Programming' 카테고리의 다른 글
==와 같음 ()의 C # 차이 (0) | 2020.02.11 |
---|---|
Swift 언어에서 느낌표는 무엇을 의미합니까? (0) | 2020.02.11 |
SQL Server VARCHAR / NVARCHAR 문자열에 줄 바꿈을 삽입하는 방법 (0) | 2020.02.11 |
JavaScript에서 "double tilde"(~~) 연산자 란 무엇입니까? (0) | 2020.02.11 |
cURL이 진행률 표시 줄을 표시하지 않게하려면 어떻게합니까? (0) | 2020.02.11 |