부호없는 정수와 부호있는 정수의 성능 차이는 무엇입니까? [닫은] 정수와 부동 소수점을 혼합 할

부호있는 정수와 부동 소수점을 혼합 할 때의 성능 저하를 알고 있습니다.

부호없는 정수와 부동 소수점을 혼합하는 것이 더 나쁜가요?

플로트없이 부호있는 / 부호없는 믹싱 할 때 히트가 있습니까?

다른 크기 (u32, u16, u8, i32, i16, i8)가 성능에 영향을 미칩니 까? 어떤 플랫폼에서?



답변

int (모든 종류의)와 float를 혼합하면 큰 페널티는 서로 다른 레지스터 세트에 있기 때문입니다. 한 레지스터 세트에서 다른 레지스터 세트로 이동하려면 값을 메모리에 쓰고 다시 읽어야하므로 로드 적중 저장소 스톨이 발생합니다.

다른 크기 또는 int의 부호 사이를 이동하면 모든 레지스터가 동일한 레지스터 세트에 유지되므로 큰 벌칙을 피할 수 있습니다. 부호 확장 등으로 인해 더 적은 벌금이 부과 될 수 있지만, 이는로드 적중 상점보다 훨씬 작습니다.


답변

Xbox 360 및 PS3에 대한 정보는 특히 하위 수준의 세부 정보와 같이 라이선스가 허여 된 개발자 전용 벽 뒤에있을 것으로 생각됩니다. 그러나 동등한 x86 프로그램을 구성하고 분해하여 일반적인 아이디어를 얻을 수 있습니다.

먼저 서명되지 않은 확장 비용이 무엇인지 살펴 보겠습니다.

unsigned char x = 1;
unsigned int y = 1;
unsigned int z;
z = x;
z = y;

관련 부분은 다음과 같이 분해됩니다 (GCC 4.4.5 사용).

    z = x;
  27:   0f b6 45 ff             movzbl -0x1(%ebp),%eax
  2b:   89 45 f4                mov    %eax,-0xc(%ebp)
    z = y;
  2e:   8b 45 f8                mov    -0x8(%ebp),%eax
  31:   89 45 f4                mov    %eax,-0xc(%ebp)

따라서 기본적으로 동일합니다. 한 경우에는 바이트를 이동하고 다른 경우에는 단어를 이동합니다. 다음:

signed char x = 1;
signed int y = 1;
signed int z;
z = x;
z = y;

로 바뀝니다 :

   z = x;
  11:   0f be 45 ff             movsbl -0x1(%ebp),%eax
  15:   89 45 f4                mov    %eax,-0xc(%ebp)
    z = y;
  18:   8b 45 f8                mov    -0x8(%ebp),%eax
  1b:   89 45 f4                mov    %eax,-0xc(%ebp)

따라서 부호 확장 비용은 하위 명령 수준이 movsbl아닌 비용입니다 movzbl. 현대식 프로세서의 작동 방식으로 인해 현대식 프로세서에서 정량화하는 것은 기본적으로 불가능합니다. 메모리 속도에서 캐싱, 파이프 라인에 있던 것에 이르기까지 모든 것이 런타임을 지배하게 될 것입니다.

~ 10 분 만에 이러한 테스트를 작성하는 데 걸리는 시간에 실제 성능 버그를 쉽게 발견 할 수 있었으며 컴파일러 최적화 수준을 설정하자마자 이러한 간단한 작업에서 코드를 인식 할 수 없게되었습니다.

이것은 스택 오버플로가 아니므로 여기서는 아무도 마이크로 최적화가 중요하지 않다고 주장하지 않기를 바랍니다. 게임은 종종 매우 크고 숫자가 큰 데이터에서 작동하므로 분기, 캐스트, 일정, 구조 정렬 등에주의를 기울이면 매우 중요한 개선이 이루어질 수 있습니다. PPC 코드를 최적화하는 데 많은 시간을 보낸 사람이라면 아마도로드 히트 상점에 대한 공포 이야기가 적어도 하나있을 것입니다. 그러나이 경우에는 문제가되지 않습니다. 정수 유형의 스토리지 크기는 정렬되고 레지스터에 맞는 한 성능에 영향을 미치지 않습니다.


답변

부호있는 정수 연산은 거의 모든 아키텍처에서 더 비쌀 수 있습니다. 예를 들어, 부호없는 경우 상수로 나누기가 더 빠릅니다. 예 :

unsigned foo(unsigned a) { return a / 1024U; }

다음에 최적화 될 것입니다.

unsigned foo(unsigned a) { return a >> 10; }

그러나…

int foo(int a) { return a / 1024; }

다음에 최적화합니다.

int foo(int a) {
  return (a + 1023 * (a < 0)) >> 10;
}

또는 분기가 저렴한 시스템에서

int foo(int a) {
  if (a >= 0) return a >> 10;
  else return (a + 1023) >> 10;
}

모듈로도 마찬가지입니다. 2의 제곱이 아닌 경우에도 마찬가지입니다 (그러나 예제는 더 복잡합니다). 아키텍처에 하드웨어 분할 (예 : 대부분의 ARM)이없는 경우 서명되지 않은 비 정렬 분할도 더 빠릅니다.

일반적으로 컴파일러에 음수를 지정할 수 없으면 표현식, 특히 루프 종료 및 기타 조건에 사용되는 표현식을 최적화하는 데 도움이됩니다.

다른 크기의 정수에 관해서는 그렇습니다. 약간의 영향이 있지만 메모리를 덜 옮기는 것과 무게를 비교해야합니다. 요즘에는 확장 할 때보 다 적은 메모리에 액세스하여 더 많은 것을 얻을 수 있습니다. 당신은 그 시점에서 마이크로 최적화에 먼 거리에 있습니다.


답변

부호있는 또는 부호없는 int를 사용하는 작업은 현재 프로세서 (x86_64, x86, powerpc, arm)에서 동일한 비용을가집니다. 32 비트 프로세서에서 u32, u16, u8 s32, s16, s8은 같아야합니다. 잘못된 정렬로 페널티를받을 수 있습니다.

그러나 int를 float로 변환하거나 float를 int로 변환하는 것은 비용이 많이 드는 작업입니다. 최적화 된 구현 (SSE2, Neon …)을 쉽게 찾을 수 있습니다.

가장 중요한 점은 아마도 메모리 액세스 일 것입니다. 데이터가 L1 / L2 캐시에 맞지 않으면 변환보다 더 많은주기가 느슨해집니다.


답변

존 퍼디 (Jon Purdy)는 위에서 언급 할 수 없다고 말했다. 서명되지 않은 산술은 단어의 비트 수에 대한 간단한 모수 산술 모듈로 2입니다. 서명 된 작업은 원칙적으로 오버플로가 발생할 수 있지만 일반적으로 해제되어 있습니다.

때로는 두 개 이상의 데이터 항목을 int로 묶는 것과 같이 영리한 (그러나 읽기 쉬운 것은 아니지만) 명령 당 여러 작업 (포켓 산술)을 얻을 수 있습니다. 그러나 당신은 무엇을하고 있는지 이해해야합니다. 물론 MMX를 사용하면 자연스럽게이 작업을 수행 할 수 있습니다. 그러나 때로는 가장 큰 HW 지원 단어 크기를 사용하고 수동으로 데이터를 패킹하면 가장 빠른 구현이 가능합니다.

데이터 정렬에주의하십시오. 대부분의 HW 구현에서 정렬되지 않은로드 및 저장은 느립니다. 자연스러운 정렬은 4 바이트 워드의 경우 주소가 4의 배수이고 8 바이트 워드 주소가 8 바이트의 배수 여야 함을 의미합니다. 이것은 SSE로 넘어갑니다 (128 비트는 16 바이트 정렬을 선호합니다). AVX는 곧 이러한 “벡터”레지스터 크기를 256 비트에서 512 비트로 확장합니다. 정렬 된로드 / 스토어는 정렬되지 않은로드 / 스토어보다 빠릅니다. HW 괴짜에게는 정렬되지 않은 메모리 작업이 캐시 라인 및 페이지 경계와 같은 범위에 걸쳐있을 수 있으며 HW 가주의해야합니다.


답변

C에서는 부호있는 오버플로가 정의되지 않기 때문에 루프 인덱스에 부호있는 정수를 사용하는 것이 약간 좋습니다. 따라서 컴파일러는 그러한 루프의 코너 수가 더 적다고 가정합니다. 이것은 gcc의 “-fstrict-overflow”(기본적으로 활성화되어 있음)에 의해 제어되며 어셈블리 출력을 읽지 않으면 그 효과를 알아 채기가 어려울 수 있습니다.

그 외에도 x86은 메모리 피연산자를 사용할 수 있기 때문에 유형을 혼합하지 않으면 더 잘 작동합니다. 유형을 변환해야하는 경우 (확장자 또는 0 확장) 명시적인로드 및 레지스터 사용을 의미합니다.

지역 변수에 대해 int를 사용하면 대부분 기본적으로 발생합니다.


답변

celion이 지적했듯이 int와 float 간 변환 오버 헤드는 레지스터 간의 값 복사 및 변환과 관련이 있습니다. 서명되지 않은 int 자체의 유일한 오버 헤드는 보장 된 랩 어라운드 동작에서 비롯되므로 컴파일 된 코드에서 일정량의 오버플로 검사가 필요합니다.

부호있는 정수와 부호없는 정수를 변환 할 때 기본적으로 오버 헤드는 없습니다. 플랫폼에 따라 다른 크기의 정수 (무한하게) 액세스하는 속도가 빠르거나 느려질 수 있습니다. 일반적으로, 플랫폼의 워드 크기에 가장 가까운의 정수의 크기에 가장 빠른 것입니다 액세스, 하지만 전반적인 성능 차이는 특히 캐시 크기, 다른 많은 요인에 따라 달라집니다 당신이 사용하는 경우 uint64_t당신이 필요로하는 모든 일 때 uint32_t, 그것은 할 수있다 적은 양의 데이터가 한 번에 캐시에 들어가기 때문에로드 오버 헤드가 발생할 수 있습니다.

그래도 이것에 대해 생각하는 것은 약간 과도합니다. 데이터에 적합한 유형을 사용하면 완벽하게 작동해야하며 아키텍처를 기반으로 유형을 선택하여 얻을 수있는 힘의 양은 무시할 수 있습니다.