실제 예는 다음과 같습니다. 고정 소수점은 이전 컴파일러에서 곱합니다.
이들은 부동 소수점이없는 장치에서 유용 할뿐만 아니라 예측 가능한 오류와 함께 32 비트의 정밀도를 제공하므로 정밀도가 높아지면 빛을 발합니다 (부동은 23 비트 만 있고 정밀 손실을 예측하기가 더 어렵습니다). 즉, 균일 에 가까운 상대 정밀도 ( ) 대신 전체 범위에서 균일 한 절대 정밀도 입니다.float
최신 컴파일러는이 고정 소수점 예제를 훌륭하게 최적화하므로 여전히 컴파일러 관련 코드가 필요한 최신 예제는 다음을 참조하십시오.
- 64 비트 정수 곱셈의 많은 부분 얻기 :
uint64_t
32x32 => 64 비트 곱하기에 사용하는 이식 가능한 버전 은 64 비트 CPU에서 최적화하지 못하므로 __int128
64 비트 시스템의 내장 코드 나 효율적인 코드 가 필요 합니다.
- Windows 32 비트의 _umul128 : 32 비트 정수를 64로 캐스트 할 때 MSVC가 항상 잘 작동하지 않으므로 내장 함수가 많은 도움이되었습니다.
C에는 완전 곱셈 연산자가 없습니다 (N 비트 입력의 2N 비트 결과). C로 표현하는 일반적인 방법은 입력을 더 넓은 유형으로 캐스트하고 컴파일러가 입력의 상위 비트가 흥미롭지 않다는 것을 인식하기를 바랍니다.
// on a 32-bit machine, int can hold 32-bit fixed-point integers.
int inline FixedPointMul (int a, int b)
{
long long a_long = a; // cast to 64 bit.
long long product = a_long * b; // perform multiplication
return (int) (product >> 16); // shift by the fixed point bias
}
이 코드의 문제점은 C 언어로 직접 표현할 수없는 무언가를한다는 것입니다. 우리는 두 개의 32 비트 숫자를 곱하고 64 비트 결과를 얻고 그 결과 중간 32 비트를 반환합니다. 그러나 C에서는이 배수가 존재하지 않습니다. 정수를 64 비트로 승격시키고 64 * 64 = 64 곱하기 만하면됩니다.
그러나 x86 (및 ARM, MIPS 및 기타)은 단일 명령어로 곱할 수 있습니다. 일부 컴파일러는이 사실을 무시하고 곱셈을 수행하기 위해 런타임 라이브러리 함수를 호출하는 코드를 생성했습니다. 16만큼의 시프트는 종종 라이브러리 루틴에 의해 수행됩니다 (x86도 그러한 시프트를 수행 할 수 있습니다).
따라서 우리는 곱하기 위해 하나 또는 두 개의 라이브러리 호출을 남겼습니다. 이것은 심각한 결과를 초래합니다. 시프트는 느릴뿐만 아니라 함수 호출에서 레지스터를 유지해야하며 인라인 및 코드 언 롤링에도 도움이되지 않습니다.
(인라인) 어셈블러에서 동일한 코드를 다시 작성하면 상당한 속도 향상을 얻을 수 있습니다.
이 외에도 ASM을 사용하는 것이 문제를 해결하는 가장 좋은 방법은 아닙니다. 대부분의 컴파일러에서는 C로 표현할 수없는 경우 어셈블러 명령어를 내장 형식으로 사용할 수 있습니다. 예를 들어 VS.NET2008 컴파일러는 32 * 32 = 64 비트 mul을 __emul로, 64 비트 이동을 __ll_rshift로 노출합니다.
내장 함수를 사용하면 C 컴파일러가 진행 상황을 이해할 수있는 방식으로 함수를 다시 작성할 수 있습니다. 이를 통해 코드를 인라인하고 레지스터를 할당하며 공통 하위 표현식을 제거하고 지속적인 전파를 수행 할 수 있습니다. 당신은 얻을 것이다 큰 손으로 쓴 어셈블러 코드 방식으로 그 이상의 성능 향상을.
참조 : VS.NET 컴파일러의 고정 소수점 mul에 대한 최종 결과는 다음과 같습니다.
int inline FixedPointMul (int a, int b)
{
return (int) __ll_rshift(__emul(a,b),16);
}
고정 소수점 나누기의 성능 차이는 훨씬 큽니다. 몇 개의 asm-line을 작성하여 분할 고정 고정 소수점 코드에 대해 요소 10까지 개선했습니다.
Visual C ++ 2013을 사용하면 두 가지 방법 모두에 동일한 어셈블리 코드가 제공됩니다.
2007 년 gcc4.1은 순수한 C 버전을 훌륭하게 최적화합니다. (Godbolt 컴파일러 탐색기에는 이전 버전의 gcc가 설치되어 있지 않지만 구식 GCC 버전조차도 내장 기능 없이이 작업을 수행 할 수 있습니다.)
Godbolt 컴파일러 탐색기 에서 x86 (32 비트) 및 ARM에 대한 source + asm을 참조하십시오 . (불행히도 간단한 순수한 C 버전에서 잘못된 코드를 생성 할만큼 오래된 컴파일러는 없습니다.)
현대 CPU가 C가 사업자가없는 일을 할 수 전혀 같은 popcnt
또는 비트 스캔 첫 번째 또는 마지막 세트 비트를 찾을 . (POSIX에는 ffs()
기능이 있지만 그 의미는 x86 bsf
/ 와 일치하지 않습니다 bsr
. https://en.wikipedia.org/wiki/Find_first_set 참조 ).
일부 컴파일러는 정수의 세트 비트 수를 계산하여 popcnt
명령으로 컴파일하는 루프를 인식 할 수 있지만 (컴파일 타임에 활성화 된 경우) __builtin_popcnt
GNU C 또는 x86에서만 사용하는 것이 훨씬 안정적입니다. SSE4.2와 하드웨어를 대상으로 : _mm_popcnt_u32
에서<immintrin.h>
.
또는 C ++에서 a에 할당하고을 std::bitset<32>
사용하십시오 .count()
. (이것은 언어가 이식 항상 올바른 일을 컴파일하는 방식으로, 표준 라이브러리를 통해 popcount의 최적화 된 구현을 노출하는 방법을 발견 한 경우이며, 어떤 대상 지원을 활용할 수 있습니다.) 참조 HTTPS : //en.wikipedia.org/wiki/Hamming_weight#Language_support .
마찬가지로 일부 C 구현에서 ntohl
컴파일 할 수 있습니다 bswap
(엔디안 변환을위한 x86 32 비트 바이트 스왑).
내장 또는 손으로 쓴 asm의 또 다른 주요 영역은 SIMD 명령어를 사용한 수동 벡터화입니다. 컴파일러는와 같은 간단한 루프로 나쁘지는 않지만 dst[i] += src[i] * 10.0;
일이 더 복잡해질 때 종종 나쁘거나 자동 벡터화하지 않습니다. 예를 들어, SIMD를 사용하여 atoi를 구현하는 방법 과 같은 것을 얻지 못할 것입니다 . 스칼라 코드에서 컴파일러가 자동으로 생성합니다.