저는 제 삶을 위해 우리 선생님이 그날 정확히 말한 것을 기억할 수 없으며 아마도 당신이 알기를 바랍니다.
모듈은 "데이터 구조 및 알고리즘"이며 그는 다음과 같은 내용을 우리에게 말했습니다.
if
문은 가장 비싼 [뭔가]입니다. [무언가]는 [무언가]를 등록합니다.
네, 끔찍한 기억이 있고 정말 미안하지만 몇 시간 동안 인터넷 검색을했는데 아무것도 나오지 않았습니다. 어떤 아이디어?
저는 제 삶을 위해 우리 선생님이 그날 정확히 말한 것을 기억할 수 없으며 아마도 당신이 알기를 바랍니다.
모듈은 "데이터 구조 및 알고리즘"이며 그는 다음과 같은 내용을 우리에게 말했습니다.
if
문은 가장 비싼 [뭔가]입니다. [무언가]는 [무언가]를 등록합니다.
네, 끔찍한 기억이 있고 정말 미안하지만 몇 시간 동안 인터넷 검색을했는데 아무것도 나오지 않았습니다. 어떤 아이디어?
답변:
(하드웨어에서) 매우 낮은 수준에서, 예, 경우 들 비싸다. 이유를 이해하려면 파이프 라인의 작동 방식을 이해해야합니다 .
실행될 현재 명령은 일반적으로 명령 포인터 (IP) 또는 프로그램 카운터 (PC) 라고하는 것에 저장됩니다 . 이러한 용어는 동의어이지만 아키텍처마다 다른 용어가 사용됩니다. 대부분의 명령어에서 다음 명령어의 PC는 현재 PC에 현재 명령어의 길이를 더한 것입니다. 대부분의 RISC 아키텍처에서 명령어는 모두 일정한 길이이므로 PC를 일정한 양만큼 증가시킬 수 있습니다. x86과 같은 CISC 아키텍처의 경우 명령어는 가변 길이가 될 수 있으므로 명령어를 디코딩하는 로직은 현재 명령어가 다음 명령어의 위치를 찾는 데 걸리는 시간을 파악해야합니다.
들어 가지 지침, 그러나 실행되는 다음 명령은 현재 명령 후 다음 위치가 아닙니다. 분기는 gotos입니다-프로세서에게 다음 명령어가 어디에 있는지 알려줍니다. 분기는 조건부 또는 무조건 부일 수 있으며 대상 위치는 고정 또는 계산 될 수 있습니다.
조건부 대 무조건 부는 이해하기 쉽습니다. 조건부 분기는 특정 조건이 유지되는 경우에만 사용됩니다 (예 : 한 숫자가 다른 숫자와 같은지 여부). 분기를 취하지 않으면 정상적으로 분기 후 다음 명령으로 제어가 진행됩니다. 무조건 분기의 경우 항상 분기가 사용됩니다. 조건부 분기는 if
문과 for
및 while
루프 의 제어 테스트에 표시됩니다 . 무조건 분기는 무한 루프, 함수 호출, 함수 반환 break
및 continue
명령문, 악명 높은 goto
명령문 등에서 표시됩니다 (이 목록은 완전하지 않습니다).
지점 대상은 또 다른 중요한 문제입니다. 대부분의 분기에는 고정 분기 대상이 있습니다. 컴파일 타임에 고정되는 코드의 특정 위치로 이동합니다. 여기에는 if
문, 모든 종류의 루프, 일반 함수 호출 등 이 포함됩니다 . 계산 된 분기는 런타임에 분기의 대상을 계산합니다. 여기에는 switch
명령문 (때때로), 함수에서 반환, 가상 함수 호출 및 함수 포인터 호출이 포함됩니다.
그렇다면이 모든 것이 성능에 어떤 의미가 있습니까? 프로세서가 파이프 라인에 분기 명령이 표시되는 것을 확인하면 파이프 라인을 계속 채울 방법을 파악해야합니다. 프로그램 스트림에서 분기 다음에 오는 명령어를 파악하려면 (1) 분기를 사용할 것인지 (2) 분기 대상을 알아야합니다. 이를 파악하는 것을 분기 예측 이라고 하며 이는 어려운 문제입니다. 프로세서가 올바르게 추측하면 프로그램이 최대 속도로 계속됩니다. 대신 프로세서가 잘못 추측 하면 잘못된 것을 계산하는 데 시간이 걸립니다. 이제 파이프 라인을 플러시하고 올바른 실행 경로의 명령으로 다시로드해야합니다. 결론 : 큰 성능 저하.
따라서 if 문이 비싼 이유는 분기 예측 오류 때문 입니다. 이것은 가장 낮은 수준입니다. 고급 코드를 작성하는 경우 이러한 세부 사항에 대해 전혀 걱정할 필요가 없습니다. 성능이 매우 중요한 코드를 C 또는 어셈블리로 작성하는 경우에만주의해야합니다. 이 경우 몇 가지 명령이 더 필요하더라도 분기없는 코드를 작성하는 것이 분기하는 코드보다 우월 할 수 있습니다. 당신은 같은 일을 계산하기 위해 할 수있는 멋진 비트 만지작 트릭이있다 abs()
, min()
및 max()
분기없이.
"비싸다"는 매우 상대적인 용어이며 특히 "if
"문과의 에서 조건의 비용도 고려해야하기 때문입니다. 이는 몇 가지 짧은 CPU 명령어에서 원격 데이터베이스를 호출하는 함수의 결과를 테스트하는 것까지 다양합니다.
나는 그것에 대해 걱정하지 않을 것입니다. 임베디드 프로그래밍을 수행하지 않는 한 " if
" 의 비용에 대해 전혀 걱정할 필요가 없습니다 . 대부분의 프로그래머를 들어 그냥 않을거야 지금까지 앱의 성능을 구동 요인.
특히 RISC 아키텍처 마이크로 프로세서의 분기는 가장 값 비싼 명령어 중 일부입니다. 이는 많은 아키텍처에서 컴파일러가 가장 가능성이 높은 실행 경로를 예측하고 해당 명령을 실행 파일에 배치하므로 분기가 발생할 때 이미 CPU 캐시에 있기 때문입니다. 브랜치가 다른 방향으로 가면 메인 메모리로 돌아가서 새 명령어를 가져와야합니다. 이는 상당히 비쌉니다. 많은 RISC 아키텍처에서 모든 명령어는 분기 (종종 2주기)를 제외하고 하나의주기입니다. 여기서는 주요 비용에 대해 이야기하는 것이 아니므로 걱정하지 마십시오. 또한 컴파일러는 99 %의 시간보다 더 잘 최적화됩니다. ) EPIC 아키텍처 (Itanium이 예)에 대한 정말 멋진 점 중 하나는 브랜치의 양쪽에서 명령을 캐시 (및 처리 시작) 한 다음 브랜치의 결과가 나오면 필요하지 않은 세트를 폐기한다는 것입니다. 모두 다 아는. 이렇게하면 예상치 못한 경로를 따라 분기되는 경우 일반적인 아키텍처의 추가 메모리 액세스가 절약됩니다.
셀 성능에 대한 분기 제거 를 통한 성능 향상 기사를 확인하십시오 . 또 다른 재미있는 것은 Real Time Collision Detection Blog의 분기없는 선택 에 대한이 게시물 입니다.
이 질문에 대한 답변으로 이미 게시 된 우수한 답변 외에도 "if"문이 값 비싼 저수준 작업으로 간주되지만 상위 수준 환경에서 분기없는 프로그래밍 기술을 활용하려고한다는 점을 상기시켜 드리고 싶습니다. 스크립팅 언어 또는 비즈니스 로직 레이어 (언어에 관계없이)와 같은은 엄청나게 부적절 할 수 있습니다.
대부분의 경우 프로그램은 먼저 명확성을 위해 작성되고 성능을 위해 최적화되어야합니다. 성능이 가장 중요한 문제가 많은 영역이 있지만 대부분의 개발자는 렌더링 엔진의 핵심 또는 몇 주 동안 실행되는 고성능 유체 역학 시뮬레이션의 핵심에서 사용할 모듈을 작성하지 않습니다. 솔루션이 "그냥 작동"하는 것이 최우선 과제 인 경우 마지막으로 생각하는 것은 코드에서 조건 문의 오버 헤드를 줄일 수 있는지 여부입니다.
if
그 자체로는 느리지 않습니다 . 느림은 항상 상대적으로 당신이 if- 문의 "오버 헤드"를 느껴본 적이 없다고 확신합니다. 고성능 코드를 만들려면 어쨌든 분기를 피하고 싶을 것입니다. if
느리게 만드는 것은 프로세서가 if
휴리스틱과 그 밖의 것을 기반으로 한 후 코드를 미리로드한다는 것 입니다. 또한 if
프로세서가 아직 어떤 경로를 사용할지 알지 못하기 때문에 파이프 라인 이 기계 코드 의 분기 명령어 바로 뒤에서 코드를 실행 하지 못하도록 차단합니다 (파이프 라인 프로세서에서는 여러 명령이 인터리브되고 실행 됨). 실행 된 코드는 역순으로 실행되거나 (다른 브랜치가 사용 된 경우라고 함 branch misprediction
) noop
이러한 위치에 채워져 이런 일이 발생하지 않습니다.
경우 if
악하고 switch
악한도, 그리고 &&
, ||
너무. 그것에 대해 걱정하지 마십시오.
가능한 가장 낮은 수준 if
은 다음과 같이 구성됩니다 (특정에 대한 모든 앱별 전제 조건을 계산 한 후 if
).
그와 관련된 비용 :
점프가 비싸다는 이유에 대한 공명 :
요약하자면 :
최신 프로세서에는 긴 실행 파이프 라인이 있으므로 여러 명령이 동시에 여러 단계에서 실행됩니다. 다음 명령이 실행되기 시작할 때 한 명령의 결과를 항상 알 수는 없습니다. 조건부 점프 (만약)가 발생하면 파이프 라인이 비워 질 때까지 기다려야 명령 포인터가 어느 방향으로 이동해야하는지 알 수 있습니다.
나는 그것을 긴화물 열차라고 생각한다. 많은화물을 직선으로 빠르게 운반 할 수 있지만 코너가 심하게 나옵니다.
Pentium 4 (Prescott)는 31 단계의 긴 파이프 라인을 가지고있었습니다.
Wikipedia 에 대한 추가 정보
분기가 CPU 명령 프리 페치를 죽일 수 있습니까?
또한 루프 내부가 반드시 비용이 많이 드는 것은 아닙니다 .
최신 CPU는 if- 문을 처음 방문 할 때 "if-body"를 취해야한다고 가정합니다 (또는 반대로 말하면 루프 본문도 여러 번 취해지는 것으로 가정합니다) (*). 두 번째 및 추가 방문시, CPU (CPU)는 분기 내역 테이블을 볼 수 있습니다. 조건이 마지막으로 어땠는지 확인할 수 있습니다 (참 이었습니까? 거짓 이었습니까?). 지난번에 거짓 이었다면, 추론 적 실행은 if의 "else"또는 루프를 넘어서 진행됩니다.
(*) 규칙은 실제로 " 정방향 분기를 사용하지 않고 역방향 분기를 사용합니다 "입니다. if- 문 에서 조건이 false로 평가되면 ( if-body 다음 지점까지) [forward] 점프 만 있습니다 (기억하십시오 : CPU는 어쨌든 분기 / 점프를 취하지 않는다고 가정). , 루프 이후 위치에 대한 전방 분기 (취하지 않음)와 반복시 후방 분기 (취득)가있을 수 있습니다.
이것은 또한 가상 함수 또는 함수 포인터 호출에 대한 호출이 많은 사람들이 가정하는 것만 큼 나쁘지 않은 이유 중 하나입니다 ( http://phresnel.org/blog/ )
이것이 언급 할 수 있다고 상상할 수있는 유일한 것은 if
진술이 일반적으로 분기를 생성 할 수 . 프로세서 아키텍처의 특성에 따라 분기는 파이프 라인 중단 또는 기타 최적의 상황이 아닐 수 있습니다.
그러나 이것은 매우 상황에 따라 다릅니다. 대부분의 최신 프로세서에는 분기의 부정적인 영향을 최소화하려는 분기 예측 기능이 있습니다. 또 다른 예는 ARM 아키텍처 (및 기타)가 조건부 논리를 처리 할 수있는 방법입니다. ARM에는 명령어 수준 조건부 실행이 있으므로 간단한 조건부 논리로 인해 분기가 발생하지 않습니다. 조건이 충족되지 않으면 명령이 NOP로 실행됩니다.
말한 모든 것-이 물건에 대해 걱정하기 전에 논리를 수정하십시오. 잘못된 코드는 최대한 최적화되지 않았습니다.
CPU는 깊이 파이프 라인되어 있습니다. 분기 명령어 (if / for / while / switch / etc)는 CPU가 다음에로드하고 실행할 명령어를 실제로 알지 못한다는 것을 의미합니다.
CPU가 수행 할 작업을 기다리는 동안 중단되거나 CPU가 추측합니다. 오래된 CPU의 경우 또는 추측이 잘못된 경우 올바른 명령을로드하고로드하는 동안 파이프 라인 지연이 발생해야합니다. CPU에 따라 이것은 10 ~ 20 개의 명령 치가 될 수 있습니다.
최신 CPU는 좋은 분기 예측을 수행하고 동시에 여러 경로를 실행하고 실제 경로 만 유지하여이를 방지하려고합니다. 이것은 많은 도움이되지만 지금까지만 갈 수 있습니다.
수업에서 행운을 빕니다.
또한 실생활에서 이것에 대해 걱정해야한다면 아마도 OS 디자인, 실시간 그래픽, 과학 컴퓨팅 또는 이와 유사한 CPU 바인딩을 수행하고있을 것입니다. 걱정하기 전에 프로필.
분명히 비효율적이지 않은 가장 명확하고 간단하며 깨끗한 방법으로 프로그램을 작성하십시오. 이는 가장 값 비싼 리소스를 최대한 활용합니다. 프로그램을 작성하거나 나중에 디버깅 (이해 필요)합니다. 성능이 충분하지 않으면 측정병목 현상이있는 곳과이를 완화하는 방법을 확인하십시오. 매우 드문 경우에만 그렇게 할 때 개별 (소스) 지침에 대해 걱정해야합니다. 성능은 첫 번째 줄에서 올바른 알고리즘과 데이터 구조를 선택하고 신중한 프로그래밍, 충분히 빠른 시스템을 얻는 것입니다. 좋은 컴파일러를 사용하면 현대 컴파일러가 코드를 재구성하는 것을 보면 놀랄 것입니다. 성능을 위해 코드를 재구성하는 것은 일종의 최후의 수단이며, 코드는 더 복잡해지고 (따라서 버그가 더 커지고) 수정하기가 더 어려워지고 따라서 전반적으로 더 비쌉니다.
일부 CPU (예 : X86)는 이러한 분기 예측 대기 시간을 피하기 위해 프로그래밍 수준에 분기 예측을 제공합니다.
일부 컴파일러는 (GCC와 같은)이를 더 높은 수준의 프로그래밍 언어 (예 : C / C ++)에 대한 확장으로 노출합니다.
Linux 커널에서 like () / unlikely () 매크로를 참조하십시오 -어떻게 작동합니까? 그들의 이점은 무엇입니까? .
나는 한 번 내 친구와이 논쟁을했다. 그는 매우 순진한 원 알고리즘을 사용하고 있었지만 내 것보다 더 빠르다고 주장했습니다 (원의 1/8 만 계산하는 종류). 결국 if 문은 sqrt로 대체되었으며 어떻게 든 더 빨랐습니다. 아마도 FPU에 sqrt가 내장되어 있기 때문일까요?
ALU 사용 측면에서 가장 비쌉니까? 비교할 값을 저장하기 위해 CPU 레지스터를 사용하고 if 문이 실행될 때마다 값을 가져오고 비교하는 데 시간이 걸립니다.
따라서 그 최적화는 루프가 실행되기 전에 하나의 비교를 수행하고 결과를 변수로 저장하는 것입니다.
누락 된 단어를 해석하려고합니다.