컴파일러가 모든 것을 인라인하지 않는 이유는 무엇입니까? [닫은]

때때로 컴파일러는 함수 호출을 인라인합니다. 즉, 호출 된 함수의 코드를 호출 함수로 이동합니다. 이렇게하면 통화 스택에서 물건을 밀거나 뺄 필요가 없기 때문에 약간 더 빨라집니다.

내 질문은, 왜 컴파일러가 모든 것을 인라인하지 않습니까? 실행 파일이 훨씬 빠르다고 가정합니다.

내가 생각할 수있는 유일한 이유는 상당히 큰 실행 파일이지만 요즘에는 수백 GB의 메모리가 중요합니까? 개선 된 성능이 가치가 없습니까?

컴파일러가 모든 함수 호출을 인라인하지 않는 다른 이유가 있습니까?



답변

먼저 인라인의 주요 효과 중 하나는 호출 사이트에서 추가 최적화가 가능하다는 것입니다.

귀하의 질문에 대해 : 인라인하기 어렵거나 불가능한 것들이 있습니다.

  • 동적으로 연결된 라이브러리

  • 동적으로 결정된 함수 (함수 포인터를 통해 호출 된 동적 디스패치)

  • 재귀 함수 (꼬리 재귀 캔)

  • 코드가없는 기능 (그러나 링크 시간 최적화는 일부 기능을 허용합니다)

그런 다음 인라인은 유익한 효과뿐만 아니라

  • 더 큰 실행 파일은 더 많은 디스크 공간과 더 큰로드 시간을 의미합니다

  • 실행 파일이 클수록 캐시 압력이 증가 함을 의미합니다 (간단한 게터와 같은 작은 기능을 충분히 줄이면 실행 파일 크기와 캐시 압력이 줄어들 수 있습니다)

마지막으로, 사소한 시간이 걸리지 않는 함수의 경우 이득은 그만한 가치가 없습니다.


답변

주요 제한 사항은 런타임 다형성입니다. 작성할 때 동적 디스패치가 발생하면 foo.bar()메소드 호출을 인라인 할 수 없습니다. 이것은 컴파일러가 모든 것을 인라인하지 않는 이유를 설명합니다.

재귀 호출도 쉽게 인라인 할 수 없습니다.

크로스 모듈 인라이닝은 기술적 인 이유로 수행하기 어렵습니다 (예 : 증분 재 컴파일은 불가능)

그러나 컴파일러는 많은 것을 인라인합니다.


답변

첫째, 항상 인라인 할 수는 없습니다. 예를 들어, 재귀 함수는 항상 인라이닝되지 않을 수 있습니다 (그러나 fact인쇄만으로 재귀 정의를 포함하는 프로그램 fact(8)은 인라인 될 수 있습니다).

그러면 인라인이 항상 유익하지는 않습니다. 컴파일러가 인라인을 너무 많이하여 결과 코드가 핫 파트가 L1 명령어 캐시에 맞지 않을 정도로 충분히 크면 인라인되지 않은 버전 (L1 캐시에 쉽게 맞음)보다 훨씬 느릴 수 있습니다 … 또한, 최근의 프로세서는 CALL기계 명령 (적어도 알려진 위치, 즉 직접 호출, 포인터를 통한 호출이 아닌)까지 기계 명령 을 실행하는 데 매우 빠르다 .

마지막으로 전체 인라인에는 전체 프로그램 분석이 필요합니다. 이것은 가능하지 않거나 너무 비쌉니다. GCC (및 Clang / LLVM )에 의해 컴파일 된 C 또는 C ++ 을 사용하면 링크 타임 최적화 (예 : 컴파일 및 링크 )를 활성화해야하며 g++ -flto -O2컴파일 시간이 많이 걸립니다.


답변

놀랍게도 모든 것을 인라인한다고해서 반드시 실행 시간이 단축되는 것은 아닙니다. 코드 크기가 커지면 CPU가 모든 코드를 캐시에 한 번에 유지하기가 어려울 수 있습니다. 코드의 캐시 미스가 더 커지고 캐시 미스가 비쌉니다. 잠재적으로 인라인 된 함수가 크면 훨씬 나빠집니다.

헤더 파일에서 ‘인라인’으로 표시된 코드를 대량으로 가져 와서 소스 코드에 넣음으로써 코드의 성능이 향상되었습니다. 따라서 코드는 모든 호출 사이트가 아닌 한 곳에만 있습니다. 그런 다음 CPU 캐시가 더 잘 사용되고 컴파일 시간이 더 좋아집니다 …


답변

모든 것을 인라인하면 디스크 메모리 소비가 증가하는 것이 아니라 내부 메모리 소비가 증가한다는 것을 의미합니다. 코드는 또한 코드 세그먼트의 메모리에 의존한다는 것을 기억하십시오. 함수가 10000 개 장소 (예 : 상당히 큰 프로젝트의 표준 라이브러리에있는 함수)에서 호출되면 해당 함수의 코드는 10000 배 더 많은 내부 메모리를 차지합니다.

또 다른 이유는 JIT 컴파일러 일 수 있습니다. 모든 것이 인라인이면 동적으로 컴파일 할 핫스팟이 없습니다.


답변

하나, 모든 것을 인라인하면 매우 나쁘게 작동하는 간단한 예가 있습니다. 이 간단한 C 코드를 고려하십시오.

void f1 (void) { printf ("Hello, world\n"); }
void f2 (void) { f1 (); f1 (); f1 (); f1 (); }
void f3 (void) { f2 (); f2 (); f2 (); f2 (); }
...
void f99 (void) { f98 (); f98 (); f98 (); f98 (); }

모든 것이 당신에게 어떤 영향을 줄지 추측하십시오.

다음으로 인라인을 사용하면 작업 속도가 빨라진다는 가정을합니다. 때때로 그런 경우가 있지만 항상 그런 것은 아닙니다. 한 가지 이유는 명령 캐시에 맞는 코드가 훨씬 빠르게 실행되기 때문입니다. 10 곳에서 함수를 호출하면 항상 명령어 캐시에있는 코드를 실행합니다. 인라인 인 경우 사본이 모든 곳에 있으며 훨씬 느리게 실행됩니다.

인라인은 큰 기능을 생성합니다. 거대한 기능은 최적화하기가 훨씬 어렵습니다. 컴파일러가 함수를 인라인하지 못하게하기 위해 함수를 별도의 파일에 숨겨 성능에 중요한 코드가 크게 향상되었습니다. 결과적으로 이러한 함수에 대해 생성 된 코드는 숨겨 졌을 때 훨씬 더 좋았습니다.

BTW. “수백 GB의 메모리”가 없습니다. 제 작품 컴퓨터에는 “수백 GB의 하드 드라이브 공간”도 없습니다. 내 응용 프로그램이 “수백 GB의 메모리”인 경우 응용 프로그램을 메모리에로드하는 데 20 분이 걸립니다.