programing

함수 포인터가 명령 파이프라인을 삭제하도록 강제합니까?

newstyles 2023. 10. 6. 20:56

함수 포인터가 명령 파이프라인을 삭제하도록 강제합니까?

현대의 CPU들은 광범위한 파이프라인을 가지고 있는데, 즉 실제로 명령을 실행하기 훨씬 전에 필요한 명령과 데이터를 로드하는 것입니다.

파이프라인에 로드된 데이터가 무효화되는 경우가 있으며 파이프라인을 지우고 새 데이터로 다시 로드해야 합니다.파이프라인을 다시 채우는 데 걸리는 시간이 상당하여 성능 저하를 초래할 수 있습니다.

C에서 함수 포인터를 호출하면 파이프라인의 포인터가 함수 포인터이며 다음 명령을 위해 해당 포인터를 따라야 한다는 것을 깨달을 수 있을 정도로 파이프라인이 똑똑합니까?아니면 기능 포인터가 있으면 파이프라인이 지워지고 성능이 저하됩니까?

저는 C에서 일하고 있지만, 많은 기능 호출이 v-table을 통해 이루어지는 C++에서는 이것이 더욱 중요하다고 생각합니다.


편집하다

@JensGustedt writes:

함수 호출에 실질적인 성능을 발휘하려면 호출하는 함수가 매우 짧아야 합니다.코드를 측정하여 이를 관찰할 경우 해당 호출이 인라인될 수 있도록 설계를 다시 확인해야 합니다.

불행하게도, 그것이 제가 빠져든 함정일지도 모릅니다.

성능상의 이유로 타겟 함수를 작고 빠르게 작성하였습니다.

그러나 다른 기능으로 쉽게 교체할 수 있도록 함수-포인터에서 참조합니다(포인터 참조를 다른 기능으로 만들기만 하면 됩니다!).함수 포인터로 참조하기 때문에 인라인이 안 될 것 같습니다.

그래서 저는 아주 짧은, 인라인이 아닌 기능을 가지고 있습니다.

일부 프로세서에서는 간접 분기가 항상 파이프라인의 적어도 일부를 삭제합니다. 왜냐하면 이는 항상 잘못된 예측을 하기 때문입니다.주문형 프로세서의 경우가 특히 그렇습니다.

예를 들어, 인라인 함수 호출, 직접 함수 호출, 간접 함수 호출(가상 함수 또는 함수 포인터, 이 플랫폼에서 동일함)의 오버헤드를 비교하여 개발한 프로세서에 대해 몇 가지 타이밍을 실행했습니다.

저는 아주 작은 함수 본체를 작성하고 수백만 개의 통화로 이루어진 촘촘한 루프에서 측정하여 통화 위약금만으로 인한 비용을 계산했습니다."인라인" 함수는 단지 함수 본체의 비용만을 측정하는 대조군(기본적으로 단일 부하 작업)이었습니다.직접 함수는 정확하게 예측된 분기의 패널티(정적인 목표이고 PPC의 예측 변수가 항상 이 값을 정확하게 얻을 수 있기 때문에)와 함수 프롤로그를 측정했습니다.다했습니다.bctrl간접 가지

614,400,000 함수 호출:

inline:   411.924 ms  (   2 cycles/call )
direct:  3406.297 ms  ( ~17 cycles/call )
virtual: 8080.708 ms  ( ~39 cycles/call )

보다시피 직접 통화는 기능 본체보다 15주기가 더 많이 소요되며 가상 통화(정확히는 기능 포인터 호출과 동일)는 직접 통화보다 22주기가 더 소요됩니다.이는 파이프라인의 시작(명령어 페치)과 분기 ALU의 끝 사이에 대략 몇 개의 파이프라인 단계가 존재하는지에 대한 것입니다.따라서 이 아키텍처에서는 간접 분기(일명 가상 통화)를 통해 22개의 파이프라인 단계가 100% 해소됩니다.

다른 아키텍처는 다를 수 있습니다.프로세서가 예측하는 것에 대한 가정이 아니라 직접적인 경험적 측정이나 CPU의 파이프라인 사양을 통해 이러한 결정을 내려야 합니다. 구현 방식이 매우 다르기 때문입니다.이 경우 bctrl이 은퇴할 때까지 분기 예측 변수가 어디로 이동할지 알 수 없기 때문에 파이프라인 지우기가 발생합니다.기껏해야 지난 bctrl과 같은 목표를 향해 있는 것으로 추측할 수 있을 것이고, 이 특정 CPU는 그런 추측조차 시도하지 않습니다.

함수 포인터를 호출하는 것은 C++에서 가상 메소드를 호출하는 것과 근본적으로 다르지 않으며, 반환과도 근본적으로 다르지 않습니다.프로세서는 앞을 내다보면서 포인터를 통해 분기가 다가오고 있음을 인식하고 프리페치 파이프라인에서 포인터를 안전하고 효과적으로 해결하고 경로를 따라갈 수 있는지 결정합니다.이는 일반적인 상대 브랜치를 따르는 것보다 더 어렵고 비용이 많이 들지만, 현대 프로그램에서 간접 브랜치는 매우 흔하기 때문에 대부분의 프로세서가 시도할 일입니다.

Oli의 말처럼, 조건부 분기에 대한 잘못된 예측이 있을 경우에만 파이프라인을 "정리"하는 것이 필요할 것이며, 이는 분기가 오프셋에 의한 것인지 가변 주소에 의한 것인지와는 무관합니다.그러나 프로세서는 분기 주소의 유형에 따라 다르게 예측하는 정책을 가질 수 있습니다. 일반적으로 프로세서는 잘못된 주소의 가능성 때문에 조건부 분기의 간접 경로를 적극적으로 따르지 않을 것입니다.

함수 포인터를 통한 호출이 반드시 파이프라인 삭제를 야기하는 것은 아니지만 시나리오에 따라 그럴 수도 있습니다.CPU가 지점의 목적지를 미리 효과적으로 예측할 수 있느냐가 관건입니다.

현대의 "큰" 순서 외 코어가 간접 호출1 처리하는 방법은 대략 다음과 같습니다.

  • 간접 분기를 몇 번 실행한 후에는 간접 분기 예측 변수가 해당 분기가 앞으로 발생할 주소를 예측하려고 합니다.
  • 최초의 간접 분기 예측기는 고정된 단일 위치만을 "예측"할 수 있는 매우 단순했습니다.
  • 대부분의 최신 CPU를 포함한 나중의 예측기는 훨씬 더 복잡하며, 종종 간접 점프의 반복 패턴을 잘 예측할 수 있으며 점프 목표를 이전의 조건부 또는 간접 분기의 방향과 상관시킬 수 있습니다.
  • 예측이 성공하면 간접 통화는 일반적인 직접 통화와 비슷한 비용을 갖게 되고, 이 비용은 코드의 나머지 부분과 대부분 "범위를 벗어남"(즉, 종속성 체인에 참여하지 않음) 따라서 통화가 매우 조밀하지 않는 한 코드의 최종 런타임에 미치는 영향은 작을 수 있습니다.
  • 반면 예측이 실패하면 분기 방향 예측과 마찬가지로 완전한 예측 오류가 발생합니다.이러한 잘못된 예측의 비용은 주변 코드에 따라 다르기 때문에 고정된 숫자를 넣을 수는 없지만, 일반적으로 프론트엔드에서 약 20 사이클의 버블을 발생시키며, 런타임의 전체 비용은 종종 유사하게 끝납니다.

따라서 이러한 기본 사항을 고려할 때 특정 시나리오에서 어떤 일이 발생하는지에 대해 다음과 같이 교육적으로 추측할 수 있습니다.

  1. 함수 포인터가 항상 같은 함수를 가리키면 거의 항상1 잘 예측되고 일반 함수 호출과 거의 같은 비용이 듭니다.
  2. 여러 대상 간에 임의로 교대하는 함수 포인터는 거의 항상 잘못 예측됩니다.기껏해야 예측 변수가 가장 일반적인 대상을 항상 예측할 수 있기를 바랄 수 있으므로 최악의 경우에는 대상을 임의로 선택할 수 있습니다.N목표 예측 성공률은 다음과 같이 제한됩니다.1 / N(즉, N이 무한대로 가면서 0으로 갑니다.)이 점에서 간접 가지는 조건부 가지보다 최악의 경우 행태가 더 심한데, 이는 일반적으로 최악의 경우 오예측률이 50%2에 달합니다.
  3. 어느 정도 예측 가능한(예를 들어 반복 패턴을 따르는) 동작을 하는 함수 포인터의 예측률은 하드웨어의 세부 정보와 예측기의 정교함에 크게 의존합니다.현대의 인텔 칩들은 꽤 좋은 간접 예측기들을 가지고 있지만, 세부적인 사항들은 공개되지 않았습니다.일반적인 통념으로는 조건부 분기에도 사용되는 TAGE 예측 변수의 간접적인 변형을 사용하고 있다고 합니다.

1 예측 변수가 아직 보지 못한 간접 호출을 예측할 수 없기 때문에 단일 대상에 대해서도 예측을 잘못하는 경우가 있습니다.또한 CPU의 예측 자원의 크기가 제한되어 있기 때문에 함수 포인터를 한동안 사용하지 않았다면 결국 예측 자원은 다른 브랜치에 사용되고 다음에 호출할 때 잘못된 예측을 하게 됩니다.

2 실제로 최근에 가장 자주 볼 수 있는 방향을 단순하게 예측하는 매우 단순한 조건부 예측 변수는 전체 랜덤 분기 방향에 대해 50%의 예측률을 가져야 합니다.결과가 50%보다 현저하게 나빠지려면 기본적으로 예측 변수를 모형화하고 항상 모형의 반대 방향으로 분기하도록 선택하는 적대적 알고리즘을 설계해야 합니다.

함수 포인터 호출과 "정상적인" 호출 사이에는 추가적인 수준의 지시 외에는 큰 차이가 없습니다.따라서 대상 주소가 아직 캐시 또는 레지스터에 없는 경우 CPU는 메인 메모리에서 검색되는 동안 대기해야 할 가능성이 있습니다.

예, 파이프라인이 지연될 수는 있지만 이는 일반 기능 호출과 다르지 않습니다.그리고 여느 때와 마찬가지로 분기 예측 및 고장 실행과 같은 메커니즘을 통해 위약금을 최소화할 수 있습니다.

언급URL : https://stackoverflow.com/questions/10757167/do-function-pointers-force-an-instruction-pipeline-to-clear