float -> int 형변환시 속도가 빠르다는 이 소스 분석좀..
      글쓴이: maximus / 작성시간: 수, 2004/12/01 - 1:56오후    
  
  아래는 인터넷에서 읽은 글입니다..
<<-----------------------------------------------------------------------
보통 float형을 int형으로 cast할때 (int)로 cast하는데요...
  이렇게 하는 것 보다는 다음과 같이 해주면 훨씬 빠르게 됩니다.
  num이란 float형 변수가 있을때
*(int *)#
  라고 해주면 훨씬 빠른 프로그램을 작성할 수 있습니다.
<<------------------------------------------------------------------------
왜 그러한지 (int) 했을때와 어떻게 틀려지는지 알기 쉽게 설명좀 해주세요.
Forums: 


(int)로 캐스트하면 말 그대로 캐스팅이 이루어지구요,*(int
(int)로 캐스트하면 말 그대로 캐스팅이 이루어지구요,
*(int*)&num은 float형 데이터를 int형으로 간주하고 읽으니까 반드시 올바른 값이 나올 거라고는 간주할 수 없겠는데요. 빠르기는 하겠지만요.
----------------------------
May the F/OSS be with you..
어설픈 지식으로 이런 말도 안되는 얘기를 퍼뜨리고 다니는 사람이 있다니.
어설픈 지식으로 이런 말도 안되는 얘기를 퍼뜨리고 다니는 사람이 있다니... :(
*(int*)&num 이것은 type punning이라고 하는 것인데 float형을 나타내는 비트열을
int형의 비트열로 읽겠다는 것입니다. 당연히 쓰레기값이 나옵니다.
ps. Effective C++에 나오는 우스갯소리가 생각나는군요. "나는 파블로프의 개와 비슷한
실험을 개발자들에게도 행하지 않았는가 하는 의심이 든다. 그렇지 않다면 efficiency라는
말을 들을 때마다 프로그래머들이 침을 흘리는 현상을 어떻게 설명하겠는가?"
참고
http://www-perso.iro.umontreal.ca/~feeley/cours/ift2240/doc/assembly/floatformats.html
이게 맞나봅니다.
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/_core_ieee_floating.2d.point_representation_and_microsoft_languages.asp
답변 주신분들 모두 감사합니다.MSDN 쪽 내용을 읽어 보니 대충 감
답변 주신분들 모두 감사합니다.
MSDN 쪽 내용을 읽어 보니 대충 감이 오네요..
구조적으로 메모리에서 integer 형태를 읽어와서는 값이 같다고 볼수가 없겠네요.. 덕분에 floating 저장 형태를 배울수 있었습니다...
=================================
:: how about a cup of tea ? ::
=================================
[quote="doldori"]ps. Effective C++에 나오
재미있네요~~~ :P
##########################################################
넘어지는건 아직 괜찮다.
하지만 넘어질때마다 무언가를 주워서 일어나자.
Re: float -> int 형변환시 속도가 빠르다는 이 소스 분석좀.
저 인터넷 글은 엉터리지만, 실수를 정수로 빠르게 변환하는 트릭은
있습니다. 게임 분야에 이용되는 트릭인데요... 인터넷 상에서 찾아서
제가 매크로로 정의한 것입니다. 함수 첫머리에
USE_FTOI 한 뒤,
long value = OP_FTOI(fvalue);
합니다. 이 매크로는 반올림을 자동으로 합니다.
#ifdef __BIGENDIAN__ # define FTOI_LONG reserved, l #else # define FTOI_LONG l #endif #define USE_FTOI register union{ double r; struct { _integer4 FTOI_LONG; } l; } __ftoitmp; #define OP_FTOI(val)\ ( ( (__ftoitmp.r=(val)+((((65536.*65536.*16.)+(65536.*.5))*65536.)) ),\ __ftoitmp.l.l-0x80000000L ) )FPU에 정수를 실수레지스터로 로드하는 인스트럭션은 있는데, 그 반대가
없습니다. 그래서 그 작업이 비교적 느린 작업에 해당합니다.
위와 같이 하면 변환 작업만 따졌을 때 꽤 많이 빨라집니다. 물론 전체
성능에 크게 영향을 미치지는 않구요.
Re: float -> int 형변환시 속도가 빠르다는 이 소스 분석좀.
로긴을 잊었군요. 위 글 제가 썼습니다.
Orion Project : http://orionids.org
Re: float -> int 형변환시 속도가 빠르다는 이 소스 분석좀.
음. 아래처럼 테스트 소스 만들어서 돌려봤습니다.(잠시 머리 식힐 겸....... 해서...)
#include <stdio.h> #include <stdlib.h> #ifndef _WIN32 /* for linux/unix */ #include <time.h> #include <sys/time.h> static void printf_localtime() { struct tm * ttt; struct timeval tv; gettimeofday(&tv, NULL); ttt = localtime(&tv); printf("%.4d-%.2d-%.2d %.2d:%.2d:%.2d.%.3d", ttt->tm_year, ttt->tm_mon, ttt->tm_mday, ttt->tm_hour, ttt->tm_min, ttt->tm_sec, tv.tv_usec/1000); } static unsigned int get_clock() { struct timeval tv; unsigned int t; gettimeofday(&tv, NULL); t = ((tv.tv_sec % 1000000) * 1000) + (tv.tv_usec / 1000); return t; } #else /* for windows */ #define get_clock clock #endif #ifdef __BIGENDIAN__ # define FTOI_LONG reserved, l #else # define FTOI_LONG l #endif #define USE_FTOI union{ double r; struct { long FTOI_LONG; } l; } __ftoitmp; #define OP_FTOI(val) \ ( ( (__ftoitmp.r=(val)+((((65536.*65536.*16.)+(65536.*.5))*65536.)) ), \ __ftoitmp.l.l-0x80000000L ) ) #define USE_FTOI2 union{ double r; struct { long FTOI_LONG; } l; } __ftoitmp; #define LOOPCNT1 10 #define LOOPCNT2 10000000 void test1() { double dval = 123456789.12345678; long lval = 0; long total = 0; long start = 0, finish = 0; long avg = 0; long i=0, j=0; start = get_clock(); for(j=0; j<LOOPCNT1; j++) { for(i=0; i<LOOPCNT2; i++) { lval = dval; } } finish = get_clock(); total = finish - start; avg = total / LOOPCNT1 / LOOPCNT2; printf("1.... value %ld total %ld ms, avg %ld ms\n", lval, total, avg); } void test2() { USE_FTOI double dval = 123456789.12345678; long lval = 0; long total = 0; long start = 0, finish = 0; long avg = 0; long i=0, j=0; start = get_clock(); for(j=0; j<LOOPCNT1; j++) { for(i=0; i<LOOPCNT2; i++) { lval = OP_FTOI(dval); } } finish = get_clock(); total = finish - start; avg = total / LOOPCNT1 / LOOPCNT2; printf("2.... value %ld total %ld ms, avg %ld ms\n", lval, total, avg); } void test3() { register USE_FTOI double dval = 123456789.12345678; long lval = 0; long total = 0; long start = 0, finish = 0; long avg = 0; long i=0, j=0; start = get_clock(); for(j=0; j<LOOPCNT1; j++) { for(i=0; i<LOOPCNT2; i++) { lval = OP_FTOI(dval); } } finish = get_clock(); total = finish - start; avg = total / LOOPCNT1 / LOOPCNT2; printf("3.... value %ld total %ld ms, avg %ld ms\n", lval, total, avg); } int main(int argc, char *argv[]) { test1(); test2(); test3(); return 0; }1번은 그냥 컴파일러가 알아서 하게 했고,
2번은 올려주신 소스를 조금 고쳐서 register 키워드를 제거했습니다.
3번은 register 키워드를 다시 넣은 것이구요...
컴파일러는 gcc 3.2.3, MS Windows / MinGW / P4 2.4 GHz 입니다.
컴파일러 최적화 옵션에 따라 아래와 같은 결과가 나왔습니다.
-O0
1.... value 123456789 total 1250 ms, avg 0 ms
2.... value 123456789 total 578 ms, avg 0 ms
3.... value 123456789 total 812 ms, avg 0 ms
-O1
1.... value 123456789 total 78 ms, avg 0 ms
2.... value 123456789 total 172 ms, avg 0 ms
3.... value 123456789 total 187 ms, avg 0 ms
-O2
1.... value 123456789 total 78 ms, avg 0 ms
2.... value 123456789 total 78 ms, avg 0 ms
3.... value 123456789 total 62 ms, avg 0 ms
-O3
1.... value 123456789 total 78 ms, avg 0 ms
2.... value 123456789 total 62 ms, avg 0 ms
3.... value 123456789 total 62 ms, avg 0 ms
O2 와 O3는 거의 비슷하게 나오네요. 여러 번 실행해보면 조금씩 값이 다르게 나오는데, O2 와 O3에서는 2와 3의 결과가 바뀌는 수도 종종 있었습니다.
다른 환경에서는 어떻게 나올런지 궁금하네요.
빠른 PC나 서버 말고 좀 느린 환경에서...
Re: float -> int 형변환시 속도가 빠르다는 이 소스 분석좀.
최근 최적화 컴파일러에서 register 키워드는 별 의미가 없습니다.
대부분 알아서 잘 하니까요.
단, 지금 하신 테스트는 값을 대입하는 부분이 로컬 함수 내에서 이루어지기
때문에 속도 최적화를 한 경우, 컴파일러가 값을 한번만 변환해도 되거나,
아예 변환이 필요 없다고 판단했을 수 있습니다. 어셈블리 코드를 살펴보실
필요가 있을 것 같구요.
좀 더 정확한 테스트를 위해 변환이 항상 일어나도록 변환 부분을 함수로
빼서, 변환 결과를 포인터에 담아주거나 리턴하는 식의 코드를
반복 루프 상에서 돌려보는 게 필요할 것 같네요.
물론 고사양의 컴퓨터라 차이가 나지 않은 것일 수도 있구요.
제가 과거 486 PC 에서 테스트 해봤을 때는 정확히 기억은 안나는데,
적어도 30% 내외로 더 빨랐습니다.
Orion Project : http://orionids.org
체스맨님께서 말씀하신대로 좀 고쳐서 다시 해 봤습니다.음... 상당히
체스맨님께서 말씀하신대로 좀 고쳐서 다시 해 봤습니다.
음... 상당히 다른 결과가 나오는군요.
-O0
1.... value 123456789 total 1750 ms, avg 0 ms
2.... value 123456789 total 921 ms, avg 0 ms
3.... value 123456789 total 1579 ms, avg 0 ms
-O1
1.... value 123456789 total 1671 ms, avg 0 ms
2.... value 123456789 total 829 ms, avg 0 ms
3.... value 123456789 total 828 ms, avg 0 ms
-O2
1.... value 123456789 total 1656 ms, avg 0 ms
2.... value 123456789 total 1047 ms, avg 0 ms
3.... value 123456789 total 1031 ms, avg 0 ms
-O3
1.... value 123456789 total 62 ms, avg 0 ms
2.... value 123456789 total 203 ms, avg 0 ms
3.... value 123456789 total 188 ms, avg 0 ms
O3 무섭군요. :shock:
O2 까지는 알려주신 방법이 더 빠르게 나오고, O3에서 역전되었습니다.
레지스터 키워드를 안 쓰는게 O0에서는 확실히 더 빠르지만, O1부터는 쓰는 게 좀 더 빠르게 측정되었습니다. O1부터의 시간차는 뭐 오차범위 정도가 아닐까 싶은데요......
어셈 코드는 귀차니즘때문에 ;;
(퇴근할 시간이 지난지라...)
아 참, 그리고 올려주신 방법은 유용하게 쓸 곳이 종종 있을 듯 합니다.
감사드리구요. (처음 답글 달 때 깜빡하고 지금에서야 ...)
사용된 소스는 아래와 같습니다.
#include <stdio.h> #include <stdlib.h> #ifndef _WIN32 /* for linux/unix */ #include <time.h> #include <sys/time.h> static void printf_localtime() { struct tm * ttt; struct timeval tv; gettimeofday(&tv, NULL); ttt = localtime(&tv); printf("%.4d-%.2d-%.2d %.2d:%.2d:%.2d.%.3d", ttt->tm_year, ttt->tm_mon, ttt->tm_mday, ttt->tm_hour, ttt->tm_min, ttt->tm_sec, tv.tv_usec/1000); } static unsigned int get_clock() { struct timeval tv; unsigned int t; gettimeofday(&tv, NULL); t = ((tv.tv_sec % 1000000) * 1000) + (tv.tv_usec / 1000); return t; } #else /* for windows */ #define get_clock clock #endif #ifdef __BIGENDIAN__ # define FTOI_LONG reserved, l #else # define FTOI_LONG l #endif #define USE_FTOI union{ double r; struct { long FTOI_LONG; } l; } __ftoitmp; #define OP_FTOI(val) \ ( ( (__ftoitmp.r=(val)+((((65536.*65536.*16.)+(65536.*.5))*65536.)) ), \ __ftoitmp.l.l-0x80000000L ) ) #define USE_FTOI2 union{ double r; struct { long FTOI_LONG; } l; } __ftoitmp; #define LOOPCNT1 10 #define LOOPCNT2 10000000 void conv1(double *dv, long *lv) { *lv = *dv; } void test1() { double dval = 123456789.12345678; long lval = 0; long total = 0; long start = 0, finish = 0; long avg = 0; long i=0, j=0; start = get_clock(); for(j=0; j<LOOPCNT1; j++) { for(i=0; i<LOOPCNT2; i++) { conv1(&dval, &lval); } } finish = get_clock(); total = finish - start; avg = total / LOOPCNT1 / LOOPCNT2; printf("1.... value %ld total %ld ms, avg %ld ms\n", lval, total, avg); } void conv2(double *dv, long *lv) { USE_FTOI *lv = OP_FTOI(*dv); } void test2() { double dval = 123456789.12345678; long lval = 0; long total = 0; long start = 0, finish = 0; long avg = 0; long i=0, j=0; start = get_clock(); for(j=0; j<LOOPCNT1; j++) { for(i=0; i<LOOPCNT2; i++) { conv2(&dval, &lval); } } finish = get_clock(); total = finish - start; avg = total / LOOPCNT1 / LOOPCNT2; printf("2.... value %ld total %ld ms, avg %ld ms\n", lval, total, avg); } void conv3(double *dv, long *lv) { register USE_FTOI *lv = OP_FTOI(*dv); } void test3() { double dval = 123456789.12345678; long lval = 0; long total = 0; long start = 0, finish = 0; long avg = 0; long i=0, j=0; start = get_clock(); for(j=0; j<LOOPCNT1; j++) { for(i=0; i<LOOPCNT2; i++) { conv3(&dval, &lval); } } finish = get_clock(); total = finish - start; avg = total / LOOPCNT1 / LOOPCNT2; printf("3.... value %ld total %ld ms, avg %ld ms\n", lval, total, avg); } int main(int argc, char *argv[]) { test1(); test2(); test3(); return 0; }O3 결과는 의미가 없습니다.O3 는 함수 호출을 인라인화 시키는 최
O3 결과는 의미가 없습니다.
O3 는 함수 호출을 인라인화 시키는 최적화까지 합니다.
결국 빈루프를 도는 정도 시간만 측정됐을 겁니다.
그래서, 처음 하신 테스트와 결과가 비슷하죠.
게다가 OP_FTOI 가 반올림을 하기 때문에 정수 캐스트 전 반올림까지 고려하면,
양수 실수면 0.5를 음수 실수면 -0.5를 더하는 코드까지 추가되어야 합니다.
Orion Project : http://orionids.org
[code:1]#ifdef __BIGENDIAN__ # def
#ifdef __BIGENDIAN__ # define FTOI_LONG reserved, l #else # define FTOI_LONG l #endif #define USE_FTOI register union{ double r; struct { _integer4 FTOI_LONG; } l; } __ftoitmp; #define OP_FTOI(val)\ ( ( (__ftoitmp.r=(val)+((((65536.*65536.*16.)+(65536.*.5))*65536.)) ),\ __ftoitmp.l.l-0x80000000L ) )제가 무식해서 그런데 이코드들이 어떤 일을 해서 캐스팅이 이루어 지는지 설명좀 해주시면 많은 도움이 될 것 같습니다. 코드의 제약사항이 있으면 그것두 좀.. :oops:
제약 사항은 없습니다. 반올림이 자동으로 되는 것이 제약일 수는 있죠.
제약 사항은 없습니다. 반올림이 자동으로 되는 것이 제약일 수는 있죠.
매직값을 더한 뒤 하위 4바이트(IEEE 실수 형식상 mantissa 하위 32비트)
에서 0x80000000 을 빼는 것인데, 왜 이렇게 되는가는 IEEE 실수 형식상
그렇게 된다고 말씀드려야 할 것 같습니다.
저 값을 발견한 사람이 대단한 분이죠.
Orion Project : http://orionids.org
약간의 속도 향상과 잠재된 버그란 면에서...
IEEE 754표준인데...
암튼 최적화란 컴파일러의 목이 아닐런지...
^^
Hello World.
double -> long 만 되는게 맞는거죠? float ->
double -> long 만 되는게 맞는거죠? float -> int 안되는거죠?ㅜㅜ
당연히 float 도 됩니다. :)
당연히 float 도 됩니다. :)
Orion Project : http://orionids.org
생각해보니 제약 사항이 하나 더 있군요.IEEE 754 표준 부동
생각해보니 제약 사항이 하나 더 있군요.
IEEE 754 표준 부동 소수점 표현 형식을 사용하는 경우에만 동작합니다.
많은 S/W, H/W 들이 이 표준을 채택하기 때문에, 이것도 큰 제약은
못됩니다.
Orion Project : http://orionids.org
Re: float -> int 형변환시 속도가 빠르다는 이 소스 분석좀.
fistp 명령이면 되지 않나요?
굳이 저렇게까지...
Re: float -> int 형변환시 속도가 빠르다는 이 소스 분석좀.
제가 쓴 다음 부분을 정정합니다. windpipe 님께서 말씀해주신
fistp 명령이 있군요. 과거에 FPU 프로그래밍할 때 제가 모르고 지나쳤나봅니다.
그런데 windpipe 님 말씀처럼 fistp 를 써도 위의 변환 트릭을 두가지
측면에서 능가하지 못합니다.
첫째는 속도 측면인데요. 실재로 gcc 가 실수--> 정수 캐스팅에 그 명령을
쓰고 있습니다. -O2 옵션을 주고 어셈블리 명령을 생성해보시면 됩니다.
위에 제시된 espereto님의 테스트나 과거 제가 해봤던 테스트의 결과에서
OP_FTOI 가 C 의 캐스트 연산보다 더 빠르다는 것을 보여줍니다.
OP_FTOI 매크로를 살펴보시면, 크게 실수 덧셈과 정수 뺄셈이 쓰이는데
이것은 FPU 나 CPU 의 아주 빠른 인스트럭션들에 해당하기 때문입니다.
* 과거에 제가 사용하던 컴파일러는 최적화 옵션을 주면 캐스팅하는 부분에서
함수를 호출했었습니다. (현재 vc++ 6.0 도 이렇군요. ) 그래서 실수
정수 변환 인스트럭션이 없다고 생각했었습니다. 게다가 그 당시 제가 그것을
찾지 못했구요...
둘째는 호환성 측면입니다. fistp 는 인텔계열 FPU 에서, 어셈블리어를
사용해야만 이용할 수 있습니다. 하지만, OP_FTOI 는 IEEE 754 형식을
채택한 어떠한 플렛폼에서도 사용할 수 있습니다.
Orion Project : http://orionids.org
Re: float -> int 형변환시 속도가 빠르다는 이 소스 분석좀.
그 모든 플랫폼에서 casting보다 더 빠르다는 보장 역시 없지 않습니까? :(
인텔 호환 플랫폼으로 제한한다 하더라도 FPU가 없는 original 386에서부터 AMD64까지 수많은 x86 호환 CPU에서 어느게 더 빠르다는 말도 쉽게 하기 힘들고요.
x86계열에서는.
x86계열에서의 속도를 보자면,
fprep dd 59c00000h fadd [fprep] fsub [fprep]와 같은 코드를 쓰는 것보다 빠르진 않을 듯합니다.
위 코드는 단 2클럭이면 해결되니까요.
Re: float -> int 형변환시 속도가 빠르다는 이 소스 분석좀.
대부분 더 빠른 것이 보장됩니다. 인스트럭션들을 분석해보시면 압니다.
혹여 앞으로 fistp 인스트럭션이 개선된다해도 OP_FTOI 와 유사한 정도에
해당할 것입니다. 특히 OP_FTOI 는 저사양 PC, PDA 등에서 좋은
성능을 보일 겁니다.
Re: float -> int 형변환시 속도가 빠르다는 이 소스 분석좀.
로그인 안해도 글 올라가는 것 너무 불편하네요.
윗 글도 제가 올렸습니다.
아...
그리고 저 연산을 매크로로 정의했던 이유는 사용하기 쉽게하기
위함도 있지만, 정수 캐스팅이 더 빠른 시스템에서는 그것으로
대체하기 쉽게하기 위한 것도 있었습니다.
말씀 드렸듯이, OP_FTOI 는 반올림이 된 결과를 내기 때문에
반올림 처리까지 고려하자면, 캐스팅은 벌써 실수 덧셈 한번이
요구됩니다. 이것이 OP_FTOI 연산에서 한번 행하는 실수 덧셈과
대응한다고 보면, 결국 정수 뺄셈 속도만큼 실수->정수 변환
인스트럭션이 빨라져야 한다는 것을 의미합니다.
그래서 대부분 OP_FTOI 가 더 빠를 것으로 예상할 수 있습니다.
Orion Project : http://orionids.org
저거 사용할
저거 사용할때 주의점은
float 에서는 값이 2^23 보다 크면 변환이 제대로 안됩니다.
double에서는 2^52보다 크면 제대로 변환이 안됩니다.,
정말 대단.. ㅡㅜ
제가 필요로 하던 정보 였습니다.
감사 합니다.
댓글 달기