Development Tip

이 지연 루프가 수면없이 여러 번 반복 한 후 더 빨리 실행되기 시작하는 이유는 무엇입니까?

yourdevel 2020. 10. 29. 20:08
반응형

이 지연 루프가 수면없이 여러 번 반복 한 후 더 빨리 실행되기 시작하는 이유는 무엇입니까?


중히 여기다:

#include <time.h>
#include <unistd.h>
#include <iostream>
using namespace std;

const int times = 1000;
const int N = 100000;

void run() {
  for (int j = 0; j < N; j++) {
  }
}

int main() {
  clock_t main_start = clock();
  for (int i = 0; i < times; i++) {
    clock_t start = clock();
    run();
    cout << "cost: " << (clock() - start) / 1000.0 << " ms." << endl;
    //usleep(1000);
  }
  cout << "total cost: " << (clock() - main_start) / 1000.0 << " ms." << endl;
}

다음은 예제 코드입니다. 타이밍 루프의 처음 26 회 반복에서 run함수 비용은 약 0.4ms이지만 비용은 0.2ms로 감소합니다.

이 때 usleep주석 처리되어, 지연 루프는 절대 과속하지, 모든 실행 0.4 밀리합니다. 왜?

코드는 g++ -O0(최적화 없음)으로 컴파일 되므로 지연 루프가 최적화되지 않습니다. Intel (R) Core (TM) i3-3220 CPU @ 3.30GHz, 3.13.0-32 일반 Ubuntu 14.04.1 LTS (Trusty Tahr)에서 실행됩니다.


26 회 반복 후 Linux는 프로세스가 풀 타임 슬라이스를 연속으로 두 번 사용하기 때문에 CPU를 최대 클록 속도까지 올립니다 .

벽시계 시간 대신 성능 카운터로 확인하면 지연 루프 당 코어 클럭주기가 일정하게 유지되어 DVFS (모든 최신 CPU가 더 많은 에너지로 실행하는 데 사용 하는)의 효과 일뿐임을 확인할 수 있습니다. 대부분의 경우 효율적인 주파수 및 전압).

새로운 전원 관리 모드 (하드웨어가 클럭 속도를 완전히 제어하는 ​​경우)에 대한 커널 지원을 사용하여 Skylake 에서 테스트 한 경우 램프 업이 훨씬 더 빠르게 발생합니다.

Turbo 를 사용하는 Intel CPU 에서 잠시 실행 상태로두면 열 제한에 따라 클럭 속도가 최대 지속 주파수로 다시 낮아지면 반복 당 시간이 다시 약간 증가하는 것을 볼 수 있습니다.


a를 도입usleep 하면 프로세스가 최소 주파수에서도 100 % 부하를 생성하지 않기 때문에 Linux의 CPU 주파수 거버너 가 클럭 속도를 높이는 것을 방지 합니다. (즉, 커널의 휴리스틱은 CPU가 실행중인 워크로드에 대해 충분히 빠르게 실행되고 있다고 결정합니다.)



다른 이론에 대한 의견 :

re : 잠재적 인 컨텍스트 전환 usleep이 캐시를 오염시킬 수 있다는 David의 이론 : 일반적으로 나쁜 생각은 아니지만이 코드를 설명하는 데 도움이되지 않습니다.

캐시 / TLB 오염은이 실험에서 전혀 중요하지 않습니다 . 기본적으로 스택의 끝을 제외하고 메모리를 건 드리는 타이밍 창 안에는 아무것도 없습니다. 대부분의 시간은 int스택 메모리 중 하나 만 닿는 작은 루프 (명령 캐시 1 줄)에서 소비됩니다 . 그 동안의 잠재적 인 캐시 오염 usleep은이 코드에 대한 시간의 아주 작은 부분입니다 (실제 코드는 다를 것입니다)!

x86에 대한 자세한 내용 :

clock()자체 호출 은 캐시 미스 일 수 있지만 코드 페치 캐시 미스는 측정 대상의 일부가 아닌 시작 시간 측정을 지연시킵니다. 두 번째 호출 clock()은 캐시에서 여전히 뜨겁기 때문에 거의 지연되지 않습니다.

run함수는 다른 캐시 라인 일 수있다 main(사람 GCC 마크 main가 덜 최적화 및 기타 냉 기능 / 데이터 도착 배치되도록 "콜드"등). 하나 또는 두 개의 명령 캐시 미스를 예상 할 수 있습니다 . 하지만 여전히 동일한 4k 페이지에 main있을 수 있으므로 프로그램의 시간 제한 영역에 들어가기 전에 잠재적 인 TLB 미스를 유발 했을 것입니다.

gcc -O0은 OP의 코드를 다음과 같이 컴파일합니다 (Godbolt Compiler explorer) : 루프 카운터를 스택의 메모리에 유지합니다.

빈 루프는 루프 카운터를 스택 메모리에 유지하므로 일반적인 Intel x86 CPU 에서 루프는 OP의 IvyBridge CPU에서 ~ 6 사이클 당 한 번의 반복으로 실행됩니다. add이는 메모리 대상 (읽기 -수정-쓰기). 100k iterations * 6 cycles/iteration최대 2 개의 캐시 미스의 기여를 지배하는 600k주기입니다 (코드 가져 오기 미스에 대해 각각 ~ 200 주기로 해결 될 때까지 추가 명령이 발행되지 못함).

비 순차적 실행 및 저장 전달은 대부분 ( call명령의 일부로) 스택에 액세스 할 때 잠재적 인 캐시 미스를 숨겨야합니다 .

루프 카운터가 레지스터에 보관되어 있어도 100k 사이클은 많습니다.


를 호출 usleep하면 컨텍스트 전환이 발생할 수도 있고 그렇지 않을 수도 있습니다. 그렇다면 그렇지 않은 경우보다 더 오래 걸립니다.

참고 URL : https://stackoverflow.com/questions/38299023/why-does-this-delay-loop-start-to-run-faster-after-several-iterations-with-no-sl

반응형