다음을 통해 공유


최적화를 위한 유용한 정보

이 문서에서는 Visual C++의 최적화와 관련된 몇 가지 모범 사례에 대해 설명합니다. 다음 항목에 대해 설명합니다.

  • 컴파일러 및 링커 옵션

    • 프로필 기반 최적화

    • 적합한 최적화 수준

    • 부동 소수점 스위치

  • 최적화 declspec

  • 최적화 pragma

  • __restrict 및 __assume

  • 내장 함수 지원

  • 예외

컴파일러 및 링커 옵션

프로필 기반 최적화

Visual C++에서는 PGO(프로필 기반 최적화)를 지원합니다. 이 최적화에서는 이전에 응용 프로그램의 계측된 버전을 실행하여 얻은 프로필 데이터를 사용하여 이후에 응용 프로그램의 최적화를 수행합니다. PGO는 사용하는 데 많은 시간이 필요하므로 모든 개발자가 사용할 수는 없지만, 제품의 최종 릴리스 빌드에는 PGO를 사용하는 것이 좋습니다. 자세한 내용은 프로필 기반 최적화을 참조하십시오.

또한 링크 시간 코드 생성이라고도 하는 전체 프로그램 최적화 및 /O1/O2 최적화가 향상되었습니다. 일반적으로 이들 옵션 중 하나를 사용하여 컴파일한 응용 프로그램은 이전 버전의 컴파일러에서 컴파일한 동일한 응용 프로그램보다 빠릅니다.

자세한 내용은 /GL(전체 프로그램 최적화)/O1, /O2(크기 최소화, 속도 최대화)를 참조하십시오.

적합한 최적화 수준

가능하면 최종 릴리스 빌드는 프로필 기반 최적화를 사용하여 컴파일해야 합니다. 인프라가 부족하여 계측된 빌드를 실행할 수 없거나 응용 프로그램 사용 시나리오를 알 수 없어 PGO를 사용하여 빌드할 수 없는 경우에는 전체 프로그램 최적화를 사용하여 빌드하는 것이 좋습니다.

/Gy 스위치도 매우 유용합니다. 이 스위치를 사용하면 각 함수에 대해 별도의 COMDAT가 생성되므로 링커에서 보다 유연하게 참조되지 않는 COMDAT 제거 및 COMDAT 정리를 수행할 수 있습니다. /Gy를 사용하는 경우 유일한 단점은 빌드 시간이 약간 길어진다는 것입니다. 따라서 일반적으로 이 스위치를 사용하는 것이 좋습니다. 자세한 내용은 /Gy(함수 수준 링크 사용)을 참조하십시오.

64비트 환경에서 링크하는 경우 /OPT:REF,ICF 링커 옵션을, 32비트 환경에서는 /OPT:REF를 사용하는 것이 좋습니다. 자세한 내용은 /OPT(최적화)을 참조하십시오.

또한 최적화된 릴리스 빌드에서도 디버그 기호를 생성하는 것이 좋습니다. 이렇게 해도 생성되는 코드에는 영향을 주지 않으며, 필요한 경우 응용 프로그램을 디버깅하기가 매우 쉬워집니다.

부동 소수점 스위치

/Op 컴파일러 옵션이 제거되었고 부동 소수점 최적화를 다루는 다음과 같은 네 개의 컴파일러 옵션이 추가되었습니다.

/fp:precise

대부분의 경우에 권장되는 기본값입니다.

/fp:fast

게임 등 성능이 가장 중요한 경우에 권장됩니다. 이 옵션을 사용하면 성능이 가장 빨라집니다.

/fp:strict

정확한 부동 소수점 예외 및 IEEE 동작이 필요한 경우에 권장됩니다. 이 옵션을 사용하면 성능이 가장 느려집니다.

/fp:except[-]

/fp:strict 또는 /fp:precise와 함께 사용할 수 있지만 /fp:fast와 함께 사용할 수는 없습니다.

자세한 내용은 /fp(부동 소수점 동작 지정)을 참조하십시오.

최적화 declspec

이 단원에서는 프로그램에서 성능을 향상시키는 데 사용할 수 있는 두 개의 declspec인 __declspec(restrict) 및 __declspec(noalias)을 살펴봅니다.

restrict declspec은 __declspec(restrict) void *malloc(size_t size);과 같이 포인터를 반환하는 함수 선언에만 적용할 수 있습니다.

restrict declspec은 별칭이 지정되지 않은 포인터를 반환하는 함수에 사용됩니다. 이 키워드는 malloc의 C 런타임 라이브러리 구현에 사용됩니다. 그 이유는 이미 해제된 메모리를 사용하는 등의 잘못된 작업을 수행하지 않는 한 이 함수는 현재 프로그램에서 이미 사용 중인 포인터 값을 반환하지 않기 때문입니다.

restrict declspec을 사용하면 컴파일러 최적화를 수행하기 위한 보다 자세한 정보가 컴파일러에 제공됩니다. 컴파일러가 판단하기 가장 어려운 내용 중 하나는 다른 포인터의 별칭으로 사용되는 포인터를 확인하는 것이며, 이 정보를 제공하면 컴파일러에 매우 큰 도움이 됩니다.

이 정보는 컴파일러에 대한 약속에 불과하며 컴파일러에서 확인하는 것은 아닙니다. 프로그램에서 이 restrict declspec을 잘못 사용하면 프로그램이 잘못 작동할 수 있습니다.

자세한 내용은 restrict을 참조하십시오.

noalias declspec도 함수에만 적용할 수 있으며, 함수가 부분 순수 함수임을 나타냅니다. 부분 순수 함수는 지역 변수, 인수 및 인수의 1차 간접 참조만 참조하거나 수정하는 함수입니다. 이 declspec은 컴파일러에 대한 약속이며, 함수에서 전역 변수 또는 포인터 인수의 2차 간접 참조를 참조하면 컴파일러에서 생성하는 코드로 인해 응용 프로그램이 제대로 작동하지 않을 수 있습니다.

자세한 내용은 noalias을 참조하십시오.

최적화 pragma

코드 최적화와 관련된 몇 가지 유용한 pragma도 있습니다. 여기서 살펴볼 첫 번째 pragma는 #pragma optimize입니다.

#pragma optimize("{opt-list}", on | off)

이 pragma를 사용하면 함수별로 최적화 수준을 설정할 수 있습니다. 이 기능은 특정 함수를 최적화하여 컴파일할 때 응용 프로그램이 충돌하는 것과 같이 드물게 발생하는 경우에 유용합니다. 이 pragma를 사용하여 단일 함수의 최적화를 해제할 수 있습니다.

#pragma optimize("", off)
int myFunc() {...}
#pragma optimize("", on)

자세한 내용은 최적화을 참조하십시오.

인라인은 컴파일러가 수행하는 가장 중요한 최적화 중 하나이며, 여기서는 인라인 동작을 수정하는 몇 가지 pragma를 살펴 봅니다.

#pragma inline_recursion은 응용 프로그램에서 재귀적 호출을 인라인할 수 있는지 여부를 지정하는 데 유용합니다. 기본적으로 이 pragma는 해제됩니다. 작은 함수를 얕은 수준으로 재귀 호출하는 경우 이 pragma를 설정할 수 있습니다. 자세한 내용은 inline_recursion을 참조하십시오.

다른 유용한 pragma로는 인라인 수준을 제한하는 #pragma inline_depth가 있습니다. 이 pragma는 일반적으로 프로그램 또는 함수의 크기를 제한하려는 경우에 유용합니다. 자세한 내용은 inline_depth을 참조하십시오.

__restrict 및 __assume

Visual C++에는 성능에 도움이 되는 __restrict__assume과 같은 여러 키워드가 있습니다.

우선 __restrict와 __declspec(restrict)은 서로 다르다는 점에 주의해야 합니다. 이 두 키워드에는 일부 연관성이 있지만 의미가 서로 다릅니다. __restrict는 const 또는 volatile과 같은 형식 한정자이지만 포인터 형식에만 사용할 수 있습니다.

__restrict로 한정된 포인터는 __restrict 포인터라고 합니다. __restrict 포인터에는 __restrict 포인터를 통해서만 액세스할 수 있습니다. 즉, __restrict 포인터가 가리키는 데이터에는 다른 포인터를 사용하여 액세스할 수 없습니다.

__restrict는 Visual C++ 최적화 프로그램의 강력한 도구이지만 세심한 주의를 기울여 사용해야 합니다. 이 키워드를 잘못 사용하면 최적화를 수행한 결과로 응용 프로그램이 제대로 동작하지 않을 수 있습니다.

__restrict 키워드는 이전 버전의 /Oa 스위치를 대체합니다.

개발자는 __assume을 사용하여 컴파일러가 일부 변수의 값을 예상하도록 할 수 있습니다.

예를 들어 __assume(a < 5);을 사용하면 최적화 프로그램은 해당 코드 줄에서 변수 a가 5보다 작은 것으로 가정합니다. 마찬가지로 이는 컴파일러에 대한 약속입니다. 프로그램의 이 시점에서 실제로는 a가 6인 경우 컴파일러에서 프로그램을 최적화한 후 프로그램이 예상과 다르게 동작할 수 있습니다. __assume은 switch 문 및 조건식 앞에서 가장 유용하게 사용됩니다.

__assume에는 몇 가지 제한이 있습니다. 첫째로, 이는 __restrict와 마찬가지로 제안일 뿐이므로 컴파일러에 의해 무시될 수 있습니다. 또한 현재 __assume에는 변수와 상수로 구성된 부등식만 사용할 수 있습니다. 예를 들어 assume(a < b)과 같이 기호로만 구성된 부등식은 사용할 수 없습니다.

내장 함수 지원

내장 함수는 컴파일러가 호출 관련 정보를 내부적으로 알고 있는 경우에 사용되는 함수 호출이며, 내장 함수에서는 라이브러리의 함수를 호출하는 대신 해당 함수의 코드를 생성합니다. <Installation_Directory>\VC\include\에 있는 intrin.h 헤더 파일에는 지원되는 세 가지 플랫폼(x86, x64 및 ARM)에서 각각 사용할 수 있는 내장 함수가 모두 들어 있습니다.

프로그래머는 어셈블리를 사용하지 않고도 내장 함수를 통해 코드를 자세히 분석할 수 있습니다. 내장 함수를 사용하면 다음과 같은 여러 이점이 있습니다.

  1. 코드의 이식성이 향상됩니다. 일부 내장 함수는 여러 CPU 아키텍처에서 사용할 수 있습니다.

  2. 코드가 여전히 C/C++로 작성되므로 코드를 읽기가 쉬워집니다.

  3. 코드에서 컴파일러 최적화의 이점을 활용할 수 있습니다. 컴파일러가 발전함에 따라 내장 함수 코드 생성도 개선됩니다.

자세한 내용은 컴파일러 내장 함수내장 함수 사용의 장점를 참조하십시오.

예외

예외를 사용하면 성능이 저하됩니다. try 블록을 사용하면 컴파일러에서 특정 최적화를 수행하지 못하게 하는 몇 가지 제한이 발생합니다. x86 플랫폼에서는 try 블록을 사용하면 코드 실행 도중 상태 정보를 추가로 생성해야 하므로 성능이 더욱 저하됩니다. 64비트 플랫폼에서는 try 블록으로 인한 성능 저하가 그만큼 크지 않지만, 예외가 throw될 때 처리기를 찾고 스택을 해제하는 과정에서 성능에 많은 부담이 생길 수 있습니다.

따라서 코드에 반드시 필요하지 않은 try/catch 블록은 사용하지 않는 것이 좋습니다. 예외를 사용해야 하는 경우에는 가능하면 동기 예외를 사용합니다. 자세한 내용은 구조적 예외 처리 (C/C++)을 참조하십시오.

마지막으로, 예외적인 경우에만 예외를 throw하십시오. 일반적인 제어 흐름에서 예외를 사용하면 성능이 저하되기 쉽습니다.

참고 항목

개념

코드 최적화