etc./StackOverFlow

0.1f를 0으로 변경하면 성능이 10배 느려지는 이유는 무엇입니까?

청렴결백한 만능 재주꾼 2022. 1. 25. 11:40
반응형

질문자 :GlassFish


이 약간의 코드는 왜,

 const float x[16] = { 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6}; const float z[16] = {1.123, 1.234, 1.345, 156.467, 1.578, 1.689, 1.790, 1.812, 1.923, 2.034, 2.145, 2.256, 2.367, 2.478, 2.589, 2.690}; float y[16]; for (int i = 0; i < 16; i++) { y[i] = x[i]; } for (int j = 0; j < 9000000; j++) { for (int i = 0; i < 16; i++) { y[i] *= x[i]; y[i] /= z[i]; y[i] = y[i] + 0.1f; // <-- y[i] = y[i] - 0.1f; // <-- } }

다음 비트보다 10배 이상 빠르게 실행됩니까(표시된 경우를 제외하고 동일)?

 const float x[16] = { 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6}; const float z[16] = {1.123, 1.234, 1.345, 156.467, 1.578, 1.689, 1.790, 1.812, 1.923, 2.034, 2.145, 2.256, 2.367, 2.478, 2.589, 2.690}; float y[16]; for (int i = 0; i < 16; i++) { y[i] = x[i]; } for (int j = 0; j < 9000000; j++) { for (int i = 0; i < 16; i++) { y[i] *= x[i]; y[i] /= z[i]; y[i] = y[i] + 0; // <-- y[i] = y[i] - 0; // <-- } }

Visual Studio 2010 SP1로 컴파일할 때. 최적화 수준은 sse2 활성화된 -02 다른 컴파일러로 테스트하지 않았습니다.



비정규화된 부동 소수점 의 세계에 오신 것을 환영합니다! 성능에 지장을 줄 수 있습니다!!!

비정규(또는 비정규) 숫자는 부동 소수점 표현에서 0에 매우 가까운 추가 값을 얻기 위한 일종의 해킹입니다. 비정규화된 부동 소수점에 대한 작업은 정규화된 부동 소수점보다 수십 배에서 수백 배 느릴 수 있습니다. 이는 많은 프로세서가 직접 처리할 수 없고 마이크로코드를 사용하여 트랩하고 해결해야 하기 때문입니다.

0 0.1 을 사용하는지에 따라 다른 값으로 수렴되었음을 알 수 있습니다.

다음은 x64에서 컴파일된 테스트 코드입니다.

 int main() { double start = omp_get_wtime(); const float x[16]={1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9,2.0,2.1,2.2,2.3,2.4,2.5,2.6}; const float z[16]={1.123,1.234,1.345,156.467,1.578,1.689,1.790,1.812,1.923,2.034,2.145,2.256,2.367,2.478,2.589,2.690}; float y[16]; for(int i=0;i<16;i++) { y[i]=x[i]; } for(int j=0;j<9000000;j++) { for(int i=0;i<16;i++) { y[i]*=x[i]; y[i]/=z[i]; #ifdef FLOATING y[i]=y[i]+0.1f; y[i]=y[i]-0.1f; #else y[i]=y[i]+0; y[i]=y[i]-0; #endif if (j > 10000) cout << y[i] << " "; } if (j > 10000) cout << endl; } double end = omp_get_wtime(); cout << end - start << endl; system("pause"); return 0; }

산출:

 #define FLOATING 1.78814e-007 1.3411e-007 1.04308e-007 0 7.45058e-008 6.70552e-008 6.70552e-008 5.58794e-007 3.05474e-007 2.16067e-007 1.71363e-007 1.49012e-007 1.2666e-007 1.11759e-007 1.04308e-007 1.04308e-007 1.78814e-007 1.3411e-007 1.04308e-007 0 7.45058e-008 6.70552e-008 6.70552e-008 5.58794e-007 3.05474e-007 2.16067e-007 1.71363e-007 1.49012e-007 1.2666e-007 1.11759e-007 1.04308e-007 1.04308e-007 //#define FLOATING 6.30584e-044 3.92364e-044 3.08286e-044 0 1.82169e-044 1.54143e-044 2.10195e-044 2.46842e-029 7.56701e-044 4.06377e-044 3.92364e-044 3.22299e-044 3.08286e-044 2.66247e-044 2.66247e-044 2.24208e-044 6.30584e-044 3.92364e-044 3.08286e-044 0 1.82169e-044 1.54143e-044 2.10195e-044 2.45208e-029 7.56701e-044 4.06377e-044 3.92364e-044 3.22299e-044 3.08286e-044 2.66247e-044 2.66247e-044 2.24208e-044

두 번째 실행에서 숫자가 0에 매우 가깝다는 점에 유의하십시오.

비정규화된 숫자는 일반적으로 드물기 때문에 대부분의 프로세서는 이를 효율적으로 처리하려고 하지 않습니다.


이것이 비정규화된 숫자와 모든 관련이 있음을 보여주기 위해 코드 시작 부분에 다음을 추가하여 비정규를 0으로 플러시하면:

 _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);

그러면 0 버전은 더 이상 10배 느리지 않고 실제로 더 빨라집니다. (이를 위해서는 SSE가 활성화된 상태에서 코드를 컴파일해야 합니다.)

이것은 이 이상한 정밀도가 거의 0에 가까운 값을 사용하는 대신 0으로 반올림한다는 것을 의미합니다.

타이밍: Core i7 920 @ 3.5GHz:

 // Don't flush denormals to zero. 0.1f: 0.564067 0 : 26.7669 // Flush denormals to zero. 0.1f: 0.587117 0 : 0.341406

결국 이것은 정수인지 부동소수점인지와 아무 관련이 없습니다. 0 또는 0.1f 는 두 루프 외부의 레지스터로 변환/저장됩니다. 따라서 성능에 영향을 미치지 않습니다.


Mysticial

gcc 사용하고 생성된 어셈블리에 diff를 적용하면 다음과 같은 차이만 발생합니다.

 73c68,69 < movss LCPI1_0(%rip), %xmm1 --- > movabsq $0, %rcx > cvtsi2ssq %rcx, %xmm1 81d76 < subss %xmm1, %xmm0

cvtsi2ssq 는 실제로 10배 느립니다.

분명히 float 버전은 메모리에서 로드된 XMM int cvtsi2ssq 명령을 사용하여 int 값 0을 float 로 변환하므로 많은 시간이 걸립니다. -O3 도움이 되지 않습니다. (gcc 버전 4.2.1.)

(사용 double 대신 float 그것이 변화하는 것을 제외하고, 중요하지 않습니다 cvtsi2ssqcvtsi2sdq .)

업데이트

일부 추가 테스트는 반드시 cvtsi2ssq 명령어가 아님을 보여줍니다. 일단 제거되면( int ai=0;float a=ai; 사용 및 0 대신 a 사용) 속도 차이는 그대로 유지됩니다. 따라서 @Mysticial이 옳습니다. 비정규화된 부동 소수점이 차이를 만듭니다. 00.1f 사이의 값을 테스트함으로써 알 수 있습니다. 위 코드의 전환점은 대략 0.00000000000000000000000000000001 에서 루프가 갑자기 10배 더 길어질 때입니다.

업데이트<<1

이 흥미로운 현상에 대한 작은 시각화:

  • 열 1: 모든 반복에 대해 2로 나눈 부동 소수점
  • 열 2: 이 float의 이진 표현
  • 3열: 이 float를 1e7번 합하는 데 걸린 시간

비정규화가 시작되면 지수(마지막 9비트)가 가장 낮은 값으로 변경되는 것을 명확하게 볼 수 있습니다. 이 시점에서 단순 덧셈은 20배 느려집니다.

 0.000000000000000000000000000000000100000004670110: 10111100001101110010000011100000 45 ms 0.000000000000000000000000000000000050000002335055: 10111100001101110010000101100000 43 ms 0.000000000000000000000000000000000025000001167528: 10111100001101110010000001100000 43 ms 0.000000000000000000000000000000000012500000583764: 10111100001101110010000110100000 42 ms 0.000000000000000000000000000000000006250000291882: 10111100001101110010000010100000 48 ms 0.000000000000000000000000000000000003125000145941: 10111100001101110010000100100000 43 ms 0.000000000000000000000000000000000001562500072970: 10111100001101110010000000100000 42 ms 0.000000000000000000000000000000000000781250036485: 10111100001101110010000111000000 42 ms 0.000000000000000000000000000000000000390625018243: 10111100001101110010000011000000 42 ms 0.000000000000000000000000000000000000195312509121: 10111100001101110010000101000000 43 ms 0.000000000000000000000000000000000000097656254561: 10111100001101110010000001000000 42 ms 0.000000000000000000000000000000000000048828127280: 10111100001101110010000110000000 44 ms 0.000000000000000000000000000000000000024414063640: 10111100001101110010000010000000 42 ms 0.000000000000000000000000000000000000012207031820: 10111100001101110010000100000000 42 ms 0.000000000000000000000000000000000000006103515209: 01111000011011100100001000000000 789 ms 0.000000000000000000000000000000000000003051757605: 11110000110111001000010000000000 788 ms 0.000000000000000000000000000000000000001525879503: 00010001101110010000100000000000 788 ms 0.000000000000000000000000000000000000000762939751: 00100011011100100001000000000000 795 ms 0.000000000000000000000000000000000000000381469876: 01000110111001000010000000000000 896 ms 0.000000000000000000000000000000000000000190734938: 10001101110010000100000000000000 813 ms 0.000000000000000000000000000000000000000095366768: 00011011100100001000000000000000 798 ms 0.000000000000000000000000000000000000000047683384: 00110111001000010000000000000000 791 ms 0.000000000000000000000000000000000000000023841692: 01101110010000100000000000000000 802 ms 0.000000000000000000000000000000000000000011920846: 11011100100001000000000000000000 809 ms 0.000000000000000000000000000000000000000005961124: 01111001000010000000000000000000 795 ms 0.000000000000000000000000000000000000000002980562: 11110010000100000000000000000000 835 ms 0.000000000000000000000000000000000000000001490982: 00010100001000000000000000000000 864 ms 0.000000000000000000000000000000000000000000745491: 00101000010000000000000000000000 915 ms 0.000000000000000000000000000000000000000000372745: 01010000100000000000000000000000 918 ms 0.000000000000000000000000000000000000000000186373: 10100001000000000000000000000000 881 ms 0.000000000000000000000000000000000000000000092486: 01000010000000000000000000000000 857 ms 0.000000000000000000000000000000000000000000046243: 10000100000000000000000000000000 861 ms 0.000000000000000000000000000000000000000000022421: 00001000000000000000000000000000 855 ms 0.000000000000000000000000000000000000000000011210: 00010000000000000000000000000000 887 ms 0.000000000000000000000000000000000000000000005605: 00100000000000000000000000000000 799 ms 0.000000000000000000000000000000000000000000002803: 01000000000000000000000000000000 828 ms 0.000000000000000000000000000000000000000000001401: 10000000000000000000000000000000 815 ms 0.000000000000000000000000000000000000000000000000: 00000000000000000000000000000000 42 ms 0.000000000000000000000000000000000000000000000000: 00000000000000000000000000000000 42 ms 0.000000000000000000000000000000000000000000000000: 00000000000000000000000000000000 44 ms

ARM에 대한 동등한 논의는 Stack Overflow 질문 Denormalized floating point in Objective-C?에서 찾을 수 있습니다. .


mvds

비정규화된 부동 소수점 사용 때문입니다. 성능 저하와 성능 저하를 모두 제거하는 방법은 무엇입니까? 비정상 숫자를 죽이는 방법에 대해 인터넷을 샅샅이 뒤졌지만, 아직 이것을 하는 "최선의" 방법은 없는 것 같습니다. 다양한 환경에서 가장 잘 작동할 수 있는 다음 세 가지 방법을 찾았습니다.

  • 일부 GCC 환경에서는 작동하지 않을 수 있습니다.

     // Requires #include <fenv.h> fesetenv(FE_DFL_DISABLE_SSE_DENORMS_ENV);
  • 일부 Visual Studio 환경에서는 작동하지 않을 수 있습니다. 1

     // Requires #include <xmmintrin.h> _mm_setcsr( _mm_getcsr() | (1<<15) | (1<<6) ); // Does both FTZ and DAZ bits. You can also use just hex value 0x8040 to do both. // You might also want to use the underflow mask (1<<11)
  • GCC와 Visual Studio 모두에서 작동하는 것으로 보입니다.

     // Requires #include <xmmintrin.h> // Requires #include <pmmintrin.h> _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON);
  • 인텔 컴파일러에는 최신 인텔 CPU에서 기본적으로 비정규를 비활성화하는 옵션이 있습니다. 자세한 내용은 여기

  • 컴파일러 스위치. -ffast-math , -msse 또는 -mfpmath=sse 는 비정규를 비활성화하고 몇 가지 다른 작업을 더 빠르게 만들지만 불행히도 코드를 손상시킬 수 있는 다른 많은 근사값도 수행합니다. 신중하게 테스트하십시오! Visual Studio 컴파일러의 빠른 수학에 해당하는 것은 /fp:fast 이지만 이것이 비정규도 비활성화하는지 여부를 확인할 수 없었습니다. 1


fig

gcc에서 다음을 사용하여 FTZ 및 DAZ를 활성화할 수 있습니다.

 #include <xmmintrin.h> #define FTZ 1 #define DAZ 1 void enableFtzDaz() { int mxcsr = _mm_getcsr (); if (FTZ) { mxcsr |= (1<<15) | (1<<11); } if (DAZ) { mxcsr |= (1<<6); } _mm_setcsr (mxcsr); }

또한 gcc 스위치를 사용하십시오: -msse -mfpmath=sse

(Carl Hetherington [1]에 해당하는 크레딧)

[1] http://carlh.net/plugins/denormals.php


German Garcia

Dan Neely의 의견 은 답변으로 확장되어야 합니다.

비정규화되거나 속도 저하를 일으키는 것은 0 상수 0.0f 가 아니라 루프를 반복할 때마다 0에 접근하는 값입니다. 0에 점점 더 가까워질수록 표현하기 위해 더 많은 정밀도가 필요하고 비정규화됩니다. 이것은 y[i] 값입니다. x[i]/z[i] i 대해 1.0보다 작기 때문에 0에 접근합니다.)

코드의 느린 버전과 빠른 버전의 결정적인 차이점은 y[i] = y[i] + 0.1f; . 루프가 반복될 때마다 이 줄이 실행되자마자 float의 추가 정밀도가 손실되고 해당 정밀도를 나타내는 데 필요한 비정규화가 더 이상 필요하지 않습니다. y[i] 에 대한 부동 소수점 연산은 비정규화되지 않았기 때문에 빠르게 유지됩니다.

0.1f 를 추가하면 추가 정밀도가 손실되는 이유는 무엇입니까? 부동 소수점 숫자에는 유효 자릿수가 너무 많기 때문입니다. 3개의 유효 자릿수에 대한 충분한 저장 공간이 있다고 가정하고 0.00001 = 1e-50.00001 + 0.1 = 0.1 , 최소한 이 예제 float 형식의 경우 0.10001 최하위 비트를 저장할 공간이 없기 때문입니다.

요컨대, y[i]=y[i]+0.1f; y[i]=y[i]-0.1f; 당신이 그렇게 생각할 수 있습니다.

Mystical도 다음과 같이 말했습니다 : 어셈블리 코드뿐만 아니라 float의 내용도 중요합니다.

편집: 이에 대해 더 자세히 설명하자면, 기계 opcode가 동일하더라도 모든 부동 소수점 연산을 실행하는 데 동일한 시간이 걸리는 것은 아닙니다. 일부 피연산자/입력의 경우 동일한 명령을 실행하는 데 더 많은 시간이 걸립니다. 이것은 특히 비정규 숫자에 해당됩니다.


remcycles

출처 : http:www.stackoverflow.com/questions/9314534/why-does-changing-0-1f-to-0-slow-down-performance-by-10x

반응형