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. 나는이 세부 사항을 자주 접하고 내가 쓴 것을 기억할 수 있기 때문에 내 자신을 찾는 것이 훨씬 쉽다.