속도 대신 크기를 최적화하면 GCC가 15-20 % 더 빠른 코드를 생성하는 이유는 무엇입니까?


445

2009 년에 GCC (적어도 내 프로젝트와 컴퓨터에서)는 속도 ( 또는 ) 대신 크기 ( -Os)를 최적화하면 눈에 띄게 더 빠른 코드를 생성하는 경향이 있으며 그 이후로 궁금해하고 있습니다.-O2-O3

나는이 놀라운 행동을 보여주는 (어리석지 않은) 코드를 만들었고 여기에 게시하기에 충분히 작습니다.

const int LOOP_BOUND = 200000000;

__attribute__((noinline))
static int add(const int& x, const int& y) {
    return x + y;
}

__attribute__((noinline))
static int work(int xval, int yval) {
    int sum(0);
    for (int i=0; i<LOOP_BOUND; ++i) {
        int x(xval+sum);
        int y(yval+sum);
        int z = add(x, y);
        sum += z;
    }
    return sum;
}

int main(int , char* argv[]) {
    int result = work(*argv[1], *argv[2]);
    return result;
}

로 컴파일하면 -Os이 프로그램을 실행하는 데 0.38 초가 걸리 -O2거나 or 로 컴파일되면 0.44 초가 걸립니다 -O3. 이 시간은 일관되고 실질적으로 소음이 없습니다 (gcc 4.7.2, x86_64 GNU / Linux, Intel Core i5-3320M).

(업데이트 : 모든 어셈블리 코드를 GitHub 로 옮겼 습니다. 포스트가 부풀어 오르고 fno-align-*플래그가 동일한 효과 를 가지므로 질문에 거의 가치가 없습니다 .)

여기에 발생 조립체 -Os-O2.

불행하게도, 조립에 대한 이해는 매우 내가 아무 생각이 없다, 그래서 제한 여부를 나는 다음 한 정확 무엇입니까 : 나는에 대한 어셈블리를 잡고 -O2및 위해 어셈블리로 모든 차이를 통합 -Os 제외.p2align 라인, 결과 여기 . 이 코드는 여전히 0.38s로 실행되며 유일한 차이점은 .p2align 것입니다.

올바르게 추측하면 스택 정렬을위한 패딩입니다. GCC 패드가 NOP와 함께 작동 하는 이유는 무엇입니까? 코드가 더 빨리 실행되기를 희망하지만 내 경우에는이 최적화가 역효과를 낳았습니다.

이 경우 범인이 패딩입니까? 왜 그리고 어떻게?

노이즈가 거의 발생하여 타이밍 미세 최적화가 불가능합니다.

C 또는 C ++ 소스 코드에서 미세 최적화 (스택 정렬과 관련이 없음)를 수행 할 때 실수로 운이 좋지 않은 정렬 / 불운 정렬이 방해받지 않도록하려면 어떻게해야합니까?


최신 정보:

다음 파스칼 Cuoq의 대답 나는 정렬과 조금 만지작 거렸다. -O2 -fno-align-functions -fno-align-loopsgcc 로 전달 하면 모든 .p2align어셈블리에서 사라지고 생성 된 실행 파일은 0.38 초 안에 실행됩니다. gcc 문서 에 따르면 :

-Os는 모든 -O2 최적화를 활성화하지만 [O] -Os는 다음 최적화 플래그를 비활성화합니다.

  -falign-functions  -falign-jumps  -falign-loops
  -falign-labels  -freorder-blocks  -freorder-blocks-and-partition
  -fprefetch-loop-arrays

따라서 (정렬) 정렬 문제처럼 보입니다.

Marat Dukhan의 답변-march=native 에서 제안한대로 여전히 회의적 입니다. 나는 이것이 (미스) 정렬 문제를 방해하는 것이 아니라고 확신하지 못한다. 내 컴퓨터에는 전혀 영향을 미치지 않습니다. (그럼에도 불구하고 나는 그의 대답을 찬성했다.)


업데이트 2 :

-Os사진을 찍을 수 있습니다 . 다음 시간은

  • -O2 -fno-omit-frame-pointer 0.37 초

  • -O2 -fno-align-functions -fno-align-loops 0.37 초

  • -S -O20.37 초 add()후에 수동으로 어셈블리 이동work()

  • -O2 0.44 초

add()전화 사이트와 의 거리가 중요합니다. 나는 시도 perf했지만 출력 perf statperf report나에게는 거의 이해가되지 않습니다. 그러나 나는 단 하나의 일관된 결과를 얻을 수있었습니다.

-O2:

 602,312,864 stalled-cycles-frontend   #    0.00% frontend cycles idle
       3,318 cache-misses
 0.432703993 seconds time elapsed
 [...]
 81.23%  a.out  a.out              [.] work(int, int)
 18.50%  a.out  a.out              [.] add(int const&, int const&) [clone .isra.0]
 [...]
       ¦   __attribute__((noinline))
       ¦   static int add(const int& x, const int& y) {
       ¦       return x + y;
100.00 ¦     lea    (%rdi,%rsi,1),%eax
       ¦   }
       ¦   ? retq
[...]
       ¦            int z = add(x, y);
  1.93 ¦    ? callq  add(int const&, int const&) [clone .isra.0]
       ¦            sum += z;
 79.79 ¦      add    %eax,%ebx

의 경우 fno-align-*:

 604,072,552 stalled-cycles-frontend   #    0.00% frontend cycles idle
       9,508 cache-misses
 0.375681928 seconds time elapsed
 [...]
 82.58%  a.out  a.out              [.] work(int, int)
 16.83%  a.out  a.out              [.] add(int const&, int const&) [clone .isra.0]
 [...]
       ¦   __attribute__((noinline))
       ¦   static int add(const int& x, const int& y) {
       ¦       return x + y;
 51.59 ¦     lea    (%rdi,%rsi,1),%eax
       ¦   }
[...]
       ¦    __attribute__((noinline))
       ¦    static int work(int xval, int yval) {
       ¦        int sum(0);
       ¦        for (int i=0; i<LOOP_BOUND; ++i) {
       ¦            int x(xval+sum);
  8.20 ¦      lea    0x0(%r13,%rbx,1),%edi
       ¦            int y(yval+sum);
       ¦            int z = add(x, y);
 35.34 ¦    ? callq  add(int const&, int const&) [clone .isra.0]
       ¦            sum += z;
 39.48 ¦      add    %eax,%ebx
       ¦    }

의 경우 -fno-omit-frame-pointer:

 404,625,639 stalled-cycles-frontend   #    0.00% frontend cycles idle
      10,514 cache-misses
 0.375445137 seconds time elapsed
 [...]
 75.35%  a.out  a.out              [.] add(int const&, int const&) [clone .isra.0]                                                                                     ¦
 24.46%  a.out  a.out              [.] work(int, int)
 [...]
       ¦   __attribute__((noinline))
       ¦   static int add(const int& x, const int& y) {
 18.67 ¦     push   %rbp
       ¦       return x + y;
 18.49 ¦     lea    (%rdi,%rsi,1),%eax
       ¦   const int LOOP_BOUND = 200000000;
       ¦
       ¦   __attribute__((noinline))
       ¦   static int add(const int& x, const int& y) {
       ¦     mov    %rsp,%rbp
       ¦       return x + y;
       ¦   }
 12.71 ¦     pop    %rbp
       ¦   ? retq
 [...]
       ¦            int z = add(x, y);
       ¦    ? callq  add(int const&, int const&) [clone .isra.0]
       ¦            sum += z;
 29.83 ¦      add    %eax,%ebx

add()느린 경우에 전화를 걸고있는 것 같습니다 .

나는 내 컴퓨터에서 뱉어 낼 수있는 모든 것을 조사했다 perf -e. 위에 주어진 통계 만이 아닙니다.

동일한 실행 파일의 stalled-cycles-frontend경우 실행 시간과 선형 상관 관계가 표시됩니다. 나는 그토록 분명하게 상관되는 어떤 것도 눈치 채지 못했습니다. ( stalled-cycles-frontend다른 실행 파일을 비교 하는 것은 의미가 없습니다.)

나는 첫 번째 의견으로 나온 캐시 미스를 포함시켰다. perf위에서 언급 한 것뿐만 아니라 내 컴퓨터에서 측정 할 수있는 모든 캐시 누락을 검사했습니다 . 캐시 미스는 매우 시끄럽고 실행 시간과 거의 또는 전혀 상관이 없습니다.


36
맹목적인 추측 : 이것이 캐시 미스 일 수 있습니까?

@ H2CO3 그것은 저의 첫 번째 생각이기도했지만 OP의 질문을 깊이 이해하고 읽지 않고서도 의견을 게시 할만큼 권장되지 않았습니다.
πάντα ῥεῖ

2
@ g-makulik 그렇기 때문에 그것이 "맹목적인 추측"이라고 경고했다; : P

3
흥미로운 데이터 포인트 : OS X에서 clang으로 이것을 컴파일 할 때 -O3 또는 -Ofast가 -Os보다 약 1.5 배 빠릅니다. (gcc로는 재현을 시도하지 않았습니다.)
Rob Napier

2
같은 코드입니다. .L3의 주소를 자세히 살펴보면 잘못 정렬 된 분기 대상이 비쌉니다.
Hans Passant

답변:


505

기본적으로 컴파일러는 "평균"프로세서를 최적화합니다. 프로세서마다 다른 명령어 시퀀스가 ​​선호되므로 컴파일러 최적화를 -O2통해 평균 프로세서에 유리하지만 특정 프로세서의 성능이 저하 될 수 있습니다 (및 동일 -Os). 다른 프로세서에서 동일한 예제를 시도하면 일부 프로세서에서 이점을 얻는 -O2반면 다른 프로세서에서는 -Os최적화에 더 유리 하다는 것을 알 수 있습니다.

다음은 time ./test 0 0여러 프로세서에 대한 결과입니다 (보고 된 사용자 시간).

Processor (System-on-Chip)             Compiler   Time (-O2)  Time (-Os)  Fastest
AMD Opteron 8350                       gcc-4.8.1    0.704s      0.896s      -O2
AMD FX-6300                            gcc-4.8.1    0.392s      0.340s      -Os
AMD E2-1800                            gcc-4.7.2    0.740s      0.832s      -O2
Intel Xeon E5405                       gcc-4.8.1    0.603s      0.804s      -O2
Intel Xeon E5-2603                     gcc-4.4.7    1.121s      1.122s       -
Intel Core i3-3217U                    gcc-4.6.4    0.709s      0.709s       -
Intel Core i3-3217U                    gcc-4.7.3    0.708s      0.822s      -O2
Intel Core i3-3217U                    gcc-4.8.1    0.708s      0.944s      -O2
Intel Core i7-4770K                    gcc-4.8.1    0.296s      0.288s      -Os
Intel Atom 330                         gcc-4.8.1    2.003s      2.007s      -O2
ARM 1176JZF-S (Broadcom BCM2835)       gcc-4.6.3    3.470s      3.480s      -O2
ARM Cortex-A8 (TI OMAP DM3730)         gcc-4.6.3    2.727s      2.727s       -
ARM Cortex-A9 (TI OMAP 4460)           gcc-4.6.3    1.648s      1.648s       -
ARM Cortex-A9 (Samsung Exynos 4412)    gcc-4.6.3    1.250s      1.250s       -
ARM Cortex-A15 (Samsung Exynos 5250)   gcc-4.7.2    0.700s      0.700s       -
Qualcomm Snapdragon APQ8060A           gcc-4.8       1.53s       1.52s      -Os

경우 gcc에 따라 옵션 -mtune=native또는을 사용하여 특정 프로세서에 대한 최적화를 요청 하여 불리한 최적화의 영향을 완화 할 수 있습니다 -march=native.

Processor            Compiler   Time (-O2 -mtune=native) Time (-Os -mtune=native)
AMD FX-6300          gcc-4.8.1         0.340s                   0.340s
AMD E2-1800          gcc-4.7.2         0.740s                   0.832s
Intel Xeon E5405     gcc-4.8.1         0.603s                   0.803s
Intel Core i7-4770K  gcc-4.8.1         0.296s                   0.288s

업데이트 : 아이비 브릿지 기반 코어는 세 가지 버전의 I3 gcc( 4.6.4, 4.7.3,와 4.8.1크게 다른 성능) 생산 바이너리를하지만, 어셈블리 코드는 미묘한 변화가있다. 지금까지이 사실에 대한 설명이 없습니다.

조립 gcc-4.6.4 -Os(0.709 초 내에 실행) :

00000000004004d2 <_ZL3addRKiS0_.isra.0>:
  4004d2:       8d 04 37                lea    eax,[rdi+rsi*1]
  4004d5:       c3                      ret

00000000004004d6 <_ZL4workii>:
  4004d6:       41 55                   push   r13
  4004d8:       41 89 fd                mov    r13d,edi
  4004db:       41 54                   push   r12
  4004dd:       41 89 f4                mov    r12d,esi
  4004e0:       55                      push   rbp
  4004e1:       bd 00 c2 eb 0b          mov    ebp,0xbebc200
  4004e6:       53                      push   rbx
  4004e7:       31 db                   xor    ebx,ebx
  4004e9:       41 8d 34 1c             lea    esi,[r12+rbx*1]
  4004ed:       41 8d 7c 1d 00          lea    edi,[r13+rbx*1+0x0]
  4004f2:       e8 db ff ff ff          call   4004d2 <_ZL3addRKiS0_.isra.0>
  4004f7:       01 c3                   add    ebx,eax
  4004f9:       ff cd                   dec    ebp
  4004fb:       75 ec                   jne    4004e9 <_ZL4workii+0x13>
  4004fd:       89 d8                   mov    eax,ebx
  4004ff:       5b                      pop    rbx
  400500:       5d                      pop    rbp
  400501:       41 5c                   pop    r12
  400503:       41 5d                   pop    r13
  400505:       c3                      ret

조립 gcc-4.7.3 -Os(0.822 초에 실행) :

00000000004004fa <_ZL3addRKiS0_.isra.0>:
  4004fa:       8d 04 37                lea    eax,[rdi+rsi*1]
  4004fd:       c3                      ret

00000000004004fe <_ZL4workii>:
  4004fe:       41 55                   push   r13
  400500:       41 89 f5                mov    r13d,esi
  400503:       41 54                   push   r12
  400505:       41 89 fc                mov    r12d,edi
  400508:       55                      push   rbp
  400509:       bd 00 c2 eb 0b          mov    ebp,0xbebc200
  40050e:       53                      push   rbx
  40050f:       31 db                   xor    ebx,ebx
  400511:       41 8d 74 1d 00          lea    esi,[r13+rbx*1+0x0]
  400516:       41 8d 3c 1c             lea    edi,[r12+rbx*1]
  40051a:       e8 db ff ff ff          call   4004fa <_ZL3addRKiS0_.isra.0>
  40051f:       01 c3                   add    ebx,eax
  400521:       ff cd                   dec    ebp
  400523:       75 ec                   jne    400511 <_ZL4workii+0x13>
  400525:       89 d8                   mov    eax,ebx
  400527:       5b                      pop    rbx
  400528:       5d                      pop    rbp
  400529:       41 5c                   pop    r12
  40052b:       41 5d                   pop    r13
  40052d:       c3                      ret

어셈블리 gcc-4.8.1 -Os(0.994 초 내에 실행) :

00000000004004fd <_ZL3addRKiS0_.isra.0>:
  4004fd:       8d 04 37                lea    eax,[rdi+rsi*1]
  400500:       c3                      ret

0000000000400501 <_ZL4workii>:
  400501:       41 55                   push   r13
  400503:       41 89 f5                mov    r13d,esi
  400506:       41 54                   push   r12
  400508:       41 89 fc                mov    r12d,edi
  40050b:       55                      push   rbp
  40050c:       bd 00 c2 eb 0b          mov    ebp,0xbebc200
  400511:       53                      push   rbx
  400512:       31 db                   xor    ebx,ebx
  400514:       41 8d 74 1d 00          lea    esi,[r13+rbx*1+0x0]
  400519:       41 8d 3c 1c             lea    edi,[r12+rbx*1]
  40051d:       e8 db ff ff ff          call   4004fd <_ZL3addRKiS0_.isra.0>
  400522:       01 c3                   add    ebx,eax
  400524:       ff cd                   dec    ebp
  400526:       75 ec                   jne    400514 <_ZL4workii+0x13>
  400528:       89 d8                   mov    eax,ebx
  40052a:       5b                      pop    rbx
  40052b:       5d                      pop    rbp
  40052c:       41 5c                   pop    r12
  40052e:       41 5d                   pop    r13
  400530:       c3                      ret

186
분명히하기 위해 : 실제로 12 가지 플랫폼에서 OP 코드의 성능을 측정하고 측정 했습니까? (단지 생각하면 +1)
anatolyg

194
@anatolyg 예, 했어요! (그리고 더 빨리 추가 될 예정입니다)
Marat Dukhan

43
과연. 다른 CPU에 대한 이론화뿐만 아니라 실제로 그것을 입증 하는 또 다른 +1 . 속도에 관한 모든 대답에서 볼 수있는 것은 아닙니다. 이 테스트는 동일한 OS에서 실행됩니까? (이것이 가능할 수도 있기 때문에 결과가 왜곡 될 수 있습니다 ...)
usr2564301

7
@Ali AMD-FX 6300에서 -O2 -fno-align-functions -fno-align-loops시간이 0.340s에 걸리므로 정렬하여 설명 할 수 있습니다. 그러나 최적의 정렬은 프로세서에 따라 다릅니다. 일부 프로세서는 정렬 된 루프 및 기능을 선호합니다.
Marat Dukhan

13
@Jongware 나는 OS가 결과에 어떻게 큰 영향을 미치는지 알지 못한다. 루프는 시스템 호출을하지 않습니다.
Ali

186

동료가 내 질문에 대한 그럴듯한 답변을 찾도록 도와주었습니다. 그는 256 바이트 경계의 중요성을 알았습니다. 그는 여기에 등록되어 있지 않으며 나에게 직접 답을 게시하고 모든 명성을 얻도록 격려했다.


짧은 답변:

이 경우 범인이 패딩입니까? 왜 그리고 어떻게?

그것은 모두 정렬로 귀결됩니다. 정렬은 성능에 큰 영향을 줄 수 -falign-*있으므로 처음에 플래그 가 있습니다 .

gcc 개발자에게 (거짓?) 버그 보고서를 제출 했습니다 . 기본 동작은 " 기본적으로 루프를 8 바이트로 정렬하지만 10 바이트 이상을 채울 필요가없는 경우 16 바이트로 정렬하려고합니다." 분명히이 기본값은이 특별한 경우와 내 컴퓨터에서 최선의 선택이 아닙니다. Clang 3.4 (트렁크) -O3는 적절한 정렬을 수행하며 생성 된 코드에는이 이상한 동작이 표시되지 않습니다.

물론, 부적절한 정렬이 이루어지면 상황이 악화됩니다. 불필요하거나 잘못된 정렬은 이유없이 바이트를 소비하고 잠재적으로 캐시 미스 등을 증가시킵니다.

노이즈가 거의 발생하여 타이밍 미세 최적화가 불가능합니다.

C 또는 C ++ 소스 코드에서 미세 최적화 (스택 정렬과 관련이 없음)를 수행 할 때 이러한 우발적 인 운 / 불운 정렬이 방해받지 않도록하려면 어떻게해야합니까?

gcc에게 올바른 정렬을하도록 지시하면됩니다.

g++ -O2 -falign-functions=16 -falign-loops=16


긴 대답 :

다음과 같은 경우 코드가 느리게 실행됩니다.

  • XX바이트 경계 삭감 add()중간에 ( XX기계 의존).

  • 호출이 바이트 경계 add()를 뛰어 넘어야 XX하고 대상이 정렬되지 않은 경우

  • add()정렬되지 않은 경우 .

  • 루프가 정렬되지 않은 경우

처음 2 개는 Marat Dukhan이 친절하게 게시 한 코드와 결과에서 아름답게 보입니다 . 이 경우 gcc-4.8.1 -Os(0.994 초에 실행) :

00000000004004fd <_ZL3addRKiS0_.isra.0>:
  4004fd:       8d 04 37                lea    eax,[rdi+rsi*1]
  400500:       c3   

256 바이트 경계 add()는 중간에서 바로 자르고 add()루프도 정렬 되지 않습니다. 놀랍게도, 이것은 가장 느린 경우입니다!

gcc-4.7.3 -Os0.822 초로 실행되는 경우 256 바이트 경계는 콜드 섹션으로 만 절단됩니다 (그러나 루프도 add()절단 되지도 않습니다 ).

00000000004004fa <_ZL3addRKiS0_.isra.0>:
  4004fa:       8d 04 37                lea    eax,[rdi+rsi*1]
  4004fd:       c3                      ret

[...]

  40051a:       e8 db ff ff ff          call   4004fa <_ZL3addRKiS0_.isra.0>

정렬 된 것이 없으며 add()256 바이트 경계를 뛰어 넘어야합니다. 이 코드는 두 번째로 느립니다.

gcc-4.6.4 -Os(0.709 초에 실행) 경우 정렬 된 것이 없지만 호출 add()은 256 바이트 경계를 뛰어 넘을 필요가 없으며 대상은 정확히 32 바이트 떨어져 있습니다.

  4004f2:       e8 db ff ff ff          call   4004d2 <_ZL3addRKiS0_.isra.0>
  4004f7:       01 c3                   add    ebx,eax
  4004f9:       ff cd                   dec    ebp
  4004fb:       75 ec                   jne    4004e9 <_ZL4workii+0x13>

이것은 세 가지 중 가장 빠릅니다. 256 바이트 경계가 그의 컴퓨터에서 왜 특별합니까? 나는 그것을 알아 내기 위해 그에게 맡길 것입니다. 나는 그런 프로세서가 없습니다.

이제 내 컴퓨터 에서이 256 바이트 경계 효과를 얻지 못했습니다. 기능과 루프 정렬 만 내 컴퓨터에서 시작됩니다. 내가 통과 g++ -O2 -falign-functions=16 -falign-loops=16하면 모든 것이 정상으로 돌아옵니다. 항상 가장 빠른 사례가 표시되고 시간은 -fno-omit-frame-pointer더 이상 깃발에 민감하지 않습니다. 나는 통과 할 수 g++ -O2 -falign-functions=32 -falign-loops=32또는 16의 배수, 코드는 그 중 하나에 민감하지 않습니다.

나는 2009 년에 gcc (적어도 내 프로젝트와 컴퓨터에서)가 속도 (-O2 또는 -O3) 대신 크기 (-Os)를 최적화하면 눈에 띄게 더 빠른 코드를 생성하는 경향이 있으며 궁금해했습니다. 그 이후로.

아마도이 예제의 것과 마찬가지로 정렬에 민감한 핫스팟이 있다고 설명합니다. 플래그 -Os대신 엉망으로 ( -O2) 대신 전달 하여 핫스팟이 우연히 운이 좋은 방식으로 정렬되어 코드가 빨라졌습니다. 크기를 최적화하는 것과는 아무런 관련이 없었습니다. 핫스팟이 더 잘 정렬되었다는 것은 우연히 우연이었습니다. 이제부터는 프로젝트에 대한 정렬 효과를 확인합니다.

아, 그리고 한가지 더. 예제에 표시된 것과 같은 핫스팟은 어떻게 발생할 수 있습니까? 그런 작은 함수의 인라인은 어떻게 add()실패 할 수 있습니까?

이걸 고려하세요:

// add.cpp
int add(const int& x, const int& y) {
    return x + y;
}

별도의 파일로 :

// main.cpp
int add(const int& x, const int& y);

const int LOOP_BOUND = 200000000;

__attribute__((noinline))
static int work(int xval, int yval) {
    int sum(0);
    for (int i=0; i<LOOP_BOUND; ++i) {
        int x(xval+sum);
        int y(yval+sum);
        int z = add(x, y);
        sum += z;
    }
    return sum;
}

int main(int , char* argv[]) {
    int result = work(*argv[1], *argv[2]);
    return result;
}

다음과 같이 컴파일됩니다 g++ -O2 add.cpp main.cpp.

      gcc는 인라인하지 않습니다 add()!

그게 전부입니다. OP의 핫스팟을 의도 치 않게 생성하기가 쉽습니다. 물론 그것은 부분적으로 내 잘못입니다. gcc는 훌륭한 컴파일러입니다. 위와 같이 컴파일하면 : g++ -O2 -flto add.cpp main.cpp즉, 링크 시간 최적화를 수행하면 코드가 0.19 초에 실행됩니다!

OP에서는 인라인이 인위적으로 비활성화되어 있으므로 OP의 코드가 2 배 느립니다.


19
와우 .. 이것은 벤치마킹 이상을 피하기 위해 내가 보통하는 일을 넘어선 것입니다.
Mysticial

@Ali 컴파일러가 보이지 않는 것을 어떻게 인라인 할 수 있습니까? 아마도 inline헤더에 + 함수 정의를 사용하는 이유 일 것입니다 . gcc에서 lto의 성숙도가 확실하지 않습니다. 적어도 mingw에서 그것에 대한 나의 경험은 히트 또는 누락입니다.
greatwolf

7
나는 몇 년 전에 상당히 큰 응용 프로그램 (perl, Spice 등)을 실행하면서 전체 크기의 이미지를 다른 크기의 Linux 환경을 사용하여 한 번에 한 바이트 씩 이동시키는 것에 관한 기사를 가지고있는 것이 ACM의 통신이라고 생각합니다. 15 % 정도의 일반적인 분산을 기억합니다. 이들 외부 변수의 정렬을 고려하지 않았기 때문에 많은 벤치 마크 결과가 쓸모가 없다고 요약했습니다.
Gene

1
특히 -flto. 당신이 전에 그것을 사용해 본 적이 없다면, 그것은 경험에서 말하면 꽤 혁명적입니다 :)
underscore_d

2
이 동영상은 얼라인먼트가 성능에 미치는 영향과 프로파일 링 방법에 대해 설명하는 환상적인 동영상입니다. youtube.com/watch?time_continue=1&v=r-TLSBdHe1A
Zhro

73

나는이 포스트-허용을 추가하여 큰 프로그램을 포함하여 프로그램의 전반적인 성능에 대한 정렬 효과가 연구되었음을 지적했다. 예를 들어, 이 기사 (그리고 CACM에도이 버전이 등장했다고 생각합니다)는 링크 순서와 OS 환경 크기 변경만으로 성능을 크게 전환하는 데 충분한 방법을 보여줍니다. 그것들은 이것을 "핫 루프"의 정렬에 기인합니다.

이 백서는 "눈에 띄지 않고 잘못된 데이터를 생성합니다!" 프로그램 실행 환경에서 거의 제어 할 수없는 차이로 인한 우연한 실험적 편향은 아마도 많은 벤치 마크 결과를 의미가 없게 만든다고 말합니다.

같은 관찰에서 다른 각도로 직면하고 있다고 생각합니다.

성능에 중요한 코드의 경우 설치 또는 런타임시 환경을 평가하고 다르게 최적화 된 주요 루틴 버전 중에서 가장 적합한 로컬 시스템을 선택하는 시스템에있어 이는 매우 좋은 주장입니다.


33

나는 당신이 한 것과 같은 결과를 얻을 수 있다고 생각합니다.

-O2에 대한 어셈블리를 잡고 .p2align 행을 제외하고 모든 차이점을 -O에 대한 어셈블리에 병합했습니다.

…를 사용하여 -O2 -falign-functions=1 -falign-jumps=1 -falign-loops=1 -falign-labels=1. 나는 이러한 옵션으로 모든 것을 컴파일 해왔으며 -O2, 15 년 동안 측정하려고 할 때마다 평범한 것보다 빠릅니다 .

또한 완전히 다른 상황 (다른 컴파일러 포함) 의 경우 상황이 비슷하다는 것을 알았 습니다.“속도보다는 코드 크기를 최적화하는”옵션이 코드 크기와 속도를 최적화합니다.

올바르게 추측하면 스택 정렬을위한 패딩입니다.

아니요, 이것은 스택과 아무런 관련이 없으며 기본적으로 생성되는 NOP이며 -falign-* = 1 옵션은 코드 정렬을위한 것입니다.

GCC 패드가 NOP와 함께 작동하는 이유는 무엇입니까? 코드가 더 빨리 실행되기를 희망하지만 내 경우에는이 최적화가 역효과를 낳았습니다.

이 경우 범인이 패딩입니까? 왜 그리고 어떻게?

패딩이 범인 일 가능성이 높습니다. 패딩이 필요한 것으로 느껴지고 일부 경우에 유용한 이유는 코드가 일반적으로 16 바이트 라인으로 페치되기 때문입니다 ( 세부 사항은 프로세서 모델에 따라 다르므로 Agner Fog의 최적화 리소스 참조 ). 16 바이트 경계에서 함수, 루프 또는 레이블을 정렬하면 함수 또는 루프를 포함하기 위해 하나의 줄이 더 적게 필요할 가능성이 통계적으로 증가합니다. 이러한 NOP는 코드 밀도를 줄이고 따라서 캐시 효율성을 저하시키기 때문에 분명히 역효과를냅니다. 루프와 레이블의 경우, NOP는 한 번만 실행해야 할 수도 있습니다 (실행이 점프와는 반대로 루프 / 라벨에 정상적으로 실행되는 경우).


재밌는 점은 다음 -O2 -fno-omit-frame-pointer과 같습니다 -Os. 업데이트 된 질문을 확인하십시오.
Ali

11

프로그램이 CODE L1 캐시에 의해 제한되는 경우 크기 최적화가 갑자기 지불되기 시작합니다.

마지막으로 확인했을 때 컴파일러는 모든 경우에 이것을 알아낼만큼 똑똑하지 않습니다.

귀하의 경우 -O3은 아마도 두 개의 캐시 라인에 충분한 코드를 생성하지만 -Os는 하나의 캐시 라인에 적합합니다.


1
align = 매개 변수가 캐시 라인의 크기와 관련이 있다고 생각하십니까?
Joshua

더 이상 신경 쓰지 않습니다. 내 컴퓨터에는 보이지 않습니다. -falign-*=16플래그 를 전달하면 모든 것이 정상으로 돌아가고 모든 것이 일관되게 작동합니다. 내가 우려하는 한이 질문은 해결되었습니다.
Ali

7

나는 결코이 분야의 전문가는 아니지만, 현대 프로세서는 지점 예측과 관련하여 매우 민감하다는 것을 기억하는 것 같습니다 . 분기를 예측하는 데 사용되는 알고리즘은 대상의 거리와 방향을 포함하여 코드의 여러 속성을 기반으로합니다 (또는 적어도 어셈블러 코드를 작성한 날에 돌아 왔습니다).

떠오르는 시나리오는 작은 루프입니다. 지점이 뒤로 가고 거리가 너무 멀지 않은 경우 모든 작은 루프가이 방식으로 수행되므로 지점 예측이이 경우에 최적화되었습니다. 생성 된 코드 의 위치 addwork생성 된 코드 의 위치를 ​​바꾸 거나 두 위치가 약간 변경 될 때도 동일한 규칙이 적용됩니다.

즉, 나는 그것을 확인하는 방법을 모른다. 나는 이것이 당신이 조사하고 싶을 수도 있음을 당신에게 알리고 싶었다.


감사. 나는 그것을 가지고 놀았다 : 나는 교환 add()하고 통과 work()하면 속도를 올린다 -O2. 다른 모든 경우에는 스와핑하여 코드가 상당히 느려집니다. 주말 동안, 나는 또한 분기 예측 / 잘못된 예측 통계를 분석 했으며이 perf이상한 행동을 설명 할 수있는 것은 발견하지 못했습니다. 유일한 일관된 결과는 느린 경우 에 루프에서 호출 한 직후 줄에 perf100.0 인치 add()및 큰 값을 보고 한다는 것입니다 add(). add()느린 경우에는 우리가 어떤 이유로 멈추고있는 것처럼 보이지만 빠른 실행에서는 그렇지 않습니다.
Ali

내 컴퓨터 중 하나에 Intel의 VTune을 설치하고 프로파일 링을 할 생각입니다. perf제한된 수의 것을 지원합니다. 아마도 Intel의 물건은 자체 프로세서에서 조금 더 편리합니다.
Ali
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.