최신 하드웨어에서 부동 소수점 대 정수 계산


100

저는 C ++에서 성능에 중요한 작업을 수행하고 있으며 현재 "더 빠르기"때문에 본질적으로 부동 소수점 문제에 정수 계산을 사용하고 있습니다. 이로 인해 성가신 문제가 많이 발생하고 성가신 코드가 많이 추가됩니다.

이제 저는 부동 소수점 계산이 약 386 일 동안 얼마나 느 렸는지 읽은 기억이납니다. 여기서 (IIRC) 선택적 공동 프로세서가 있다고 믿습니다. 하지만 요즘에는 기하 급수적으로 더 복잡하고 강력한 CPU를 사용하여 부동 소수점 또는 정수 계산을 수행하는 경우 "속도"에 차이가 없습니다. 특히 실제 계산 시간이 파이프 라인 지연을 일으키거나 주 메모리에서 무언가를 가져 오는 것과 비교하여 매우 작기 때문에?

정답은 대상 하드웨어에서 벤치마킹하는 것입니다.이를 테스트하는 좋은 방법은 무엇입니까? 나는 두 개의 작은 C ++ 프로그램을 작성하고 그들의 실행 시간을 Linux의 "시간"과 비교했지만 실제 실행 시간은 너무 가변적입니다 (가상 서버에서 실행하는 데 도움이되지 않습니다). 하루 종일 수백 개의 벤치 마크를 실행하고 그래프를 작성하는 데 소요되는 시간이 부족합니다. 상대 속도를 합리적으로 테스트하기 위해 할 수있는 일이 있습니까? 어떤 아이디어 나 생각? 내가 완전히 틀렸습니까?

내가 다음과 같이 사용한 프로그램은 어떤 식 으로든 동일하지 않습니다.

#include <iostream>
#include <cmath>
#include <cstdlib>
#include <time.h>

int main( int argc, char** argv )
{
    int accum = 0;

    srand( time( NULL ) );

    for( unsigned int i = 0; i < 100000000; ++i )
    {
        accum += rand( ) % 365;
    }
    std::cout << accum << std::endl;

    return 0;
}

프로그램 2 :

#include <iostream>
#include <cmath>
#include <cstdlib>
#include <time.h>

int main( int argc, char** argv )
{

    float accum = 0;
    srand( time( NULL ) );

    for( unsigned int i = 0; i < 100000000; ++i )
    {
        accum += (float)( rand( ) % 365 );
    }
    std::cout << accum << std::endl;

    return 0;
}

미리 감사드립니다!

편집 : 내가 관심있는 플랫폼은 데스크톱 Linux 및 Windows 시스템에서 실행되는 일반 x86 또는 x86-64입니다.

편집 2 (아래 주석에서 붙여 넣기) : 현재 광범위한 코드 기반이 있습니다. 정말로 나는 우리가 "정수 계산이 더 빠르기 때문에 float를 사용해서는 안된다"는 일반화에 반대했습니다. 그리고이 일반화 된 가정을 반증 할 방법을 찾고 있습니다. 모든 작업을 수행하고 나중에 프로파일 링하지 않으면 정확한 결과를 예측할 수 없다는 것을 알고 있습니다.

어쨌든, 모든 훌륭한 답변과 도움에 감사드립니다. 다른 것을 추가해도됩니다. :).


8
지금 시험으로 가지고있는 것은 사소한 것입니다. 어셈블리에도 거의 차이가 없을 것입니다 ( 예 : addl로 대체 됨 fadd). 실제로 좋은 측정을 얻는 유일한 방법은 실제 프로그램의 핵심 부분을 얻고 다른 버전을 프로파일 링하는 것입니다. 안타깝게도 많은 노력을 기울이지 않고도 꽤 어려울 수 있습니다. 아마도 우리에게 타겟 하드웨어와 컴파일러를 알려 주면 사람들이 최소한 기존 경험 등을 제공하는 데 도움이 될 것입니다. 정수 사용에 관해서 fixed_point는 그러한 작업을 엄청나게 쉽게 할 수있는 일종의 템플릿 클래스를 만들 수있을 것 같습니다.
GManNickG

1
전용 부동 소수점 하드웨어가없는 아키텍처가 여전히 많이 있습니다. 관심있는 시스템을 설명하는 일부 태그는 더 나은 답을 얻는 데 도움이됩니다.
Carl Norum

3
HTC Hero (android)의 하드웨어에는 FPU가 없지만 Google NexusOne (android)의 하드웨어에는 FPU가 있다고 생각합니다. 당신의 목표는 무엇입니까? 데스크탑 / 서버 PC? 넷북 (arm + linux 가능)? 전화?
SteelBytes

5
x86에서 빠른 FP를 원하면 최적화 및 SSE 코드 생성을 사용하여 컴파일하십시오. SSE (어떤 버전이든)는 단일 사이클에서 적어도 부동 더하기, 빼기 및 곱하기를 수행 할 수 있습니다. Divide, mod 및 더 높은 기능은 항상 느립니다. 또한 float속도가 향상되지만 일반적으로 double그렇지 않습니다.
Mike D.

1
고정 소수점 정수는 결과가 오버플로되지 않도록 여러 정수 연산을 사용하여 FP를 근사합니다. 이는 최신 데스크톱 CPU에서 볼 수있는 뛰어난 성능의 FPU를 사용하는 것보다 거의 항상 느립니다. 예를 들어 고정 소수점 mp3 디코더 인 MAD는 libmpg123보다 느리며 고정 소수점 디코더의 품질이 좋지만 libmpg123은 여전히 ​​반올림 오류가 적습니다. wezm.net/technical/2008/04/mp3-decoder-libraries-PPC G5의 벤치 마크 비교.
Peter Cordes

답변:


35

아아, 나는 당신에게 "그것에 달려있다"대답 만 줄 수 있습니다 ...

내 경험으로 볼 때 성능에 대한 많은 변수가 있습니다. 특히 정수와 부동 소수점 수학 사이에 있습니다. 프로세서마다 "파이프 라인"길이가 다르기 때문에 프로세서마다 크게 다릅니다 (x86과 같은 동일한 제품군 내에서도). 또한 일부 작업은 일반적으로 매우 간단하고 (예 : 추가) 프로세서를 통해 가속화 된 경로를 가지며 다른 작업 (예 : 분할)은 훨씬 더 오래 걸립니다.

다른 큰 변수는 데이터가있는 곳입니다. 추가 할 값이 몇 개만있는 경우 모든 데이터가 캐시에 상주하여 CPU로 빠르게 전송 될 수 있습니다. 캐시에 이미 데이터가있는 매우 느린 부동 소수점 연산은 정수를 시스템 메모리에서 복사해야하는 정수 연산보다 몇 배 더 빠릅니다.

성능이 중요한 응용 프로그램을 작업하고 있기 때문에이 질문을한다고 가정합니다. x86 아키텍처 용으로 개발 중이고 추가 성능이 필요한 경우 SSE 확장 사용을 고려할 수 있습니다. 이것은 단 정밀도 부동 소수점 연산 속도를 크게 높일 수 있습니다. 동일한 연산이 여러 데이터에 대해 한 번에 수행 될 수 있고 SSE 연산을위한 별도의 * 레지스터 뱅크가 있기 때문입니다. (두 번째 예제에서 "double"대신 "float"를 사용하여 단 정밀도 수학을 사용하고 있다고 생각합니다.)

* 참고 : 이전 MMX 명령어를 사용하면 실제로 프로그램 속도가 느려집니다. 이전 명령어는 실제로 FPU와 동일한 레지스터를 사용하여 FPU와 MMX를 동시에 사용할 수 없기 때문입니다.


8
그리고 일부 프로세서에서는 FP 수학이 정수 수학보다 빠를 수 있습니다. Alpha 프로세서에는 FP 나누기 명령이 있지만 정수 1이 아니므로 소프트웨어에서 정수 나누기를 수행해야했습니다.
Gabe

SSEx는 배정 밀도 산술 속도도 높일 수 있습니까? 죄송합니다. SSE에 너무 익숙하지 않습니다
Johannes Schaub-litb

1
@ JohannesSchaub-litb : SSE2 (x86-64의 기준선)에는 압축 double정밀도 FP가 있습니다. double레지스터 당 64 비트가 2 개뿐이므로 float벡터화가 잘되는 코드 보다 잠재적 인 속도 향상이 적습니다 . x86-64에서 스칼라 floatdoubleXMM 레지스터 사용, 레거시 x87은 long double. (그래서 @ Dan : 아니요, x86-64의 일반 FPU가 SSE 단위이기 때문에 MMX 레지스터는 일반 FPU 레지스터와 충돌하지 않습니다. 정수 SIMD를 수행 할 수 있으면 xmm0..158 대신 16 바이트를 원하기 때문에 MMX는 의미가 없습니다. -byte mm0..7이며 최신 CPU는 SSE 처리량보다 MMX가 더 나쁩니다.)
Peter Cordes

1
그러나 MMX 및 SSE * / AVX2 정수 명령어는 동일한 실행 단위에 대해 경쟁하므로 두 가지를 동시에 사용하는 것은 거의 유용하지 않습니다. 더 많은 작업을 수행하려면 더 넓은 XMM / YMM 버전을 사용하십시오. SIMD 정수와 FP를 동시에 사용하면 동일한 레지스터를 놓고 경쟁하지만 x86-64에는 16 개가 있습니다. 그러나 총 처리량 제한은 정수 및 FP 실행 단위를 병렬로 사용하여 두 배의 작업을 수행 할 수 없음을 의미합니다.
Peter Cordes

49

예를 들어 (숫자가 적을수록 빠름),

64 비트 Intel Xeon X5550 @ 2.67GHz, gcc 4.1.2 -O3

short add/sub: 1.005460 [0]
short mul/div: 3.926543 [0]
long add/sub: 0.000000 [0]
long mul/div: 7.378581 [0]
long long add/sub: 0.000000 [0]
long long mul/div: 7.378593 [0]
float add/sub: 0.993583 [0]
float mul/div: 1.821565 [0]
double add/sub: 0.993884 [0]
double mul/div: 1.988664 [0]

32 비트 듀얼 코어 AMD Opteron (tm) 프로세서 265 @ 1.81GHz, gcc 3.4.6 -O3

short add/sub: 0.553863 [0]
short mul/div: 12.509163 [0]
long add/sub: 0.556912 [0]
long mul/div: 12.748019 [0]
long long add/sub: 5.298999 [0]
long long mul/div: 20.461186 [0]
float add/sub: 2.688253 [0]
float mul/div: 4.683886 [0]
double add/sub: 2.700834 [0]
double mul/div: 4.646755 [0]

으로 댄 지적 (어떤이 파이프 라인의 설계 자체에 오해의 소지가 될 수있다) 심지어 한 번 정상화 클럭 주파수, 결과는 격렬하게 CPU 아키텍처에 따라 달라집니다 (개인 ALU / FPU의 성능 , 뿐만 아니라 실제 의 ALU / FPU를 수 사용할 수 당 병렬로 실행할 수있는 독립적 인 작업 수에 영향을주는 슈퍼 스칼라 설계의 핵심입니다 .

가난한 사람의 FPU / ALU 운영 벤치 마크 :

#include <stdio.h>
#ifdef _WIN32
#include <sys/timeb.h>
#else
#include <sys/time.h>
#endif
#include <time.h>
#include <cstdlib>

double
mygettime(void) {
# ifdef _WIN32
  struct _timeb tb;
  _ftime(&tb);
  return (double)tb.time + (0.001 * (double)tb.millitm);
# else
  struct timeval tv;
  if(gettimeofday(&tv, 0) < 0) {
    perror("oops");
  }
  return (double)tv.tv_sec + (0.000001 * (double)tv.tv_usec);
# endif
}

template< typename Type >
void my_test(const char* name) {
  Type v  = 0;
  // Do not use constants or repeating values
  //  to avoid loop unroll optimizations.
  // All values >0 to avoid division by 0
  // Perform ten ops/iteration to reduce
  //  impact of ++i below on measurements
  Type v0 = (Type)(rand() % 256)/16 + 1;
  Type v1 = (Type)(rand() % 256)/16 + 1;
  Type v2 = (Type)(rand() % 256)/16 + 1;
  Type v3 = (Type)(rand() % 256)/16 + 1;
  Type v4 = (Type)(rand() % 256)/16 + 1;
  Type v5 = (Type)(rand() % 256)/16 + 1;
  Type v6 = (Type)(rand() % 256)/16 + 1;
  Type v7 = (Type)(rand() % 256)/16 + 1;
  Type v8 = (Type)(rand() % 256)/16 + 1;
  Type v9 = (Type)(rand() % 256)/16 + 1;

  double t1 = mygettime();
  for (size_t i = 0; i < 100000000; ++i) {
    v += v0;
    v -= v1;
    v += v2;
    v -= v3;
    v += v4;
    v -= v5;
    v += v6;
    v -= v7;
    v += v8;
    v -= v9;
  }
  // Pretend we make use of v so compiler doesn't optimize out
  //  the loop completely
  printf("%s add/sub: %f [%d]\n", name, mygettime() - t1, (int)v&1);
  t1 = mygettime();
  for (size_t i = 0; i < 100000000; ++i) {
    v /= v0;
    v *= v1;
    v /= v2;
    v *= v3;
    v /= v4;
    v *= v5;
    v /= v6;
    v *= v7;
    v /= v8;
    v *= v9;
  }
  // Pretend we make use of v so compiler doesn't optimize out
  //  the loop completely
  printf("%s mul/div: %f [%d]\n", name, mygettime() - t1, (int)v&1);
}

int main() {
  my_test< short >("short");
  my_test< long >("long");
  my_test< long long >("long long");
  my_test< float >("float");
  my_test< double >("double");

  return 0;
}

8
왜 mult와 div를 섞었습니까? mult가 div보다 훨씬 빠르다면 흥미롭지 않습니까?
Kyss Tao

13
곱셈은 ​​정수 및 부동 소수점의 경우 나누기보다 훨씬 빠릅니다. 분할 성과는 숫자의 크기에 따라 달라집니다. 나는 보통 나누기가 ~ 15 배 느리다고 가정합니다.
Sogartar

4
pastebin.com/Kx8WGUfg 벤치 마크를 가져와 각 작업을 자체 루프로 분리하고 volatile확인하기 위해 추가 했습니다. Win64를에서 FPU는 사용되지 않고 그것을 사용하여 컴파일 있도록 MSVC, 그것에 대한 코드를 생성하지 않습니다 mulssdivss빠른 Win32에서의 FPU에 비해 25 배를 거기 XMM 지침. 테스트 시스템은 코어 I5 M 520 @ 2.40GHz입니다
제임스 던

4
@JamesDunne은 fp ops v가 매우 빠르게 0 또는 +/- inf에 매우 빠르게 도달하므로 특정 fpu 구현에 의해 (이론적으로) 특수 사례로 처리되거나 처리되지 않을 수 있습니다.
vladr

3
모든 작업이 동일한 누산기 ( v)로 수행되기 때문에이 "벤치 마크"에는 비 순차적 실행에 대한 데이터 병렬성이 없습니다 . 최근 Intel 설계에서 분할은 전혀 파이프 라인되지 않습니다 ( divss/ divps는 10-14 사이클 대기 시간과 동일한 상호 처리량을 가짐). mulss그러나 5주기 대기 시간이지만주기마다 하나씩 발행 할 수 있습니다. (또는 Haswell에서 사이클 당 2 개, 포트 0과 포트 1 모두 FMA에 대한 배율이 있기 때문입니다).
Peter Cordes

23

고정 소수점 수학과 부동 소수점 수학간에 실제 속도에는 상당한 차이가있을 수 있지만 ALU 대 FPU의 이론적 최상의 처리량은 완전히 관련이 없습니다. 대신 계산 (예 : 루프 제어용)에 사용되지 않는 아키텍처의 정수 및 부동 소수점 레지스터 (레지스터 이름이 아닌 실제 레지스터)의 수, 캐시 라인에 맞는 각 유형의 요소 수 , 정수 대 부동 소수점 수학에 대한 서로 다른 의미를 고려하여 최적화가 가능합니다. 이러한 효과가 지배적입니다. 알고리즘의 데이터 종속성은 여기서 중요한 역할을하므로 일반적인 비교로 문제의 성능 차이를 예측할 수 없습니다.

예를 들어 정수 더하기는 교환 적이므로 컴파일러가 벤치 마크에 사용한 것과 같은 루프를 발견하면 (임의 데이터가 결과를 가리지 않도록 미리 준비되었다고 가정) 루프를 풀고 다음을 사용하여 부분 합계를 계산할 수 있습니다. 종속성이없는 경우 루프가 종료 될 때 추가하십시오. 그러나 부동 소수점을 사용하면 컴파일러가 요청한 순서와 동일한 순서로 작업을 수행해야합니다 (시퀀스 포인트가 있으므로 컴파일러는 순서 변경을 허용하지 않는 동일한 결과를 보장해야 함). 따라서 각 추가 항목에 대한 강한 종속성이 있습니다. 이전 결과의 결과.

한 번에 더 많은 정수 피연산자를 캐시에 넣을 수도 있습니다. 따라서 고정 소수점 버전은 FPU가 이론적으로 더 높은 처리량을 가진 컴퓨터에서도 부동 소수점 버전보다 성능이 훨씬 뛰어납니다.


4
언 롤링 된 상수 정수 연산으로 인해 순진한 벤치 마크가 0- 시간 루프를 생성 할 수있는 방법을 지적하는 +1. 또한 컴파일러는 결과가 실제로 사용되지 않으면 루프 (정수 또는 FP)를 완전히 버릴 수 있습니다.
vladr

결론은 루핑 변수를 인수로 갖는 함수를 호출해야한다는 것입니다. 나는 컴파일러가 함수가 아무것도하지 않고 호출을 무시할 수 있음을 알 수 없다고 생각하기 때문에. 호출 오버 헤드가 있기 때문에 시간 == (float 시간-정수 시간) 차이 만 중요합니다.
GameAlchemist 2011

@GameAlchemist : 많은 컴파일러가 인라인의 부작용으로 빈 함수에 대한 호출을 제거합니다. 이를 방지하기 위해 노력해야합니다.
Ben Voigt

OP는 FP가 더 자연스러운 경우에 정수를 사용하는 것에 대해 이야기하는 것처럼 들리므로 FP 코드와 동일한 결과를 얻으려면 더 많은 정수 코드가 필요합니다. 이 경우 FP를 사용하십시오. 예를 들어, FPU가있는 하드웨어 (예 : 데스크탑 CPU)에서 고정 소수점 정수 MP3 디코더는 부동 소수점 디코더보다 느립니다 (그리고 약간 더 많은 반올림 오류). 코덱의 고정 소수점 구현은 주로 FP 하드웨어가없는 제거 된 ARM CPU에서 실행되고 느린 에뮬레이트 된 FP에서만 실행됩니다.
Peter Cordes

- 64에 AVX-512 스칼라 부동 소수점 수학 16 GP 레지스터하지만 32 개 ZMM 레지스터가되도록 함께 : 제 포인트 일례 있다 빠를
phuclv

18

덧셈은보다 훨씬 빠르기 rand때문에 프로그램은 (특히) 쓸모가 없습니다.

성능 핫스팟을 식별하고 프로그램을 점진적으로 수정해야합니다. 먼저 해결해야 할 개발 환경에 문제가있는 것 같습니다. 작은 문제 세트로 인해 PC에서 프로그램을 실행할 수 없습니까?

일반적으로 정수 산술로 FP 작업을 시도하는 것은 느린 방법입니다.


예, 부동 소수점 버전에서 rand 정수에서 부동 소수점으로의 변환도 마찬가지입니다. 이것을 테스트하는 더 좋은 방법에 대한 아이디어가 있습니까?
maxpenguin

1
속도를 프로파일 링하려는 경우 POSIX timespec_t또는 이와 유사한 것을 살펴보십시오 . 루프의 시작과 끝에서 시간을 기록하고 차이를 가져옵니다. 그런 다음 rand데이터 생성을 루프 밖으로 이동하십시오 . 알고리즘이 배열에서 모든 데이터를 가져 와서 모든 데이터를 배열에 넣는 지 확인하십시오. 이는 실제 알고리즘을 자체적으로 가져오고 설정, malloc, 결과 인쇄, 작업 전환을 제외한 모든 것을 가져오고 프로파일 링 루프에서 인터럽트합니다.
Mike D.

3
@maxpenguin : 질문은 당신이 테스트하는 것입니다. Artem은 당신이 그래픽을하고 있다고 가정했고, Carl은 당신이 FP가 아닌 임베디드 플랫폼을 사용하고 있는지, 나는 당신이 서버를위한 과학을 코딩하고 있다고 생각했습니다. 벤치 마크를 일반화하거나 "작성"할 수 없습니다. 벤치 마크는 프로그램이 수행하는 실제 작업에서 샘플링됩니다. 내가 말할 수있는 한 가지는 프로그램에서 성능에 중요한 요소를 건 드리면 "본질적으로 동일한 속도"로 유지되지 않는다는 것입니다.
Potatoswatter

좋은 지적과 좋은 대답. 현재 광범위한 코드 기반이 있습니다. 정말로 나는 우리가 "정수 계산이 더 빠르기 때문에 float를 사용해서는 안된다"는 일반화에 반대했습니다. 그리고이 일반화 된 가정을 반증 할 방법을 찾고 있습니다. 모든 작업을 수행하고 나중에 프로파일 링하지 않으면 정확한 결과를 예측할 수 없다는 것을 알고 있습니다. 어쨌든 도와 주셔서 감사합니다.
maxpenguin

18

TIL 이것은 다양합니다 (많음). 다음은 gnu 컴파일러를 사용한 몇 가지 결과입니다 (btw 나는 또한 기계에서 컴파일하여 확인했습니다. xenial의 gnu g ++ 5.4는 정확하게 linaro의 4.6.3보다 훨씬 빠릅니다)

인텔 i7 4700MQ 제니 얼

short add: 0.822491
short sub: 0.832757
short mul: 1.007533
short div: 3.459642
long add: 0.824088
long sub: 0.867495
long mul: 1.017164
long div: 5.662498
long long add: 0.873705
long long sub: 0.873177
long long mul: 1.019648
long long div: 5.657374
float add: 1.137084
float sub: 1.140690
float mul: 1.410767
float div: 2.093982
double add: 1.139156
double sub: 1.146221
double mul: 1.405541
double div: 2.093173

Intel i3 2370M의 결과도 비슷합니다.

short add: 1.369983
short sub: 1.235122
short mul: 1.345993
short div: 4.198790
long add: 1.224552
long sub: 1.223314
long mul: 1.346309
long div: 7.275912
long long add: 1.235526
long long sub: 1.223865
long long mul: 1.346409
long long div: 7.271491
float add: 1.507352
float sub: 1.506573
float mul: 2.006751
float div: 2.762262
double add: 1.507561
double sub: 1.506817
double mul: 1.843164
double div: 2.877484

Intel (R) Celeron (R) 2955U (xenial을 실행하는 Acer C720 Chromebook)

short add: 1.999639
short sub: 1.919501
short mul: 2.292759
short div: 7.801453
long add: 1.987842
long sub: 1.933746
long mul: 2.292715
long div: 12.797286
long long add: 1.920429
long long sub: 1.987339
long long mul: 2.292952
long long div: 12.795385
float add: 2.580141
float sub: 2.579344
float mul: 3.152459
float div: 4.716983
double add: 2.579279
double sub: 2.579290
double mul: 3.152649
double div: 4.691226

DigitalOcean 1GB Droplet Intel (R) Xeon (R) CPU E5-2630L v2 (트러스티 실행)

short add: 1.094323
short sub: 1.095886
short mul: 1.356369
short div: 4.256722
long add: 1.111328
long sub: 1.079420
long mul: 1.356105
long div: 7.422517
long long add: 1.057854
long long sub: 1.099414
long long mul: 1.368913
long long div: 7.424180
float add: 1.516550
float sub: 1.544005
float mul: 1.879592
float div: 2.798318
double add: 1.534624
double sub: 1.533405
double mul: 1.866442
double div: 2.777649

AMD Opteron (tm) 프로세서 4122 (정밀)

short add: 3.396932
short sub: 3.530665
short mul: 3.524118
short div: 15.226630
long add: 3.522978
long sub: 3.439746
long mul: 5.051004
long div: 15.125845
long long add: 4.008773
long long sub: 4.138124
long long mul: 5.090263
long long div: 14.769520
float add: 6.357209
float sub: 6.393084
float mul: 6.303037
float div: 17.541792
double add: 6.415921
double sub: 6.342832
double mul: 6.321899
double div: 15.362536

이것은 http://pastebin.com/Kx8WGUfg의 코드를 다음 과 같이 사용합니다.benchmark-pc.c

g++ -fpermissive -O3 -o benchmark-pc benchmark-pc.c

여러 번 통과했지만 일반적인 숫자가 동일한 경우 인 것 같습니다.

한 가지 주목할만한 예외는 ALU mul 대 FPU mul 인 것 같습니다. 덧셈과 뺄셈은 사소하게 다르게 보입니다.

위의 차트 형식은 다음과 같습니다 (전체 크기를 보려면 클릭, 낮을수록 더 빠르고 선호 됨) :

위 데이터 차트

@Peter Cordes를 수용하도록 업데이트

https://gist.github.com/Lewiscowles1986/90191c59c9aedf3d08bf0b129065cccc

i7 4700MQ Linux Ubuntu Xenial 64 비트 (2018-03-13에 대한 모든 패치 적용)
    short add: 0.773049
    short sub: 0.789793
    short mul: 0.960152
    short div: 3.273668
      int add: 0.837695
      int sub: 0.804066
      int mul: 0.960840
      int div: 3.281113
     long add: 0.829946
     long sub: 0.829168
     long mul: 0.960717
     long div: 5.363420
long long add: 0.828654
long long sub: 0.805897
long long mul: 0.964164
long long div: 5.359342
    float add: 1.081649
    float sub: 1.080351
    float mul: 1.323401
    float div: 1.984582
   double add: 1.081079
   double sub: 1.082572
   double mul: 1.323857
   double div: 1.968488
AMD Opteron (tm) 프로세서 4122 (정밀, DreamHost 공유 호스팅)
    short add: 1.235603
    short sub: 1.235017
    short mul: 1.280661
    short div: 5.535520
      int add: 1.233110
      int sub: 1.232561
      int mul: 1.280593
      int div: 5.350998
     long add: 1.281022
     long sub: 1.251045
     long mul: 1.834241
     long div: 5.350325
long long add: 1.279738
long long sub: 1.249189
long long mul: 1.841852
long long div: 5.351960
    float add: 2.307852
    float sub: 2.305122
    float mul: 2.298346
    float div: 4.833562
   double add: 2.305454
   double sub: 2.307195
   double mul: 2.302797
   double div: 5.485736
Intel Xeon E5-2630L v2 @ 2.4GHz (Trusty 64 비트, DigitalOcean VPS)
    short add: 1.040745
    short sub: 0.998255
    short mul: 1.240751
    short div: 3.900671
      int add: 1.054430
      int sub: 1.000328
      int mul: 1.250496
      int div: 3.904415
     long add: 0.995786
     long sub: 1.021743
     long mul: 1.335557
     long div: 7.693886
long long add: 1.139643
long long sub: 1.103039
long long mul: 1.409939
long long div: 7.652080
    float add: 1.572640
    float sub: 1.532714
    float mul: 1.864489
    float div: 2.825330
   double add: 1.535827
   double sub: 1.535055
   double mul: 1.881584
   double div: 2.777245

gcc5는 gcc4.6이하지 않은 것을 자동 벡터화 할 수 있습니까? 되어 benchmark-pc처리량과 대기 시간의 조합을 측정? Haswell (i7 4700MQ)에서 정수 곱셈은 클럭 처리량 당 1, 3주기 지연 시간이지만 정수 추가 / 구독 은 클럭 처리량 당 4, 1주기 지연 ( agner.org/optimize )입니다. 따라서 아마도 add와 mul이 너무 가깝게 나오도록 그 숫자를 희석하는 많은 루프 오버 헤드가있을 것입니다 (long add : 0.824088 vs. long mul : 1.017164). (gcc는 매우 낮은 반복 횟수를 완전히 해제하는 경우를 제외하고 기본적으로 루프를 해제하지 않습니다.)
Peter Cordes

그리고 BTW, 왜 테스트하지 않고 intshortlong? Linux x86-64에서는 short16 비트 (따라서 경우에 따라 부분 레지스터 속도 저하가 있음)이고 longlong long둘 다 64 비트 유형입니다. (아마도 x86-64가 여전히 32 비트를 사용하는 Windows 용으로 설계되었거나 long32 비트 모드 용으로 설계되었을 수도 있습니다.) Linux 에서 x32 ABI는 long64 비트 모드에서 32 비트 를 가지고 있으므로 라이브러리가 설치되어있는 경우 , gcc -mx32ILP32 용 컴파일러에 사용 합니다. 아니면 그냥 숫자를 사용 -m32하고보세요 long.
Peter Cordes

그리고 컴파일러가 자동 벡터화되었는지 실제로 확인해야합니다. 예를 들어 , addps대신 xmm 레지스터를 사용하여 addss4 개의 FP를 스칼라만큼 빠른 하나의 명령어에 병렬로 추가합니다 addss. ( -march=nativex86-64에 대한 SSE2 기준뿐만 아니라 CPU가 지원하는 모든 명령 세트를 사용 하도록 허용하는 데 사용).
Peter Cordes

@cincodenada는 성능을 보여주기 때문에 전체 15를 보여주는 차트를 남겨주세요.
MrMesees

@PeterCordes 나는 내일 보려고 노력할 것입니다, 부지런히 감사합니다.
MrMesees

7

고려해야 할 두 가지 사항-

최신 하드웨어는 명령어를 겹치고 병렬로 실행하며 하드웨어를 최대한 활용하기 위해 순서를 변경할 수 있습니다. 또한 중요한 부동 소수점 프로그램은 인덱스를 배열, 루프 카운터 등으로 만 계산하는 경우에도 중요한 정수 작업을 수행 할 가능성이 있으므로 느린 부동 소수점 명령이 있더라도 별도의 하드웨어 비트에서 실행될 수 있습니다. 일부 정수 작업과 겹쳤습니다. 내 요점은 부동 소수점 명령어가 정수 1보다 느리더라도 하드웨어를 더 많이 사용할 수 있기 때문에 전체 프로그램이 더 빠르게 실행될 수 있다는 것입니다.

항상 그렇듯이 확인하는 유일한 방법은 실제 프로그램을 프로파일 링하는 것입니다.

두 번째 요점은 요즘 대부분의 CPU에는 동시에 여러 부동 소수점 값에서 작동 할 수있는 부동 소수점에 대한 SIMD 명령어가 있다는 것입니다. 예를 들어, 4 개의 부동 소수점을 단일 SSE 레지스터에로드하고 모두 병렬로 4 개의 곱셈을 수행 할 수 있습니다. SSE 명령어를 사용하도록 코드의 일부를 다시 작성할 수 있다면 정수 버전보다 빠를 가능성이 높습니다. Visual C ++는이를 수행하는 컴파일러 내장 함수를 제공합니다 . 자세한 내용 은 http://msdn.microsoft.com/en-us/library/x5c07e2a(v=VS.80).aspx 를 참조 하십시오 .


Win64에서 FPU 명령어는 더 이상 MSVC 컴파일러에 의해 생성되지 않습니다. 부동 소수점은 항상 SIMD 명령어를 사용합니다. 이것은 플롭과 관련하여 Win32와 Win64 사이에 큰 속도 불일치를 만듭니다.
James Dunne

5

나머지 연산이 없으면 부동 소수점 버전이 훨씬 느려집니다. 모든 추가가 순차적이기 때문에 CPU는 합산을 병렬화 할 수 없습니다. 대기 시간이 중요합니다. FPU 추가 대기 시간은 일반적으로 3주기이고 정수 추가는 1주기입니다. 그러나 나머지 연산자에 대한 분배기는 최신 CPU에서 완전히 파이프 라인되지 않기 때문에 아마도 중요한 부분이 될 것입니다. 따라서 나누기 / 나머지 명령어가 많은 시간을 소비한다고 가정하면 지연 시간 추가로 인한 차이는 작을 것입니다.


4

초당 수백만 번 호출되는 코드를 작성하지 않는 한 (예 : 그래픽 응용 프로그램에서 화면에 선 그리기) 정수 대 부동 소수점 산술은 병목 현상이 거의 발생하지 않습니다.

효율성 질문에 대한 일반적인 첫 번째 단계는 코드를 프로파일 링하여 런타임이 실제로 소비되는 위치를 확인하는 것입니다. 이에 대한 linux 명령은 gprof.

편집하다:

정수와 부동 소수점 숫자를 사용하여 항상 선 그리기 알고리즘을 구현할 수 있다고 생각하지만 여러 번 호출하여 차이가 있는지 확인합니다.

http://en.wikipedia.org/wiki/Bresenham's_algorithm


2
과학 응용 프로그램은 FP를 사용합니다. FP의 유일한 장점은 정밀도가 척도 불변이라는 것입니다. 과학적 표기법과 같습니다. 숫자의 스케일을 이미 알고있는 경우 (예 : 선 길이가 픽셀 수) FP는 제거됩니다. 하지만 선을 그리기 전에는 사실이 아닙니다.
Potatoswatter

4

오늘날 정수 연산은 일반적으로 부동 소수점 연산보다 조금 더 빠릅니다. 따라서 정수 및 부동 소수점에서 동일한 연산으로 계산을 수행 할 수 있다면 정수를 사용하십시오. 그러나 당신은 "이것은 많은 성가신 문제를 일으키고 많은 성가신 코드를 추가합니다"라고 말하고 있습니다. 부동 소수점 대신 정수 산술을 사용하기 때문에 더 많은 연산이 필요한 것처럼 들립니다. 이 경우 부동 소수점이 더 빨리 실행됩니다.

  • 더 많은 정수 연산이 필요하면 아마도 훨씬 더 많이 필요할 것입니다. 따라서 약간의 속도 이점은 추가 연산에 의해 소모되는 것보다 많습니다.

  • 부동 소수점 코드가 더 간단합니다. 즉, 코드를 작성하는 것이 더 빠릅니다. 즉, 속도가 중요한 경우 코드를 최적화하는 데 더 많은 시간을 할애 할 수 있습니다.


여기에는 종종 계산 시간을 지배하는 하드웨어에 존재하는 2 차 효과를 설명하지 않는 많은 추측이 있습니다. 나쁜 시작점은 아니지만 프로파일 링을 통해 각 특정 응용 프로그램에서 확인해야하며 복음으로 가르치지 않아야합니다.
Ben Voigt 2014

3

rand () 대신 숫자에 1을 더한 테스트를 실행했습니다. 결과 (x86-64에서)는 다음과 같습니다.

  • 짧음 : 4.260 초
  • int : 4.020 초
  • 롱 롱 : 3.350s
  • 부동 : 7.330 초
  • 두 배 : 7.210 초

1
소스, 컴파일 옵션 및 타이밍 방법? 결과에 조금 놀랐습니다.
GManNickG

"rand () % 365"가 "1"로 대체 된 OP와 동일한 루프입니다. 최적화가 없습니다. "시간"명령의 사용자 시간.
dan04

13
"최적화 없음"이 핵심입니다. 최적화를 끄고 프로파일 링하지 않고 항상 "릴리스"모드로 프로파일 링합니다.
Dean Harding

2
하지만이 경우 최적화 해제로 인해 op가 강제로 발생하고 의도적으로 수행됩니다. 루프는 시간을 합리적인 측정 척도로 확장하기 위해 존재합니다. 상수 1을 사용하면 rand () 비용이 제거됩니다. 충분히 스마트 한 최적화 컴파일러는 루프를 벗어나지 않고 100,000,000 번 추가 된 1을보고 단일 작업에 100000000을 추가합니다. 그런 종류의 목적이 전체 목적에 맞지 않습니까?
Stan Rogers

7
@Stan, 변수를 휘발성으로 만드십시오. 스마트 최적화 컴파일러조차도 여러 작업을 수행해야합니다.
vladr

0

믿을 수있는 "내가들은 것"을 기반으로했을 때, 예전에는 정수 계산이 부동 소수점보다 약 20 ~ 50 배 더 빨랐고, 요즘에는 두 배도 안됩니다.


1
의견 이상을 제공하는 것을 다시 검토해보십시오 (특히 수집 된 사실에 직면하여 의견이 날아가는 것처럼 보이는 경우)
MrMesees

1
@MrMesees이 답변은 그다지 유용하지 않지만 귀하가 만든 테스트와 일치한다고 말하고 싶습니다. 그리고 역사적인 퀴즈도 아마 괜찮을 것입니다.
Jonatan Öström

예전에 286s와 함께 일했던 사람으로서 확인할 수 있습니다. "그래 ... 그랬어!"
David H Parry
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.