문제

나는 다음과 같은 이상한 행동을 목격했다. 거의 동일하게하는 두 가지 기능이 있습니다. 특정 작업을 수행하는 데 필요한 사이클 수를 측정합니다. 한 기능에서 루프 내부 I는 변수를 증가시킵니다. 다른 일이 일어나지 않습니다. 변수는 휘발성이므로 최적화되지 않습니다. 이것들은 기능입니다.

unsigned int _osm_iterations=5000;

double osm_operation_time(){
    // volatile is used so that j will not be optimized, and ++ operation
    // will be done in each loop
    volatile unsigned int j=0;
    volatile unsigned int i;
    tsc_counter_t start_t, end_t;
    start_t = tsc_readCycles_C();
    for (i=0; i<_osm_iterations; i++){
       ++j;
    }
    end_t = tsc_readCycles_C();
    if (tsc_C2CI(start_t) ==0 || tsc_C2CI(end_t) ==0 || tsc_C2CI(start_t) >= tsc_C2CI(end_t))
         return -1;
    return (tsc_C2CI(end_t)-tsc_C2CI(start_t))/_osm_iterations;
}

double osm_empty_time(){
    volatile unsigned int i;
    volatile unsigned int j=0;
    tsc_counter_t start_t, end_t;
    start_t = tsc_readCycles_C();
    for (i=0; i<_osm_iterations; i++){
        ;
    }
    end_t = tsc_readCycles_C();
    if (tsc_C2CI(start_t) ==0 || tsc_C2CI(end_t) ==0 || tsc_C2CI(start_t) >= tsc_C2CI(end_t))
        return -1;
    return (tsc_C2CI(end_t)-tsc_C2CI(start_t))/_osm_iterations;
}

비표준 기능이 몇 가지 있지만 관리 할 것이라고 확신합니다.

문제는 첫 번째 함수가 반환된다는 것입니다 4, 두 번째 함수 (더 적은 것으로 추정되는)는 반환됩니다. 6, 두 번째는 분명히 첫 번째 것보다 적습니다.

그것은 누구에게도 이해가됩니까?

실제로 첫 번째 기능을 만들어 두 번째 측정을 위해 루프 오버 헤드를 줄일 수있었습니다. 이 방법이 실제로 잘라 내지 않으므로 어떻게 해야하는지 아십니까?

나는 우분투에 있습니다 (64 비트 생각).

정말 감사합니다.

도움이 되었습니까?

해결책

여기서 몇 가지를 볼 수 있습니다. 하나는 두 루프의 코드가 동일하게 보인다는 것입니다. 둘째, 컴파일러는 아마도 변수가 i 그리고 변수 j 항상 같은 값을 가지며 그 중 하나를 최적화합니다. 생성 된 어셈블리를보고 실제로 무슨 일이 일어나고 있는지 확인해야합니다.

또 다른 이론은 루프의 내부 본문으로의 변경이 코드의 캐시 가능성에 영향을 미쳤다는 것입니다. 이것은 캐시 라인이나 다른 것들을 가로 질러 움직일 수있었습니다.

코드는 매우 사소하기 때문에 5000 개의 반복을 수행하더라도 사용중인 타이밍 코드의 오류 마진에 시간이 걸린다는 것을 알 수 있습니다. 최신 컴퓨터는 아마도 1 밀리 초 미만으로 실행할 수 있습니다. 아마도 반복 수를 늘려야합니까?

GCC에서 생성 된 어셈블리를 보려면 -S 컴파일러 옵션을 지정하십시오:

Q : GCC가 생성 한 어셈블리 코드를 어떻게 살펴볼 수 있습니까?

Q : C 코드와 어셈블리 변환을 함께 볼 수있는 파일을 어떻게 만들 수 있습니까?

A : -S (참고 : 자본 S) 스위치를 GCC로 사용하면 조립 코드를 .s 확장자가있는 파일로 방출합니다. 예를 들어 다음 명령은 다음과 같습니다.

gcc -o2 -s -c foo.c

FOO.S. 파일에 생성 된 어셈블리 코드를 남겨 둡니다.

어셈블리와 함께 C 코드를 보려면 변환 된 경우 다음과 같은 명령 행을 사용하십시오.

gcc -c -g -wa, -a, -ad [기타 GCC 옵션] foo.c> foo.lst

결합 된 c/어셈블리 목록을 foo.lst 파일에 출력합니다.

다른 팁

이런 종류의 것은 컴파일러 최적화와 타이머 해상도에 크게 의존합니다. 당신이 제공하는 결과 (4와 6)는 장치에 관계없이 일종의 낮습니다. 올바르게 벤치마킹하려면이 두 기능을 모두 수천 번 실행하는 루프로 랩핑해야합니다.

특히 적은 수의 반복으로 인해 이런 종류의 것을 추측하기가 어렵습니다. 그러나 발생할 수있는 한 가지는 자유 정수 실행 장치에서 증분이 실행될 수 있다는 점입니다. I의 값에 대한 DEP가 없기 때문에 약간의 평행을 얻을 수 있습니다.

이것이 64 비트 OS라고 언급 했으므로 X86_64 아키텍처에 더 많은 레지스터가 있기 때문에이 모든 값이 레지스터에 있다는 것은 거의 확실합니다. 그 외에는 더 많은 반복을 수행하고 결과가 얼마나 안정적인지 확인하고 싶습니다.

진정으로 코드의 작동을 테스트하려는 경우 ("j++;" 이 경우) 실제로 다음을 수행하는 것이 좋습니다.

1/ 실행 파일 내의 위치가 코드에 영향을 줄 수 있기 때문에 두 개의 개별 실행 파일로 수행하십시오.

2/ 경과 시간 대신 CPU 시간을 사용해야합니다 (나는 무엇을 확실하지 않습니다. "tsc_readCycles_C()" 당신에게 줘). 이는 다른 작업과 함께로드 된 CPU의 잘못된 결과를 피하는 것입니다.

3/ 컴파일러 최적화 끄기 (예 : "gcc -O0") 보장합니다 gcc 결과를 왜곡시킬 수있는 멋진 물건을 넣지 않습니다.

4/ 걱정할 필요가 없습니다 volatile 배치와 같은 실제 결과를 사용하는 경우 :

printf ("%d\n",j);

루프 후 또는 :

FILE *fx = fopen ("/dev/null","w");
fprintf (fx, "%d\n", j);
fclose (fx);

출력을 전혀 원하지 않는다면. 휘발성이 A인지 기억이 나지 않습니다 제안 컴파일러 또는 시행.

5/ 5,000의 반복은 낮은면에서 약간 보이며, "노이즈"는 판독 값에 영향을 줄 수 있습니다. 아마도 더 높은 가치가 더 나을 것입니다. 더 큰 코드를 타이밍하고 방금 포함 된 경우에는 문제가되지 않을 수 있습니다. "j++;" 장소 보유자로서.

이와 유사한 테스트를 실행할 때 일반적으로 다음과 같습니다.

  1. 시간이 적어도 초, 바람직하게는 (작은) 수십 초 안에 측정되는지 확인하십시오.
  2. 프로그램의 단일 실행이 첫 번째 함수를 호출 한 다음 두 번째 기능을 호출 한 다음 첫 번째 기능을 다시 한 번, 두 번째 기능을 다시 한 번, 이상한 캐시 워밍업 문제가 있는지 확인하십시오.
  3. 프로그램을 여러 번 실행하여 타이밍이 얼마나 안정적인지 확인하십시오.

나는 여전히 당신의 관찰 된 결과를 설명하기 위해 손실을 입고 있지만, 당신이 당신의 기능을 올바르게 식별했다고 확신한다면 (예를 들어, copy'n'paste 오류가 있었기 때문에 자명하게 사례가 아님), 그런 다음 어셈블러 출력을 보는 것이 남은 주요 옵션입니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top