[c] while (1) 또는 while (2) 중 어느 것이 더 빠릅니까?

이것은 선임 관리자가 묻는 인터뷰 질문이었습니다.

어느 것이 더 빠릅니까?

while(1) {
    // Some code
}

또는

while(2) {
    //Some code
}

내부 표현식 while이 최종적으로 true또는로 평가되어야 할 때 둘 다 동일한 실행 속도를 가지고 있다고 말했습니다 false. 이 경우 true조건 내부를 평가하고 추가 조건 지침이 없습니다 while. 따라서 둘 다 동일한 실행 속도를 가지며 나는 (1) 동안 선호합니다.

하지만 면접관은 자신있게 말했다 : “당신의 기초를 확인합니다. while(1)보다 더 빨리이다 while(2).” (그는 내 자신감을 테스트하지 않았다)

이것이 사실입니까?

참조 : 빨리 “동안 (TRUE)”이 아닌 “에 대한 (;;)”인가를? 그렇지 않은 경우 사람들은 왜 그것을 사용합니까?



답변

두 루프는 모두 무한하지만 반복마다 더 많은 명령어 / 리소스가 필요한 루프를 확인할 수 있습니다.

gcc를 사용하여 다음 두 프로그램을 다양한 수준의 최적화로 어셈블리로 컴파일했습니다.

int main(void) {
    while(1) {}
    return 0;
}

int main(void) {
    while(2) {}
    return 0;
}

도없이 최적화 (함께 -O0), 생성 된 조립체를 모두 프로그램 동일 하였다 . 따라서 두 루프 사이에 속도 차이가 없습니다.

참고로, gcc main.c -S -masm=intel최적화 플래그와 함께 생성 된 어셈블리는 다음 과 같습니다.

-O0:

    .file   "main.c"
    .intel_syntax noprefix
    .def    __main; .scl    2;  .type   32; .endef
    .text
    .globl  main
    .def    main;   .scl    2;  .type   32; .endef
    .seh_proc   main
main:
    push    rbp
    .seh_pushreg    rbp
    mov rbp, rsp
    .seh_setframe   rbp, 0
    sub rsp, 32
    .seh_stackalloc 32
    .seh_endprologue
    call    __main
.L2:
    jmp .L2
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

-O1:

    .file   "main.c"
    .intel_syntax noprefix
    .def    __main; .scl    2;  .type   32; .endef
    .text
    .globl  main
    .def    main;   .scl    2;  .type   32; .endef
    .seh_proc   main
main:
    sub rsp, 40
    .seh_stackalloc 40
    .seh_endprologue
    call    __main
.L2:
    jmp .L2
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

부착 -O2하고 -O3(동일한 출력) :

    .file   "main.c"
    .intel_syntax noprefix
    .def    __main; .scl    2;  .type   32; .endef
    .section    .text.startup,"x"
    .p2align 4,,15
    .globl  main
    .def    main;   .scl    2;  .type   32; .endef
    .seh_proc   main
main:
    sub rsp, 40
    .seh_stackalloc 40
    .seh_endprologue
    call    __main
.L2:
    jmp .L2
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

실제로 루프에 대해 생성 된 어셈블리는 모든 최적화 수준에서 동일합니다.

 .L2:
    jmp .L2
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

중요한 부분은 다음과 같습니다.

.L2:
    jmp .L2

어셈블리를 잘 읽을 수는 없지만 무조건 루프입니다. 이 jmp명령 .L2은 값을 true와 비교하지 않고도 프로그램을 무조건 레이블로 다시 재설정하며 , 프로그램이 종료 될 때까지 즉시 다시 수행합니다. 이것은 C / C ++ 코드와 직접 일치합니다.

L2:
    goto L2;

편집하다:

흥미롭게 도 최적화없는 경우에도 다음 루프 jmp는 어셈블리에서 정확히 동일한 출력 (무조건 )을 생성했습니다 .

while(42) {}

while(1==1) {}

while(2==2) {}

while(4<7) {}

while(3==3 && 4==4) {}

while(8-9 < 0) {}

while(4.3 * 3e4 >= 2 << 6) {}

while(-0.1 + 02) {}

그리고 심지어 놀랍게도 :

#include<math.h>

while(sqrt(7)) {}

while(hypot(3,4)) {}

사용자 정의 함수는 조금 더 흥미로워집니다.

int x(void) {
    return 1;
}

while(x()) {}

#include<math.h>

double x(void) {
    return sqrt(7);
}

while(x()) {}

에서 -O0,이 두 가지 예는 실제로 호출 x하고 각 반복에 대한 비교를 수행합니다.

첫 번째 예 (1을 반환) :

.L4:
    call    x
    testl   %eax, %eax
    jne .L4
    movl    $0, %eax
    addq    $32, %rsp
    popq    %rbp
    ret
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

두 번째 예 ( sqrt(7)) :

.L4:
    call    x
    xorpd   %xmm1, %xmm1
    ucomisd %xmm1, %xmm0
    jp  .L4
    xorpd   %xmm1, %xmm1
    ucomisd %xmm1, %xmm0
    jne .L4
    movl    $0, %eax
    addq    $32, %rsp
    popq    %rbp
    ret
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

그러나 -O1둘 이상에서 이전 예제와 동일한 어셈블리를 생성합니다 ( jmp이전 레이블로 돌아가는 무조건 ).

TL; DR

GCC에서 다른 루프는 동일한 어셈블리로 컴파일됩니다. 컴파일러는 상수 값을 평가하고 실제 비교를 수행하지 않습니다.

이야기의 교훈은 다음과 같습니다.

  • C ++ 소스 코드와 CPU 명령어간에 변환 계층이 있으며이 계층은 성능에 중요한 영향을 미칩니다.
  • 따라서 소스 코드 만보고 성능을 평가할 수 없습니다.
  • 컴파일러 그러한 사소한 경우를 최적화하기에 충분히 똑똑 해야합니다 . 프로그래머 대부분의 경우에 대해 생각하면서 시간을 낭비 해서는 안됩니다 .

답변

예, while(1)보다 훨씬 빠르기 때문에 while(2), 인간이 읽을! while(1)익숙하지 않은 코드베이스에서 볼 때 저자가 의도 한 것을 즉시 알 수 있으며 내 시선은 다음 줄로 계속 진행될 수 있습니다.

내가 본다면 while(2)아마 내 트랙에서 멈추고 저자가 왜 쓰지 않았는지 알아 내려고 노력할 것이다 while(1). 저자의 손가락이 키보드에서 미끄러 졌습니까? 이 코드베이스의 관리자는 while(n)루프를 다르게 보이게하는 애매한 주석 처리 메커니즘으로 사용됩니까? 정적 분석 도구가 깨진 상태에서 가짜 경고에 대한 해결 방법이 있습니까? 아니면 이것이 생성 된 코드를 읽는 단서입니까? 잘못 권고 된 찾기 및 바꾸기, 또는 잘못된 병합 또는 우주 광선으로 인한 버그입니까? 이 코드 줄이 뭔가 다른 것을해야 할 수도 있습니다. 아마 읽었을 while(w)수도 while(x2)있습니다. 파일 기록에서 저자를 찾아서 “WTF”전자 메일을 보내는 것이 좋습니다. 이제 저는 정신적 인 맥락을 깨뜨 렸습니다.while(2)while(1) 잠깐 동안 걸렸을 것입니다!

나는 과장하지만 조금만. 코드 가독성이 정말 중요합니다. 그리고 그것은 인터뷰에서 언급 할 가치가 있습니다!


답변

특정 옵션 세트를 가진 특정 대상에 대해 특정 컴파일러에 의해 생성 된 코드를 보여주는 기존 답변은 질문이 특정 상황에서 질문되지 않는 한 ( ‘x86_64에 gcc 4.7.2를 사용하는 것이 더 빠름) 예를 들어 기본 옵션을 사용 하시겠습니까? “)

언어 정의에 관한 한 추상 기계 while (1) 에서는 정수 상수 1while (2)평가하고 정수 상수를 평가합니다 2. 두 경우 모두 결과는 0과 같은지 비교됩니다. 언어 표준은 두 구성의 상대적인 성능에 대해서는 전혀 언급하지 않습니다.

나는 매우 순진한 컴파일러가 적어도 최적화를 요청하지 않고 컴파일 할 때 두 형태에 대해 다른 기계 코드를 생성 할 수 있다고 상상할 수 있습니다.

반면에 C 컴파일러 는 상수 표현식이 필요한 컨텍스트에 나타날 때 컴파일 타임에 일부 상수 표현식을 평가해야합니다 . 예를 들면 다음과 같습니다.

int n = 4;
switch (n) {
    case 2+2: break;
    case 4:   break;
}

진단이 필요합니다. 게으른 컴파일러에는 2+2실행 시간까지 평가를 연기하는 옵션이 없습니다 . 컴파일러는 컴파일 타임에 상수 표현식을 평가할 수 있어야하므로 필요하지 않은 경우에도 해당 기능을 활용하지 않을 이유가 없습니다.

C 표준 ( N1570 6.8.5p4)에 따르면

반복문 은 제어 표현식이 0과 비교 될 때까지 루프 본문 이라는 명령문이 반복적으로 실행되도록합니다.

따라서 관련 상수 표현식은 1 == 02 == 0이며 둘 다 int값으로 평가됩니다 0. 이러한 비교는 while루프 의 의미에 내재되어 있으며 실제 C 표현식으로는 존재하지 않습니다.

순진한 컴파일러 는 두 구문에 대해 다른 코드를 생성 할 수 있습니다. 예를 들어 첫 번째 경우 무조건 무한 루프를 생성하고 ( 1특별한 경우로 처리 ) 두 번째 경우와 동등한 명시 적 런타임 비교를 생성 할 수 2 != 0있습니다. 그러나 실제로 그렇게 작동하는 C 컴파일러를 본 적이 없으며 그러한 컴파일러가 존재하는지 심각하게 의심합니다.

대부분의 컴파일러 (모든 프로덕션 품질의 컴파일러를 말하려는 유혹이 있습니다)에는 추가 최적화를 요청할 수있는 옵션이 있습니다. 이러한 옵션을 사용하면 컴파일러가 두 형식에 대해 다른 코드를 생성 할 가능성이 훨씬 줄어 듭니다.

컴파일러가 두 구문에 대해 다른 코드를 생성하는 경우 먼저 다른 코드 시퀀스가 ​​실제로 다른 성능을 갖는지 확인하십시오. 그렇다면 최적화 옵션을 사용하여 다시 컴파일하십시오 (가능한 경우). 여전히 다른 경우, 버그 보고서를 컴파일러 공급 업체에 제출하십시오. C 표준을 준수하지 못한다는 의미에서 (필요하게) 버그는 아니지만 거의 수정해야 할 문제입니다.

결론 : while (1)while(2) 거의 확실하게 동일한 성능을 가지고있다. 그것들은 정확히 같은 의미론을 가지고 있으며, 어떤 컴파일러도 동일한 코드를 생성하지 않을 이유가 없습니다.

컴파일러가 for while(1)보다 빠른 코드를 생성하는 것은 합법적이지만 while(2)컴파일러가 동일한 프로그램에서 while(1)다른 코드 보다 더 빠른 코드를 생성하는 것은 합법적입니다 while(1).

(당신이 물었던 또 다른 질문이 있습니다 : 당신은 잘못된 기술적 요점을 주장하는 면접관을 어떻게 다루나요? 아마도 직장 사이트에 좋은 질문 일 것입니다 ).


답변

잠깐만 면접관, 그는이 사람처럼 보였습니까?

여기에 이미지 설명을 입력하십시오

면접관 자신 이이 면담에 실패한 것은 나쁘다. 만약이 회사의 다른 프로그래머가이 시험을 “통과”하면 어떨까?

제 명령문을 평가 1 == 0하고 2 == 0 있어야한다 동등하게 빨리. 우리 하나가 다른 것보다 빠를 수있는 좋지 않은 컴파일러 구현을 상상할 수 있습니다. 하지만 아무도 없다 좋은의 하나가 다른 하나보다 더 빠르게되어야하는 이유 이유는.

주장이 사실 일 때 모호한 상황이 있더라도 프로그래머는 모호한 (이 경우 소름 끼치는) 퀴즈에 대한 지식을 바탕으로 평가해서는 안됩니다. 이 인터뷰에 대해 걱정하지 마십시오. 가장 좋은 방법은 걸어가는 것입니다.

면책 조항 : 이것은 원래 Dilbert 만화가 아닙니다. 이것은 단지 매시업 입니다.


답변

설명이 맞습니다. 이것은 기술적 지식뿐만 아니라 자신감을 테스트하는 질문 인 것 같습니다.

그런데 대답하면

두 코드를 모두 완료하는 데 무한 시간이 걸리기 때문에 두 코드 모두 동일하게 빠릅니다

면접관은

그러나 while (1)초당 더 많은 반복을 수행 할 수 있습니다. 이유를 설명 할 수 있습니까? (이것은 말도 안됩니다; 당신의 자신감을 다시 테스트하십시오)

그래서 당신이했던 것처럼 대답함으로써, 당신은 그렇지 않으면이 나쁜 질문에 대해 논의하는 데 낭비되는 시간을 절약 할 수 있습니다.


다음은 최적화가 해제 된 시스템 (MS Visual Studio 2012)에서 컴파일러가 생성 한 코드 예입니다.

yyy:
    xor eax, eax
    cmp eax, 1     (or 2, depending on your code)
    je xxx
    jmp yyy
xxx:
    ...

최적화가 설정된 경우 :

xxx:
    jmp xxx

따라서 생성 된 코드는 적어도 최적화 컴파일러와 정확히 동일합니다.


답변

질문에 대한 가장 가능성있는 설명은 인터뷰자가 프로세서가 0이 아닌 값에 도달 할 때까지 숫자의 개별 비트를 하나씩 확인한다고 생각한다는 것입니다.

1 = 00000001
2 = 00000010

“0”인 경우 알고리즘은 숫자의 오른쪽에서 시작하여 0이 아닌 비트에 도달 할 때까지 각 비트 while(1) { }를 확인해야합니다 while(2) { }. 루프 는 루프 당 반복 당 두 배의 비트를 확인해야합니다 .

이를 위해서는 컴퓨터 작동 방식에 대한 매우 잘못된 정신 모델이 필요하지만 자체 내부 논리가 있습니다. 확인하는 방법 중 하나는 물어하는 것 while(-1) { }또는 while(3) { }경우에 똑같이 빠른 것, 또는 while(32) { }, 심지어 느린 .


답변

물론 나는이 관리자의 실제 의도를 알지 못하지만 완전히 다른 견해를 제안합니다. 새로운 회원을 팀에 채용 할 때, 그가 충돌 상황에 어떻게 대응하는지 아는 것이 유용합니다.

그들은 당신을 갈등으로 몰아 넣었습니다. 이것이 사실이라면, 그들은 영리하고 질문은 좋았습니다. 은행업과 같은 일부 산업의 경우 문제를 Stack Overflow에 게시하면 거부 사유가 될 수 있습니다.

그러나 물론 나는 모른다. 단지 하나의 옵션을 제안한다.