programing

<< = >보다 빠릅니까?

randomtip 2022. 8. 29. 21:52
반응형

<< = >보다 빠릅니까?

이는?if (a < 901) if (a <= 900)

이 간단한 예시와 달리 루프 복합 코드에 약간의 성능 변화가 있습니다.그게 사실일 경우를 대비해서 생성된 기계 코드와 관련이 있는 것 같아요

아니요, 대부분의 아키텍처에서 더 빠르지 않습니다.지정하지 않았습니다만, x86 에서는, 모든 통합 비교는 통상, 다음의 2개의 머신 명령으로 실장됩니다.

  • A test ★★★★★★★★★★★★★★★★★」cmp 「」, 「」를 합니다.EFLAGS
  • 비교 유형(및 코드 레이아웃)에 따라 (점프) 명령:
  • jne --> - 지지 、 같 ->> -- >ZF = 0
  • jz - 0이면 점프합니다. - 0이면 점프합니다.ZF = 1
  • jg --> - 더 크면 점프 -->ZF = 0 and SF = OF
  • (등)

(간단히 편집) 컴파일 대상$ gcc -m32 -S -masm=intel test.c

    if (a < b) {
        // Do something 1
    }

컴파일 대상:

    mov     eax, DWORD PTR [esp+24]      ; a
    cmp     eax, DWORD PTR [esp+28]      ; b
    jge     .L2                          ; jump if a is >= b
    ; Do something 1
.L2:

그리고.

    if (a <= b) {
        // Do something 2
    }

컴파일 대상:

    mov     eax, DWORD PTR [esp+24]      ; a
    cmp     eax, DWORD PTR [esp+28]      ; b
    jg      .L5                          ; jump if a is > b
    ; Do something 2
.L5:

그래서 이 둘의 유일한 차이점은jg와 a의 대 a의 비교jge. 둘 다 같은.두 사람은 같은 시간이 걸릴 것이다.


점프 명령이 다르면 같은 시간이 걸린다는 것을 나타내는 것은 아무것도 없다는 지적에 대응하고 싶습니다.이것은 대답하기 조금 어렵지만, 제가 드릴 수 있는 것은 다음과 같습니다.인텔 인스톨 세트 레퍼런스에서는, 이러한 모든 것이 공통의 1개의 명령으로 그룹화되어 있습니다.Jcc(조건이 충족되면 점프합니다).부록 C의 Optimization Reference Manual(최적화 참조 매뉴얼)에 동일한 그룹이 함께 작성되어 있습니다.레이텐시와 스루풋

[Latency] : 명령어를 형성하는 모든 μops의 실행을 완료하기 위해 실행 코어가 필요한 클럭사이클

throughput : 문제 포트가 동일한 명령을 다시 수신할 수 있을 때까지 대기하는 데 필요한 클럭사이클 수많은 명령에서 명령의 throughput은 지연 시간보다 훨씬 적을 수 있습니다.

의 값Jcc과 같습니다

      Latency   Throughput
Jcc     N/A        0.5

Jcc:

  1. 조건부 점프 지침은 분지의 예측 가능성을 개선하기 위해 섹션 3.4.1 "가지 예측 최적화"의 권고에 기초해야 한다.예측되면 지연 합니다.jcc사실상 제로입니다.

인텔 도 이 문서를 .Jcc하다

에 대해 , 이 명령어에서는 다른 AND/할 수 .EFLAGS이치노따라서 2비트를 테스트하는 명령어가 1비트만 테스트하는 명령보다 시간이 더 걸리거나 더 짧을 필요는 없습니다(게이트 전파 지연 무시, 클럭 주기보다 훨씬 짧습니다).


편집: 부동 소수점

입니다만, 「x87」이 사용되고 있습니다).doubleint

        fld     QWORD PTR [esp+32]
        fld     QWORD PTR [esp+40]
        fucomip st, st(1)              ; Compare ST(0) and ST(1), and set CF, PF, ZF in EFLAGS
        fstp    st(0)
        seta    al                     ; Set al if above (CF=0 and ZF=0).
        test    al, al
        je      .L2
        ; Do something 1
.L2:

        fld     QWORD PTR [esp+32]
        fld     QWORD PTR [esp+40]
        fucomip st, st(1)              ; (same thing as above)
        fstp    st(0)
        setae   al                     ; Set al if above or equal (CF=0).
        test    al, al
        je      .L5
        ; Do something 2
.L5:
        leave
        ret

역사적으로 (1980년대와 1990년대 초에 대해 이야기하고 있습니다) 이것이 사실인 아키텍처가 몇 가지 있었습니다.근본적인 문제는 정수 비교가 본질적으로 정수 감산을 통해 구현된다는 것입니다.이로 인해 다음과 같은 경우가 발생합니다.

Comparison     Subtraction
----------     -----------
A < B      --> A - B < 0
A = B      --> A - B = 0
A > B      --> A - B > 0

그럼 ,, 그, 그, 그.A < B뺄셈은 손으로 더하고 뺄 때 가지고 다니거나 빌릴 때처럼 높은 비트를 빌려야 정확한 뺄셈을 할 수 있습니다.이 "빌린" 비트는 보통 캐리어 비트라고 불리며 분기 명령으로 테스트할 수 있습니다.감산이 동등함을 의미하는 동일한 0일 경우 제로 비트라고 불리는 두 번째 비트가 설정됩니다.

보통 적어도 2개의 조건부 분기 명령이 있었습니다.하나는 캐리 비트로 분기하고 다른 하나는 제로 비트로 분기합니다.

이제 문제의 핵심을 파악하려면 이전 표를 확장하여 캐리 및 제로비트 결과를 포함하도록 하겠습니다.

Comparison     Subtraction  Carry Bit  Zero Bit
----------     -----------  ---------  --------
A < B      --> A - B < 0    0          0
A = B      --> A - B = 0    1          1
A > B      --> A - B > 0    1          0

" "의 :A < B하나의 명령으로 실행할 수 있습니다.이 경우만 캐리 비트가 클리어되기 때문입니다.즉,

;; Implementation of "if (A < B) goto address;"
cmp  A, B          ;; compare A to B
bcz  address       ;; Branch if Carry is Zero to the new address

그러나 동등하지 않은 비교를 하려면 제로 플래그를 추가로 체크하여 균등한 경우를 파악해야 합니다.

;; Implementation of "if (A <= B) goto address;"
cmp A, B           ;; compare A to B
bcz address        ;; branch if A < B
bzs address        ;; also, Branch if the Zero bit is Set

따라서 일부 기계에서는 "미만" 비교를 사용하면 하나의 기계 명령이 저장될 수 있습니다.이것은 서브 메가헤르츠 프로세서 속도와 1:1의 CPU 대 메모리 속도 비율의 시대에는 관련이 있었지만, 오늘날에는 거의 관련이 없습니다.

내부 정수형이라고 가정할 때 한쪽이 다른 쪽보다 더 빠를 수 있는 방법은 없습니다.그들은 분명히 의미론적으로 동일하다.둘 다 컴파일러에게 정확히 같은 일을 하도록 요구합니다.끔찍하게 망가진 컴파일러만이 이들 중 하나에 대해 열등한 코드를 생성할 수 있습니다.

만약 어떤 플랫폼이 있다면< 빨랐다<=단순한 정수 타입의 경우 컴파일러는 항상 변환해야 합니다.<=로로 합니다.<.그렇지 않은 컴파일러는 (그 플랫폼에서는) 나쁜 컴파일러일 뿐입니다.

TL;DR 응답

, 및는, 「 」, 「 」, 「 」, 「 」, 「 」< 않다<=.

풀답변

다른 답변은 x86 아키텍처에 집중되어 있으며, 생성된 코드에 대해 구체적으로 코멘트할 만큼 ARM 아키텍처(예: 어셈블러)에 대해서는 잘 모르지만, 이것은 매우 아키텍처에 특화되어 있어 최적화에 대한 반(反) 최적화일 가능성이 높은 마이크로 최적화의 예입니다.n.

따라서, 나는 이러한 종류의 미세 최적화가 베스트 소프트웨어 엔지니어링 프랙티스가 아닌 화물 컬트 프로그래밍의 한 예라고 제안한다.

반례

이것이 최적화인 아키텍처도 있을 수 있지만, 그 반대일 가능성이 있는 아키텍처는 적어도 1개 이상 알고 있습니다.기존의 트랜스푸터 아키텍처는 동등하거나 그 이상대한 기계 코드 명령만 가지고 있었기 때문에 모든 비교는 이러한 원시 요소로부터 구축되어야 했습니다.

그 후에도 거의 모든 경우에 컴파일러는 실제로 비교가 다른 어떤 것보다도 유리하지 않은 방식으로 평가 명령을 명령할 수 있었다.그러나 최악의 경우 오퍼랜드스택의 상위2개의 항목을 스왑하기 위해 Reverse Instruction(REV; 역방향 명령)을 추가해야 할 수 있습니다.이 명령어는 1바이트 명령으로 1사이클이 걸리기 때문에 오버헤드는 최소한으로 억제됩니다.

요약

이와 같은 미세 최적화가 최적화인지 반최적화인지는 사용하는 특정 아키텍처에 따라 달라집니다.따라서 일반적으로 아키텍처 고유의 미세 최적화를 사용하는 습관을 들이는 것은 좋지 않습니다.그렇지 않으면 부적절할 때 본능적으로 사용할 수 있습니다.이 방법은 exacac인 것처럼 보입니다.당신이 읽고 있는 책이 무엇을 옹호하고 있는가.

부동소수점 코드의 경우, 최신 아키텍처에서도 <= 비교가 실제로 (1개의 명령으로) 더 느릴 수 있습니다.첫 번째 기능은 다음과 같습니다.

int compare_strict(double a, double b) { return a < b; }

을 업데이트합니다).PC에서는 먼저 부동소수점 비교(업데이트)를 수행합니다.cr상황 레지스터)를 사용하여 상황 레지스터를 GPR로 이동하고 "비교 미만" 비트를 제자리에 옮긴 다음 반환합니다.네 가지 지시사항이 필요합니다.

대신 이 기능을 고려하십시오.

int compare_loose(double a, double b) { return a <= b; }

, , , , , , , , , , , , , , , , , , , , , , , , , , , , , 같은 작업이 합니다.compare_strict과 ' that to라는 두 가 있습니다. "less to"는 "less to"와 "less to"는 "less to"와 같았다. 지시가 합니다.cror OR이두합니다.-상 、 - 、 ) 、 ) 、 OR ) 。 ★★★★★★★★★★★★★★★★★.compare_loose에는 5개의하며, 5개의 명령어는 5개의 명령어를 필요로 합니다.compare_strict4시 정각

컴파일러가 두 번째 함수를 다음과 같이 최적화할 수 있다고 생각할 수 있습니다.

int compare_loose(double a, double b) { return ! (a > b); }

이 NaNs NaN은 잘못 됩니다. NaN1 <= NaN2 ★★★★★★★★★★★★★★★★★」NaN1 > NaN2을 하다

이 답변의 첫 번째 버전을 쓸 때 저는 < vs >에 대한 제목 질문만 보고 있었습니다.일반적으로 상수의 구체적인 예가 아닌 <=a < 901 ★★a <= 900합니다.< ★★★★★★★★★★★★★★★★★」<=-128 -128 -128 -128 -128 -128 -128 -128 . 에에 。

ARM의 경우 즉시 부호화할 수 있는지 여부는 좁은 필드를 단어 내의 임의의 위치로 회전시킬 수 있는지에 따라 달라집니다. ★★★★★★★★★★★★★★★★★.cmp r0, #0x00f000가능한 , 「」는 「」입니다.cmp r0, #0x00efff그렇지 않을 것이다.따라서 비교를 위한 make-it-smaller 규칙과 컴파일 시간 상수가 ARM에 항상 적용되는 것은 아닙니다.의 경우 의 회전이 아닌 Shift-by-12로cmp ★★★★★★★★★★★★★★★★★」cmn32살 ARM 엄지손가락이다.


< vs . = (실행 시간 지연 조건 포함)

에서 " "의 "를 사용합니다.<=비교와 같은 비용이 든다<이는 분기, 부울화로 0/1 정수를 만들거나 분기 없는 선택 작업(x86 CMOV 등)의 술어로 사용할 때 적용됩니다.다른 답변은 질문의 이 부분만 다루었습니다.

그러나 이 질문은 최적화 도구에 대한 입력인 C++ 연산자에 대한 것입니다.보통 둘 다 똑같이 효율적입니다.컴파일러는 항상 asm으로 구현되는 비교를 바꿀 수 있기 때문에 이 책의 조언은 완전히 거짓으로 들립니다.단, 적어도 한 가지 예외가 있습니다.<=컴파일러가 최적화할 수 없는 것을 우연히 작성할 수 있습니다.

루프 조건으로서는 컴파일러가 루프가 무한하지 않음을 증명하지 못하도록 하는 와 질적으로 다를 수 있습니다.이로 인해 자동 벡터화가 비활성화되어 큰 차이가 발생할 수 있습니다.

되지 않은 된 오버플로(UB.서명된 하지 않으므로 합니다.「 」 ( UB ) 。서명된 루프 카운터는 일반적으로 서명된 오버플로 UB에 따라 최적화되는 컴파일러가 발생하지 않으므로 안전합니다.++i <= size결국엔 거짓이 될 거야(정의되지 않은 동작에 대해 모든 C 프로그래머가 알아야사항)

void foo(unsigned size) {
    unsigned upper_bound = size - 1;  // or any calculation that could produce UINT_MAX
    for(unsigned i=0 ; i <= upper_bound ; i++)
        ...

컴파일러는 정의되지 않은 동작을 유발하는 값을 제외한 모든 가능한 입력 값에 대해 C++ 소스의 (정의된 법적으로 관측 가능한) 동작을 보존하는 방법으로만 최적화할 수 있습니다.

(심플것것)i <= size이 문제도 발생합니다만, 상한치를 계산하는 것은, 우연히, 상관하지 않는 입력에 무한 루프가 발생할 가능성이 있는 것을 나타내는 보다 현실적인 예라고 생각했습니다.

이 경우 는 로 이어지며 항상 True 입니다. 따라서루프는 무한하며, 프로그래머로서 size=0을 전달할 의도가 전혀 없는 경우에도 컴파일러는 이를 존중해야 합니다.만약 컴파일러가 size=0이 불가능하다는 것을 증명할 수 있는 호출기에 이 함수를 인라인화할 수 있다면, 그렇다면, 그것은 그것을 위해 할 수 있는 것처럼 최적화할 수 있다.i < size.

처럼.if(!size) skip the loop; do{...}while(--size); 、 「 「 」를 최적화하는 입니다.for( i<size )이 「」인 는, 「」i루프 내에서는 불필요합니다(루프가 항상 "do..."로 컴파일되는 이유는 무엇입니까?while" style(테일 점프)?

이 수 . with with with with with with with with with with with with with with with with with with 로 입력하면 무한할 수 없습니다. 다음과 같이 입력할 경우size==02^n의 반복이 있습니다.(for loop C의 모든 부호 없는 정수에 대해 반복하면 0을 포함한 모든 부호 없는 정수에 대해 루프를 표현할 수 있지만 asm과 같은 반송 플래그가 없으면 쉽지 않습니다.)

루프 카운터의 랩어라운드가 가능하기 때문에 최신 컴파일러는 보통 '포기'만 하고 거의 적극적으로 최적화하지 않습니다.

예: 1에서 n까지의 정수 합계

서명되지 않은 패배자를 사용하면 가우스의 루프를 기반으로 닫힌 형태로 최적화하는 clang의 사자성어 인식n * (n+1) / 2★★★★★★ 。

unsigned sum_1_to_n_finite(unsigned n) {
    unsigned total = 0;
    for (unsigned i = 0 ; i < n+1 ; ++i)
        total += i;
    return total;
}

Godbolt 컴파일러 탐색기의 clang7.0 및 gcc8.2 x86-64 asm

 # clang7.0 -O3 closed-form
    cmp     edi, -1       # n passed in EDI: x86-64 System V calling convention
    je      .LBB1_1       # if (n == UINT_MAX) return 0;  // C++ loop runs 0 times
          # else fall through into the closed-form calc
    mov     ecx, edi         # zero-extend n into RCX
    lea     eax, [rdi - 1]   # n-1
    imul    rax, rcx         # n * (n-1)             # 64-bit
    shr     rax              # n * (n-1) / 2
    add     eax, edi         # n + (stuff / 2) = n * (n+1) / 2   # truncated to 32-bit
    ret          # computed without possible overflow of the product before right shifting
.LBB1_1:
    xor     eax, eax
    ret

하지만 순진한 버전이라면, 우리는 그저 쨍그랑에서 멍청한 루프를 얻었을 뿐이다.

unsigned sum_1_to_n_naive(unsigned n) {
    unsigned total = 0;
    for (unsigned i = 0 ; i<=n ; ++i)
        total += i;
    return total;
}
# clang7.0 -O3
sum_1_to_n(unsigned int):
    xor     ecx, ecx           # i = 0
    xor     eax, eax           # retval = 0
.LBB0_1:                       # do {
    add     eax, ecx             # retval += i
    add     ecx, 1               # ++1
    cmp     ecx, edi
    jbe     .LBB0_1            # } while( i<n );
    ret

GCC는 어느 쪽이든 클로즈드 폼을 사용하지 않기 때문에 루프 조건을 선택해도 문제가 없습니다.SIMD 정수 덧셈에 의해 자동 벡터화되어 4가 실행됩니다.iXMM 레지스터 요소의 값을 병렬로 표시합니다.

# "naive" inner loop
.L3:
    add     eax, 1       # do {
    paddd   xmm0, xmm1    # vect_total_4.6, vect_vec_iv_.5
    paddd   xmm1, xmm2    # vect_vec_iv_.5, tmp114
    cmp     edx, eax      # bnd.1, ivtmp.14     # bound and induction-variable tmp, I think.
    ja      .L3 #,       # }while( n > i )

 "finite" inner loop
  # before the loop:
  # xmm0 = 0 = totals
  # xmm1 = {0,1,2,3} = i
  # xmm2 = set1_epi32(4)
 .L13:                # do {
    add     eax, 1       # i++
    paddd   xmm0, xmm1    # total[0..3] += i[0..3]
    paddd   xmm1, xmm2    # i[0..3] += 4
    cmp     eax, edx
    jne     .L13      # }while( i != upper_limit );

     then horizontal sum xmm0
     and peeled cleanup for the last n%3 iterations, or something.
     

또한 플레인 스칼라 루프가 있어 매우 작은 스칼라 루프를 사용합니다.n및/또는 무한 루프 케이스의 경우.

참고로 이 두 루프 모두 루프 오버헤드에 대한 명령(및 Sandybridge 패밀리 CPU 상의 uop)을 낭비합니다. sub eax,1/jnz대신add eax,1/cmp/jcc가 더 효율적입니다. (sub/jcc 또는 cmp/jcc 매크로 검색 후) 2가 아닌 1 uop입니다.양쪽 루프 후의 코드는 무조건 EAX를 쓰기 때문에 루프 카운터의 최종값을 사용하지 않습니다.

어느 쪽도 빠르지 않다는 것을 알 수 있다.컴파일러는 각 조건에서 값이 다른 동일한 기계 코드를 생성합니다.

if(a < 901)
cmpl  $900, -4(%rbp)
jg .L2

if(a <=901)
cmpl  $901, -4(%rbp)
jg .L3

나의 예if는 Linux의 x86_64 플랫폼 상의 GCC에서 가져옵니다.

컴파일러 라이터는 매우 똑똑한 사람들이고, 그들은 이런 것들을 생각하고, 다른 많은 사람들은 당연하게 여긴다.

상수가 아닐 경우 어느 경우든 동일한 기계 코드가 생성된다는 것을 알게 되었습니다.

int b;
if(a < b)
cmpl  -4(%rbp), %eax
jge   .L2

if(a <=b)
cmpl  -4(%rbp), %eax
jg .L3

컴퓨터를 만든 사람들이 부울 논리에 맞지 않는 경우에만.그래선 안 되는 거지

모든 비교 (>= <= > <)도 같은 속도로 실행할 수 있습니다.

모든 비교는 단지 뺄셈(차이)과 양수인지 음수인지를 확인하는 것입니다.
(만약,msb설정되어 있고, 숫자는 마이너스입니다.)

확인하는 방법a >= b? 서브a-b >= 0확인하다a-b양성입니다.
확인하는 방법a <= b? 서브0 <= b-a확인하다b-a양성입니다.
확인하는 방법a < b?서브a-b < 0확인해 봐a-b부정적이다.
어떻게 확인하기 위해a > b?서브0 > b-a확인해 봐b-a부정적이다.

간단히 말해서, 이 컴퓨터가 이 아래 지정된 브람스:두건을 할 수 있다.

a >= b==msb(a-b)==0
a <= b==msb(b-a)==0
a > b==msb(b-a)==1
a < b==msb(a-b)==1

물론 컴퓨터를 실제로 할 필요가 없다.==0또는==1어느 하나.
를 위해==0방금 전도될 수 있다.msb회로에서.

어쨌든, 그들이 가장 확실히 없었을 거예요a >= b로 계산되다a>b || a==bㅋㅋㅋ

적어도 이것이 사실이라면 컴파일러는 <= b를 !(a > b)에 최적화할 수 있습니다.따라서 가장 순진한 컴파일러를 제외한 모든 컴파일러와의 비교 자체가 실제로 느리더라도 당신은 차이를 알아차리지 못할 것입니다.

이는 C가 컴파일되는 기본 아키텍처에 크게 의존합니다.일부 프로세서 및 아키텍처에는 서로 다른 사이클 수로 실행되는 것과 같거나 작거나 같은 명령어가 명시되어 있을 수 있습니다.

하지만 컴파일러가 이 문제를 해결할 수 있기 때문에 이는 매우 이례적인 일입니다.

이 행은 대부분의 스크립트 언어에서 올바른 행이라고 할 수 있습니다.문자를 추가하면 코드 처리가 약간 느려지기 때문입니다.단, 상위 답변에서 지적한 바와 같이 C++에서는 효과가 없을 것이며 스크립트 언어를 사용하여 수행하는 모든 작업은 최적화에 대한 우려는 거의 없습니다.

어쩌면 그 이름 없는 책의 저자가 그걸 읽었을지도 몰라a > 0보다 빨리 달리다a >= 1그게 보편적으로 사실이라고 생각합니다.

하지만 그건0관여하고 있다(왜냐하면)CMP아키텍처에 따라 다음과 같이 대체할 수 있습니다.OR)이(가) 원인이 아닙니다.<.

그들은 같은 속도를 가지고 있다.어떤 특수한 아키텍처에서는 그 사람의 말이 맞을지도 모르지만, 적어도 x86 패밀리에서는 그것들이 같다는 것을 알고 있습니다.이를 위해 CPU는 서브섹션(a - b)을 수행하고 플래그 레지스터의 플래그를 체크하기 때문입니다.이 레지스터의 2비트는 ZF(제로 플래그)와 SF(사인 플래그)라고 불리며, 1회 마스크 조작으로 이루어지기 때문에 1회 사이클로 실행됩니다.

차이가 있어도 알아채지 못할 거예요.게다가, 실제로, 당신은 추가 작업을 해야 할 것이다.a + 1또는a - 1마법의 상수를 사용하지 않는 한 상태를 유지하도록 하는 것은 매우 나쁜 관행입니다.

언급URL : https://stackoverflow.com/questions/12135518/is-faster-than

반응형