2017-03-04 1 views
1
I는 다음 코드 ++ 값 및 C에 참조 (g ++ 5.4.0)에 전달하는 시간을 비교하기로 결정

:타이밍

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

int fooVal(int a) { 
    for (size_t i = 0; i < 1000; ++i) { 
     ++a; 
     --a; 
    } 
    return a; 
} 
int fooRef(int & a) { 
    for (size_t i = 0; i < 1000; ++i) { 
     ++a; 
     --a; 
    } 
    return a; 
} 

int main() { 
    int a = 0; 
    struct timeval stop, start; 
    gettimeofday(&start, NULL); 
    for (size_t i = 0; i < 10000; ++i) { 
     fooVal(a); 
    } 
    gettimeofday(&stop, NULL); 
    printf("The loop has taken %lu microseconds\n", stop.tv_usec - start.tv_usec); 
    gettimeofday(&start, NULL); 
    for (size_t i = 0; i < 10000; ++i) { 
     fooRef(a); 
    } 
    gettimeofday(&stop, NULL); 
    printf("The loop has taken %lu microseconds\n", stop.tv_usec - start.tv_usec); 
    return 0; 
} 

그것을 fooRef 내부에서 연산을 수행하는 동안 "look up"참조 된 값 때문에 메모리가 fooVal 인 경우보다 더 많은 시간이 걸릴 것으로 예상되었습니다.

The loop has taken 18446744073708648210 microseconds 
The loop has taken 99967 microseconds 

내가 코드를 실행할 때 그것은과 함께 시간을 만들어 값의 대부분은 서로 가까운

The loop has taken 97275 microseconds 
The loop has taken 99873 microseconds 

뭔가를 (생성 할 수 있습니다 : 그러나 입증 결과는 나를 위해 예상치 못한하기 fooRef이 조금 느리다), 가끔씩 첫 번째 실행 결과와 같은 폭발이 발생할 수있다. (fooReffooVal 루프 모두).

이 이상한 결과를 설명해 주시겠습니까?

UPD : O0 수준이 최적화되었습니다.

+5

OMG! 당신은 정말로'18446744073708648210' 마이크로 초를 기다렸습니까 ?? – WhiZTiM

+0

하려면 @WhiZTiM 물론 아닙니다. 그것은 정말 빨리 실행됩니다. 그러나 데이터는 매우 혼란 스럽습니다. –

+0

최적화를 사용 하시겠습니까? –

답변

0

저는이 분야의 전문가는 아니지만 두 번이 다소 비슷한 이유는 cache memory입니다.

당신이 (AN IA-32 아키텍처로에 말, 주소 0xaabbc125) 메모리 위치에 접근 할 필요가

, 캐시 메모리로 CPU 복사 메모리 블록 (주소 0xaabbcfff- 0xaabbc000). 메모리 읽기 및 쓰기는 매우 느림이지만 일단 캐시로 복사되면 매우 빠르게 값에 액세스 할 수 있습니다. 이것은 프로그램이 대개 같은 범위의 주소를 반복해서 요구하기 때문에 유용합니다.

같은 코드를 반복해서 실행하고 코드가 많은 메모리를 필요로하지 않기 때문에 함수가 처음 실행될 때 메모리 블록이 캐시로 한 번 복사되고, 아마도 97000 시간 단위의 대부분을 차지할 것입니다. 이후에 귀하의 fooValfooRef 함수를 호출 할 때는 이미 캐시에있는 주소가 필요하므로 몇 나노초 (약 10ns ~ 1μs)를 필요로합니다. 따라서 포인터를 참조 해제하는 것은 (참조가 포인터로 구현되기 때문에) 값에 액세스하는 것과 비교하여 시간이 약 두 배이지만 어쨌든 두 배가됩니다.

전문가 이상의 지식을 가진 사람이 내 것보다 더 훌륭하고 완벽하게 설명 할 수 있지만 여기가 무슨 일이 일어나는지 이해하는 데 도움이 될 것으로 생각됩니다.

약간의 아이디어 : start을 설정하고 루프를 시작하기 전에 fooValfooRef 함수를 몇 번 (예 : 10 번) 실행 해보십시오. 그렇게하면 (내 설명이 맞다면!) 루프를 시작할 때 메모리 블록이 이미 캐시에 있어야합니다. 즉, 캐시를 사용하지 않을 것입니다.

당신이 가진 초 고화질에 관해서는 설명 할 수 없습니다. 그러나 그 가치는 분명히 틀린 것입니다.

이것은 기능이 아닙니다.=)

+0

답장을 보내 주셔서 감사합니다. 캐싱은 실제로 타이밍을 동일하게 만들 수 있습니다. 불행히도 측정하기 전에'fooVal'과'fooRef'를 몇 번 실행하는 것에 대한 아이디어는 이전과 같지만 결과는 같습니다. –

1

gettimeofday() 함수가 운영 체제 시계에 의존하는 경우이 시계는 실제로 마이크로 초를 정확하게 처리하기위한 것이 아닙니다. 시계는 일반적으로 주기적으로 만 업데이트되며 날짜/시간 값을 사용하여 작업 할 때 초를 정확하게 표시 할 수 있도록 자주 업데이트됩니다. 마이크로 초 수준의 샘플링은 수행중인 벤치 마크와 같은 벤치 마크에서 신뢰할 수 없습니다.

테스트 시간을 훨씬 길게 설정하면이 제한을 피할 수 있습니다. 예 : 몇 초.

다른 답변 및 의견에서 언급했듯이 다양한 유형의 메모리 (레지스터, 캐시, 기본 등)에 액세스하는 효과와 다양한 최적화가 적용되는지 여부가 결과에 큰 영향을 줄 수 있습니다.

시간 샘플링 제한을 해결하는 것과 마찬가지로 작은 메모리 블록을 대상으로 한 메모리 최적화가 효과적으로 우회되도록 테스트 데이터 세트를 훨씬 크게 만들어 메모리 유형 및 최적화 문제를 다소 해결할 수 있습니다.

1

먼저 어셈블리 언어를 살펴보고 참조로 전달하고 값으로 전달하는 것 사이에 차이가 있는지 확인해야합니다.

두 번째로 상수 참고을 전달하여 함수를 동일하게 만듭니다. 값을 전달하면 원래 변수가 변경되지 않는다고 표시됩니다. 일정한 참조에 의한 전달은 동일한 원칙을 유지합니다.

내 믿음은 두 기술이 어셈블리 언어와 성능 모두에서 동일해야한다는 것입니다.