CPU 수준에서 어떤 opcode가 더 빠릅니까? [닫은]


19

모든 프로그래밍 언어에는 다른 것보다 권장되는 opcode 세트가 있습니다. 속도 순서대로 여기에 나열하려고했습니다.

  1. 비트 단위
  2. 정수 더하기 / 빼기
  3. 정수 곱셈 / 나눗셈
  4. 비교
  5. 제어 흐름
  6. 플로트 덧셈 / 빼기
  7. 플로트 곱셈 / 나눗셈

고성능 코드가 필요한 곳에서는 SIMD 명령어 또는보다 효율적인 제어 흐름, 데이터 유형 등을 사용하기 위해 어셈블리에서 C ++을 수동으로 최적화 할 수 있습니다. 따라서 데이터 유형 (int32 / float32 / float64) 또는 사용되는 상기 ( *, +,& )이 CPU 레벨의 성능에 영향을 미친다.

  1. CPU에서 단일 배수가 ​​추가보다 속도가 느립니까?
  2. MCU 이론에서 opcode의 속도는 실행하는 데 소요되는 CPU주기 수에 따라 결정된다는 것을 알게됩니다. 곱하기에 4주기가 걸리고 더하기에 2가 걸리는 것을 의미합니까?
  3. 기본 수학 및 제어 흐름 연산 코드의 속도 특성은 정확히 무엇입니까?
  4. 두 개의 opcode가 동일한 사이클 수를 실행하는 경우 성능 향상 / 손실없이 두 가지 opcode를 서로 바꾸어 사용할 수 있습니까?
  5. x86 CPU 성능과 관련하여 공유 할 수있는 다른 기술 정보는 높이 평가됩니다.

17
이것은 조기 최적화와 비슷하게 들리며 컴파일러는 입력 한 내용을 출력하지 않으며 실제로 그렇게하지 않으면 어셈블리를 작성하고 싶지 않다는 것을 기억하십시오.
Roy T.

3
부동 소수점 곱셈과 나눗셈은 완전히 다른 것이므로 같은 범주에 두지 않아야합니다. n 비트 숫자의 경우 곱셈은 O (n) 프로세스이고 나누기는 O (nlogn) 프로세스입니다. 이것은 최신 CPU에서의 곱셈보다 약 5 배나 느리게합니다.
sam hocevar

1
유일한 대답은 "프로필 작성"입니다.
Tetrad

1
Roy의 대답을 확장하면 손 최적화 어셈블리는 실제로 예외가 아닌 한 거의 항상 순 손실이 될 것입니다. 현대의 CPU는 매우 복잡한 짐승이며 최적화가 잘 된 컴파일러는 코드를 완전히 명확하지 않고 직접 작성하는 것이 쉽지 않은 코드 변환을 수행합니다. SSE / SIMD의 경우에도 항상 C / C ++에서 내장 함수를 사용하고 컴파일러가 사용을 최적화하도록하십시오. 원시 어셈블리를 사용하면 컴파일러 최적화가 비활성화되고 크게 손실됩니다.
Sean Middleditch

SIMD를 사용하기 위해 어셈블리를 수동으로 최적화 할 필요는 없습니다. SIMD는 상황에 따라 최적화하는 데 매우 유용하지만 SSE2 사용을위한 대부분의 표준 규칙 (최소한 GCC 및 MSVC에서 작동)이 있습니다. 목록에 관한 한, 현대의 supserscalar 다중 파이프 라인 프로세서에서 데이터 종속성 및 레지스터 압력은 원시 정수 및 때때로 부동 소수점 성능보다 더 많은 문제를 야기합니다. 데이터 지역도 마찬가지입니다. 그건 그렇고, 정수 나누기는 현대 x86의 곱셈과 같습니다
OrgnlDave

답변:


26

Agner Fog의 최적화 가이드 는 훌륭합니다. 그는 모든 최신 x86 CPU 설계 (인텔 펜티엄까지 거슬러 올라가는)의 마이크로 아키텍처에 대한 지침, 지시 타이밍 테이블 및 문서를 보유하고 있습니다. /programming//tags/x86/info 에서 링크 된 다른 리소스도 참조 하십시오.

재미를 위해 몇 가지 질문 (최근 인텔 CPU의 숫자)에 대답하겠습니다. 코드 최적화의 주요 요소는 운영 부서의 선택이 아닙니다 (분할을 피할 수 없다면).

CPU에서 단일 배수가 ​​추가보다 속도가 느립니까?

예 (2의 거듭 제곱이 아닌 한). (인텔에서는 클럭 당 하나의 처리량으로 3 ~ 4 배의 지연 시간을가집니다.) 2 ~ 3 개의 속도가 빠르기 때문에이를 피하기 위해 방해하지 마십시오.

기본 수학 및 제어 흐름 연산 코드의 속도 특성은 정확히 무엇입니까?

정확히 알고 싶다면 Agner Fog의 명령어 표와 마이크로 아키텍처 안내서를 참조하십시오 . 조건부 점프에주의하십시오. 무조건 점프 (함수 호출과 같은)는 약간의 오버 헤드가 있지만 그리 많지는 않습니다.

두 개의 opcode가 동일한 사이클 수를 실행하는 경우 성능 향상 / 손실없이 두 가지 opcode를 서로 바꾸어 사용할 수 있습니까?

아니, 그들은 다른 것과 같은 실행 포트를 놓고 경쟁하거나 그렇지 않을 수도 있습니다. CPU가 병렬로 작업 할 수있는 다른 종속성 체인에 따라 다릅니다. 실제로는 유용한 결정을 내릴 수 없습니다. 인텔 CPU의 다른 포트에서 실행되는 벡터 시프트 또는 벡터 셔플을 사용할 수있는 경우가 종종 있습니다. 그러나 전체 레지스터의 바이트 단위 ( PSLLDQ등)은 셔플 장치에서 실행됩니다.)

x86 CPU 성능과 관련하여 공유 할 수있는 다른 기술 정보는 높이 평가됩니다.

Agner Fog의 마이크로 아치 문서는 인텔 및 AMD CPU의 파이프 라인을 반복하여 루프 당 반복해야하는 사이클 수와 병목 현상이 UOP 처리량, 종속성 체인 또는 하나의 실행 포트에 대한 경합인지 여부를 정확하게 설명 할 수 있도록 충분히 자세하게 설명합니다. this 또는 this 과 같은 StackOverflow에 대한 내 답변 중 일부를 참조하십시오 .

또한 http://www.realworldtech.com/haswell-cpu/ (및 이전 디자인과 유사)는 CPU 디자인을 좋아한다면 재미있게 읽을 수 있습니다.

여기 내 최고의 손님을 기준으로 Haswell CPU별로 정렬 된 목록이 있습니다. 이것은 실제로 asm 루프를 튜닝하는 것 외에는 아무것도 생각하지 않는 유용한 방법입니다. 캐시 / 분기 예측 효과는 일반적으로 우세하므로 좋은 패턴을 갖도록 코드를 작성하십시오. 숫자는 매우 수동적이며 처리량이 문제가되지 않더라도 파이프 라인을 막히는 더 많은 UOP를 생성하여 다른 일이 병렬로 발생할 수 있도록 더 많은 대기 시간을 고려하려고합니다. Esp. 캐시 / 분기 번호는 매우 구성되어 있습니다. 지연은 루프로 수행되는 종속성에 중요하며 처리는 각 반복이 독립적 일 때 중요합니다.

TL : DR이 숫자는 대기 시간, 실행 포트 병목 현상 및 프런트 엔드 처리량 간의 균형 (또는 분기 누락과 같은 문제의 경우)에 대한 "일반적인"사용 사례를 위해 무엇을 캡처하고 있는지에 따라 구성됩니다. ). 심각한 성능 분석에는이 숫자를 사용하지 마십시오 .

  • 0.5 ~ 1 비트 단위 / 정수 덧셈 / 뺄셈 /
    시프트 및 회전 (컴파일 타임 const 카운트) /
    벡터 버전 (사이클 당 처리량 당 1 ~ 4, 1 사이클 대기 시간)
  • 1 벡터 최소, 최대, 같음 비교,보다 큼 (마스크 만들기)
  • 1.5 벡터 셔플. Haswell과 최신 버전에는 하나의 셔플 포트 만 있으며, 필요한 경우 많은 셔플 링이 필요한 것이 일반적이므로 셔플을 적게 사용하는 것에 대한 생각을 높이기 위해 약간 더 높은 가중치를 부여하고 있습니다. 그들은 무료가 아닙니다. 메모리에서 pshufb 제어 마스크가 필요한 경우.
  • 1.5로드 / 저장 (지연 시간보다 L1 캐시 적중 처리량 향상)
  • 1.75 정수 곱셈 (Intelger의 3c 대기 시간 / 1c tput 당 1 개, AMD의 4c lat 및 2c tput 당 1 개). LEA 및 / 또는 ADD / SUB / shift를 사용하면 작은 상수가 훨씬 저렴 합니다. 그러나 물론 컴파일 타임 상수는 항상 좋으며 종종 다른 것으로 최적화 할 수 있습니다. (그리고 루프를 곱하면 종종 컴파일러가 tmp += 7루프 대신 강도를 줄일 수 있습니다.tmp = i*7 )
  • 1.75 약 256b 벡터 셔플 (AVX 벡터의 128b 레인간에 데이터를 이동할 수있는 인스 턴 스의 추가 대기 시간) (또는 레인 교차로 셔플에 더 많은 UOP가 필요한 Ryzen에서 3 ~ 7)
  • 2fp 추가 / 서브 (및 동일한 벡터 버전) (사이클 처리량 당 1 또는 2, 3 ~ 5 사이클 대기 시간) 지연 시간에 병목 현상이 발생하면 (예 : sum변수가 1 개인 배열 합산) 속도가 느려질 수 있습니다 . (사용 사례에 따라 가중치를 낮출 수 있고 fp mul을 1 또는 5로 낮출 수 있습니다).
  • 2 벡터 fp mul 또는 FMA. (x * y + z는 FMA 지원이 활성화 된 상태에서 컴파일하는 경우 mul 또는 add보다 저렴합니다).
  • 2 범용 레지스터를 벡터 요소에 삽입 / 추출 ( _mm_insert_epi8등)
  • 2.25 벡터 int mul (16 비트 요소 또는 8 * 8-> 16 비트를 수행하는 pmaddubsw). 스칼라 mul보다 더 나은 처리량으로 Skylake에서 저렴
  • 가변 카운트 (2c 대기 시간, 인텔에서 2c 처리 당 1 개, AMD 또는 BMI2에서 더 빠름)로 2.25 시프트 / 회전
  • 2.5 분기없는 비교 ( y = x ? a : b, 또는 y = x >= 0) ( test / setcc또는 cmov)
  • 3 int-> float 변환
  • 3 완벽하게 예측 된 제어 흐름 (예상 분기, 호출, 반환).
  • 4 개의 벡터 int mul (32 비트 요소) (2 uops, Haswell에서 10c 대기 시간)
  • 4 정수 나누기 또는 %컴파일 타임 상수 (2의 제곱이 아님).
  • 7 개의 벡터 수평 연산 (예 : PHADD벡터 내에 값 추가)
  • 11 (벡터) FP 분할 (10-13c 대기 시간, 7c 처리량 당 하나 이상) (드물게 사용하면 저렴할 수 있지만 처리량은 FP mul보다 6-40 배 더 나쁩니다)
  • 13? 제어 흐름 (예상치 못한 예측, 아마도 75 % 예측 가능)
  • 13 int division ( 그렇습니다. FP division보다 느리고 벡터화 할 수 없습니다). (컴파일러 는 마법 상수와 함께 mul / shift / add를 사용하여 상수로 나눕니다. div / mod는 2의 거듭 제곱으로 매우 저렴합니다.)
  • 16 (벡터) FP sqrt
  • 25? 로드 (L3 캐시 적중). (캐시 미스 스토어는로드보다 저렴합니다.)
  • 50? FP trig / exp / log. 많은 exp / log가 필요하고 전체 정확도가 필요하지 않은 경우, 짧은 다항식 및 / 또는 테이블을 사용하여 속도의 정확도를 교환 할 수 있습니다. 벡터화 할 수도 있습니다.
  • 50-80? 항상 잘못 예측되는 분기, 15-20주기 비용
  • 200-400? 로드 / 저장 (캐시 미스)
  • 3000 ??? 파일에서 페이지 읽기 (OS 디스크 캐시 적중) (여기서 숫자 작성)
  • 20000 ??? 디스크 읽기 페이지 (OS 디스크 캐시 누락, 빠른 SSD) (완전히 구성된 수)

나는 추측에 근거하여 이것을 완전히 만들었다 . 뭔가 잘못 보이면 다른 사용 사례를 생각하거나 편집 오류가 발생했기 때문입니다.

시프트 횟수가 가변적 일 때 정수 시프터가 더 빠르다는 점을 제외하면 AMD CPU의 상대적 비용은 비슷합니다. AMD 불도저 제품군 CPU는 여러 가지 이유로 대부분의 코드에서 느리게 진행됩니다. (Ryzen은 많은 것을 잘합니다).

일차원적인 비용으로 물건을 끓이는 것은 실제로 불가능하다는 것을 명심하십시오 . 캐시 누락 및 분기 오판 이외의 코드 블록의 병목 현상은 대기 시간, 총 uop 처리량 (프론트 엔드) 또는 특정 포트의 처리량 (실행 포트) 일 수 있습니다.

FP 부서와 같은 "느린"작업 은 주변 코드로 인해 다른 작업으로 CPU 사용량이 많은 경우 매우 저렴할 수 있습니다. . (벡터 FP div 또는 sqrt는 각각 1uop이며 지연 시간과 처리량이 나쁩니다. 전체 실행 포트가 아닌 나누기 단위 만 차단합니다. 정수 div는 몇 번입니다.) 따라서 하나의 FP 나누기가있는 경우 ~ 20 mul마다 추가하고 CPU가 수행해야 할 다른 작업 (예 : 독립 루프 반복)이 있으면 FP div의 "비용"은 FP mul과 거의 동일 할 수 있습니다. 이것은 아마도 당신이하는 모든 일에서 처리량이 적은 것의 가장 좋은 예 일 것입니다.

정수 나누기는 주변 코드와 거의 비슷하지 않습니다. Has Haswell에서는 처리량이 8-11c 당 1 개이고 대기 시간이 22-29c 인 9 개의 Uops입니다. ( Skylake에서도 64 비트 분할이 훨씬 느립니다.) 따라서 대기 시간 및 처리량 수치는 FP div와 다소 비슷하지만 FP div는 하나의 UOP 일뿐입니다.

처리량, 대기 시간 및 총 UOP에 대한 간단한 일련의 기능을 분석하는 예는 다음 SO 답변 중 일부를 참조하십시오.

다른 사람들이 이런 종류의 분석을 포함하여 SO 답변을 작성하는 경우 IDK. 나는이 세부 사항을 자주 접하고 내가 쓴 것을 기억할 수 있기 때문에 내 자신을 찾는 것이 훨씬 쉽다.


4의 "예측 지점"은 의미가 있습니다. 20-25의 "예측 지점"은 실제로 무엇이어야합니까? (I 훨씬 더 비싼보다했다) 잘못 예측 분기 (약 13 나열 생각했지만, 내가 가까이 진실에 무언가를 배우고,이 페이지에있어 그 이유는 정확히 - 위대한 테이블에 대한 감사합니다!)
매트

@ 매트 : 편집 오류이며 "예상치 못한 지점"으로 생각되었습니다. 지적 해 주셔서 감사합니다. 13은 항상 잘못 예측 된 분기가 아니라 불완전하게 예측 된 분기를위한 것이므로이 점을 명확히했습니다. 나는 손짓을 다시하고 약간의 편집을했다. :피
Peter Cordes

16

문제의 CPU에 따라 다르지만 최신 CPU의 경우 목록은 다음과 같습니다.

  1. 비트, 덧셈, 뺄셈, 비교, 곱셈
  2. 분할
  3. 제어 흐름 (답변 3 참조)

CPU에 따라 64 비트 데이터 유형으로 작업하는 데 상당한 비용이들 수 있습니다.

당신의 질문:

  1. 최신 CPU에서는 전혀 또는 전혀 눈에 띄지 않습니다. CPU에 따라 다릅니다.
  2. 그 정보는 20 ~ 30 년이 지나서 구식입니다 (학교가 짜증나고, 이제 증명을 얻었습니다). 현대 CPU는 클럭 당 가변 개수의 명령을 처리합니다.
  3. 나눗셈은 나머지보다 약간 느리고, 분기 예측이 정확하면 제어 흐름이 매우 빠르며, 잘못하면 매우 느립니다 (20 사이클과 같은 것은 CPU에 따라 다름) 결과적으로 많은 코드가 주로 제어 흐름에 의해 제한됩니다. if산술로 합리적으로 할 수 있는 일을 하지 마십시오 .
  4. 어떤 명령어가 얼마나 많은 사이클을 수행하는지에 대한 고정 된 숫자는 없지만 때로는 두 개의 다른 명령어가 동일하게 수행되어 다른 컨텍스트에 배치 될 수도 있고 그렇지 않을 수도 있습니다. 다른 CPU에서 실행하면 세 번째 결과를 볼 수 있습니다.
  5. 제어 흐름 이외에도 캐시에없는 데이터를 읽으려고 할 때마다 CPU가 메모리에서 데이터를 가져 오기를 기다려야합니다. 일반적으로 모든 곳에서 데이터를 선택하지 않고 서로 옆에있는 데이터를 동시에 처리해야합니다.

마지막으로 게임을 만드는 경우 CPU 사이클을 자르는 것보다 좋은 게임을 만드는 데 집중하십시오.


또한 FPU가 매우 빠르다는 점을 지적하고 싶습니다. 특히 Intel의 경우 결정적 결과를 원할 경우 고정 소수점이 실제로 필요합니다.
Jonathan Dickinson

2
나는 마지막 부분에 더 중점을 두었습니다. 좋은 게임을 만드십시오. 코드를 명확하게하는 데 도움이되므로 성능 문제를 실제로 측정 할 때만 적용됩니다. 필요한 경우 이러한 ifs를 더 나은 것으로 변경하는 것은 항상 쉽습니다. 반면에, 5. 더 까다 롭습니다. 저는 이것이 일반적으로 아키텍처를 바꾸는 것을 의미하기 때문에 당신이 정말로 먼저 생각하고 싶은 경우라는 것에 분명히 동의합니다.
Luaan

3

x64_64에서 정수 연산 마녀가 백만 번 반복되는 것에 대한 테스트를 수행했으며 다음과 같은 간단한 결론에 도달했습니다.

--- 116 마이크로 초 추가

하위 ---- 116 마이크로 초

mul ---- 1036 마이크로 초

div ---- 13037 마이크로 초

위의 데이터는 이미 루프로 인한 오버 헤드를 줄였습니다.


2

인텔 프로세서 설명서는 웹 사이트에서 무료로 다운로드 할 수 있습니다. 그것들은 상당히 크지 만 기술적으로 당신의 질문에 대답 할 수 있습니다. 특히 최적화 매뉴얼은 사용자가 추구하는 것이지만, 매뉴얼에는 칩마다 다르기 때문에 simd 명령에 대한 대부분의 주요 CPU 라인에 대한 타이밍과 대기 시간도 있습니다.

일반적으로 전체 분기와 포인터 추적 (링크 목록 순회, 가상 함수 호출)을 최고 성능까지 고려하지만 x86 / x64 CPU는 다른 아키텍처와 비교할 때 매우 우수합니다. 다른 플랫폼으로 포팅 한 경우 고성능 코드를 작성하는 경우 얼마나 많은 문제가 발생할 수 있는지 알 수 있습니다.


+1, 의존적 부하 (포인터 체 이싱)는 큰 문제입니다. 캐시 미스는 향후로드가 시작되지 않도록 차단합니다. 메인 메모리에서 한 번에 많은 부하를받는 것은 한 번의 작업으로 이전을 완전히 완료하는 것보다 훨씬 나은 대역폭을 제공합니다.
Peter Cordes
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.