버블 정렬이 GCC의 -O2보다 -O3의 -O3의 -O2일 때
C에서 버블 정렬을 구현하고 성능을 테스트하고 있을 때 이 버블 정렬이-O3
깃발은 깃발이 전혀 없는 것보다 더 느리게 작동하게 만들었다! ★★★★★★★★★★★★★★★★★.-O2
기대했던 것보다 훨씬 더 빨리 작동하게 만든 것 같아요.
최적화 없음:
time ./sort 30000
./sort 30000 1.82s user 0.00s system 99% cpu 1.816 total
-O2
:
time ./sort 30000
./sort 30000 1.00s user 0.00s system 99% cpu 1.005 total
-O3
:
time ./sort 30000
./sort 30000 2.01s user 0.00s system 99% cpu 2.007 total
코드:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>
int n;
void bubblesort(int *buf)
{
bool changed = true;
for (int i = n; changed == true; i--) { /* will always move at least one element to its rightful place at the end, so can shorten the search by 1 each iteration */
changed = false;
for (int x = 0; x < i-1; x++) {
if (buf[x] > buf[x+1]) {
/* swap */
int tmp = buf[x+1];
buf[x+1] = buf[x];
buf[x] = tmp;
changed = true;
}
}
}
}
int main(int argc, char *argv[])
{
if (argc != 2) {
fprintf(stderr, "Usage: %s <arraysize>\n", argv[0]);
return EXIT_FAILURE;
}
n = atoi(argv[1]);
if (n < 1) {
fprintf(stderr, "Invalid array size.\n");
return EXIT_FAILURE;
}
int *buf = malloc(sizeof(int) * n);
/* init buffer with random values */
srand(time(NULL));
for (int i = 0; i < n; i++)
buf[i] = rand() % n + 1;
bubblesort(buf);
return EXIT_SUCCESS;
}
에 대해 -O2
(godbolt.org에서):
bubblesort:
mov r9d, DWORD PTR n[rip]
xor edx, edx
xor r10d, r10d
.L2:
lea r8d, [r9-1]
cmp r8d, edx
jle .L13
.L5:
movsx rax, edx
lea rax, [rdi+rax*4]
.L4:
mov esi, DWORD PTR [rax]
mov ecx, DWORD PTR [rax+4]
add edx, 1
cmp esi, ecx
jle .L2
mov DWORD PTR [rax+4], esi
mov r10d, 1
add rax, 4
mov DWORD PTR [rax-4], ecx
cmp r8d, edx
jg .L4
mov r9d, r8d
xor edx, edx
xor r10d, r10d
lea r8d, [r9-1]
cmp r8d, edx
jg .L5
.L13:
test r10b, r10b
jne .L14
.L1:
ret
.L14:
lea eax, [r9-2]
cmp r9d, 2
jle .L1
mov r9d, r8d
xor edx, edx
mov r8d, eax
xor r10d, r10d
jmp .L5
-O3
:
bubblesort:
mov r9d, DWORD PTR n[rip]
xor edx, edx
xor r10d, r10d
.L2:
lea r8d, [r9-1]
cmp r8d, edx
jle .L13
.L5:
movsx rax, edx
lea rcx, [rdi+rax*4]
.L4:
movq xmm0, QWORD PTR [rcx]
add edx, 1
pshufd xmm2, xmm0, 0xe5
movd esi, xmm0
movd eax, xmm2
pshufd xmm1, xmm0, 225
cmp esi, eax
jle .L2
movq QWORD PTR [rcx], xmm1
mov r10d, 1
add rcx, 4
cmp r8d, edx
jg .L4
mov r9d, r8d
xor edx, edx
xor r10d, r10d
lea r8d, [r9-1]
cmp r8d, edx
jg .L5
.L13:
test r10b, r10b
jne .L14
.L1:
ret
.L14:
lea eax, [r9-2]
cmp r9d, 2
jle .L1
mov r9d, r8d
xor edx, edx
mov r8d, eax
xor r10d, r10d
jmp .L5
나에게 중요한 유일한 차이점은 SIMD를 사용하려는 명백한 시도인 것 같습니다. 이것은 큰 개선이 될 것 같지만, 나는 또한 그것들로 대체 무엇을 시도하는지 알 수 없습니다.pshufd
... 이건 요? 몇 추가 명령이 명령캐시를 일 수도 .SIMD? 아니면 명령 캐시를 조금씩 제거하는 것 뿐인가요?
타이밍은 AMD Ryzen 5 3600으로 설정되었습니다.
GCC의 스토어 포워딩 스톨에 대한 순진함이 이곳의 자동 벡터화 전략에 타격을 주고 있는 것 같다.하드웨어 퍼포먼스 카운터를 사용한 인텔의 실제 벤치마크에 대해서는, 「Store Forwarding by hardware performance counters」를 참조해 주세요.또, x86에서의 스토어 투 로드 전송에 실패했을 경우의 코스트는 얼마입니까?또한 Agner Fog의 x86 최적화 가이드.
)gcc -O3
를 유효하게 하다-ftree-vectorize
에 되지 않은 몇 옵션-O2
예 , ) 。if
- - '브런치리스'cmov
이는 GCC가 예상하지 못한 데이터 패턴에 피해를 줄 수 있는 또 다른 방법입니다.이에 비해 Clang은 자동벡터라이제이션이 가능합니다.-O2
그 「」로 하게 되어 .-O3
64비트 로드(및 저장 여부에 관계없이 분기)를 int 쌍으로 수행합니다.즉, 마지막 반복을 스왑하면 이 로드의 절반은 해당 저장소에서, 절반은 신규 메모리에서 발생하므로 스왑할 때마다 스토어 포워딩 스톨이 발생합니다.그러나 버블 정렬은 종종 요소 버블처럼 모든 반복을 교환하는 긴 체인을 가지고 있기 때문에 이는 매우 좋지 않습니다.
(버블 정렬은 일반적으로 좋지 않습니다.특히 이전 반복의 두 번째 요소를 레지스터에 보관하지 않고 순진하게 구현된 경우에는 더욱 그렇습니다.asm의 상세 내용을 분석하면 재미있어지기 때문에 시도해 봐도 무방합니다.)
어쨌든 이것은 GCC Bugzilla에서 "missed-optimization" 키워드를 사용하여 보고해야 하는 안티 최적화입니다.스칼라 로드는 저렴하고 스토어 포워딩은 비용이 많이 듭니다.(현대판 x86 구현은 여러 이전 스토어에서 스토어 포워딩이 가능합니까?또한 Atom 이외의 마이크로아키텍처는 이전 스토어 중 하나와 부분적으로 중복되거나 L1d 캐시에서 전송되어야 하는 데이터에서 효율적으로 로딩할 수 없습니다.)
좋은 은 계속 가지고 있는 이다.buf[x+1]
buf[x]
저장 및 로드를 피할 수 있습니다.(수기 ASM 버블 정렬의 좋은 예와 같이, 그 중 일부는 스택 오버플로에 존재합니다).
(AFIK GCC가 코스트 모델에서는 알 수 없는) 스토어 포워딩 스토어가 아니었다면, 이 전략은 손익분기점에 관한 것이었을지도 모른다.SSE 4.1(브런치리스용)pmind
pmaxd
Comparator는 흥미롭지만 항상 저장해야 하며 C 소스에서는 그렇지 않습니다.
이 배폭 부하 전략이 장점이 있다면 x86-64와 같은 64비트 머신에 순수 정수를 사용하여 구현하면 좋을 것입니다.x86-64에서는 상위 절반의 가비지(또는 귀중한 데이터)가 있는 하위 32비트만 조작할 수 있습니다.예.,
## What GCC should have done,
## if it was going to use this 64-bit load strategy at all
movsx rax, edx # apparently it wasn't able to optimize away your half-width signed loop counter into pointer math
lea rcx, [rdi+rax*4] # Usually not worth an extra instruction just to avoid an indexed load and indexed store, but let's keep it for easy comparison.
.L4:
mov rax, [rcx] # into RAX instead of XMM0
add edx, 1
# pshufd xmm2, xmm0, 0xe5
# movd esi, xmm0
# movd eax, xmm2
# pshufd xmm1, xmm0, 225
mov rsi, rax
rol rax, 32 # swap halves, just like the pshufd
cmp esi, eax # or eax, esi? I didn't check which is which
jle .L2
movq QWORD PTR [rcx], rax # conditionally store the swapped qword
BMI2를 ('BMI2'에서 )-march=native
,rorx rsi, rax, 32
이데올로기 때문에사용하지 않을 BMI2는mov
복사 대신 원본 파일을 스왑하면 Ice Lake와 같은 이동 제거 기능이 없는 CPU에서 실행되는 경우 지연 시간이 줄어듭니다.)
따라서 비교할 부하의 총 지연 시간은 정수 부하 + 1개의 ALU 연산(회전)입니다.로드 비교 -> XMM 부부 ->movd
그리고 ALUUUP도 적습니다.그러나 이것은 스토어 포워딩 스톨 문제에 전혀 도움이 되지 않습니다.이것은 여전히 쇼스토퍼입니다.이는 pshufd x 2와 pshufd x 2를 대체하는 동일한 전략의 정수 SWAR 구현에 불과합니다.movd r32, xmm
mov
+rol
.
은 쓸 pshufd
레지스터를 와 XMM의 양쪽 할 수 .movd
XMM regs는 XMM regs입니다. 의 두 부분이 두 의 GCC를 했습니다.pshufd
지시사항; 하나는 셔플 상수를 16진수로 인쇄하고 다른 하나는 10진수로 인쇄합니다!하려고 것 .vec[1]
qword의 입니다.
전혀 깃발이 없는 것보다 느리다
은 "Default"-O0
C 스테이트먼트마다 모든 변수를 메모리에 흘려보내는 일관된 디버깅모드입니다.따라서 상당히 끔찍하고 큰 스토어 포워딩 레이텐시의 보틀 넥이 발생합니다.(모든 변수가 다음과 같은 경우)volatile
그러나 스토어 포워딩은 성공적이어서 정지하지 않고 최대 5사이클에 불과하지만 레지스터의 경우 0보다 훨씬 심각합니다(Zen 2를 비롯한 일부 최신 마이크로 아키텍처는 지연 시간이 짧은 특수한 경우가 있습니다).파이프라인을 통과해야 하는 추가 저장 및 적재 지침은 도움이 되지 않습니다.
으로 벤치마킹하는 -O0
-O1
★★★★★★★★★★★★★★★★★」-Og
컴파일러가 일반인이 기대하는 최적화의 기본적인 양을 실행하기 위한 기준선이 되어야 합니다.단, 레지스터 할당을 건너뛰어 asm을 의도적으로 강요해서는 안 됩니다.
반관련성: 속도가 아닌 크기에 맞게 버블 정렬을 최적화하려면 메모리 대상 회전(백투백 스왑을 위한 스토어 포워딩 스톨 생성) 또는 메모리 대상이 필요할 수 있습니다.xchg
(비밀(이행)lock
prefix -> 매우 느립니다).이 Code Golf 답변을 참조하십시오.
언급URL : https://stackoverflow.com/questions/69503317/bubble-sort-slower-with-o3-than-o2-with-gcc
'sourcecode' 카테고리의 다른 글
JSON 문자열을 HashMap으로 변환 (0) | 2022.07.27 |
---|---|
Unix 콘솔 또는 Mac 터미널에서 C/C++ 코드를 컴파일하여 실행하려면 어떻게 해야 합니까? (0) | 2022.07.27 |
VM을 사용할 시기 또는 이 항목을 선택합니다.Vue.js로 (0) | 2022.07.26 |
다른 vue 앱에 vue 앱을 포함하려면 어떻게 해야 합니까? (0) | 2022.07.26 |
비트맵 개체를 액티비티 간에 전달하려면 어떻게 해야 합니까? (0) | 2022.07.26 |