2010-06-27 5 views
8

그렇기 때문에 성능 프로파일 링에 대한 몇 가지 질문이 있지만 전체적인 그림을 찾지 못했습니다. 관련된 몇 가지 문제가 있으며 대부분 Q & A는 한 번에 몇 가지를 무시하거나 제안을 정당화하지 않습니다.C++에서 코드 속도를 테스트하는 가장 좋은 방법은 프로파일 러가 없거나 시도하는 것이 합리적이지 않습니까?

무엇이 궁금합니다. 똑같은 일을하는 두 가지 함수가 있고 속도의 차이에 대해 궁금한 점이 있다면, 타이머가있는 외부 도구없이 테스트하는 것이 맞습니까? 아니면 테스트에서 컴파일 된 결과가 결과에 많은 영향을 줍니까?

C + + 프로그래머로서 합리적이라면 외부 도구를 사용하는 것보다 훨씬 간단하므로 최선의 방법을 알고 싶습니다. 이해가된다면 모든 가능한 함정을 진행하십시오.

이 예제를 고려하십시오.

#include <algorithm> 
#include <ctime> 
#include <iostream> 

typedef unsigned char byte; 

inline 
void 
swapBytes(void* in, size_t n) 
{ 
    for(size_t lo=0, hi=n-1; hi>lo; ++lo, --hi) 

     in[lo] ^= in[hi] 
    , in[hi] ^= in[lo] 
    , in[lo] ^= in[hi] ; 
} 

int 
main() 
{ 
     byte arr[9]  = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' }; 
    const int  iterations = 100000000; 
     clock_t begin  = clock(); 

    for(int i=iterations; i!=0; --i) 

     swapBytes(arr, 8); 

    clock_t middle = clock(); 

    for(int i=iterations; i!=0; --i) 

     std::reverse(arr, arr+8); 

    clock_t end = clock(); 

    double secSwap = (double) (middle-begin)/CLOCKS_PER_SEC; 
    double secReve = (double) (end-middle )/CLOCKS_PER_SEC; 


    std::cout << "swapBytes, for: " << iterations << " times takes: " << middle-begin 
      << " clock ticks, which is: " << secSwap << "sec."   << std::endl; 

    std::cout << "std::reverse, for: " << iterations << " times takes: " << end-middle 
      << " clock ticks, which is: " << secReve << "sec."   << std::endl; 

    std::cin.get(); 
    return 0; 
} 

// Output: 

// Release: 
// swapBytes, for: 100000000 times takes: 3000 clock ticks, which is: 3sec. 
// std::reverse, for: 100000000 times takes: 1437 clock ticks, which is: 1.437sec. 

// Debug: 
// swapBytes, for: 10000000 times takes: 1781 clock ticks, which is: 1.781sec. 
// std::reverse, for: 10000000 times takes: 12781 clock ticks, which is: 12.781sec. 

이슈 : 다음 코드는 같은 일을하고있는 2 가지 방법을 보여줍니다

사용하는 방법과 CPU 시간은 실제로 질문 아래의 코드에 의해 소비 얻을, 타이머
  1. ?
  2. 컴파일러 최적화의 효과는 무엇입니까? (이 함수는 바이트를 앞뒤로 스왑하기 때문에 가장 효율적인 것은 아무 것도하지 않는 것입니다)?
  3. 여기에 제시된 결과를 고려해 볼 때 정확하다고 생각하십니까? (여러 번 실행하면 매우 비슷한 결과가 나옵니다.) 그렇다면 사용자 정의 함수의 단순성을 고려하여 std :: reverse가 너무 빨라지는 방식을 설명 할 수 있습니까? 나는이 테스트에서 사용한 vC++ 버전의 소스 코드가 없지만 GNU의 here is the implementation입니다. 그것은 나를 위해 완전히 이해할 수없는 함수 iter_swap으로 귀결됩니다. 이것은 또한 커스텀 함수보다 두 배 빠르다고 예상 할 수 있습니까? 만약 그렇다면 왜?

사색 :

  1. 두 개의 고정밀 타이머가 제안되고있는 것 같다 clock()QueryPerformanceCounter (Windows의 경우). 분명히 우리는 코드가 아닌 실시간으로 CPU 시간을 측정하고 싶지만, 이해할 수있는 한 이러한 기능은 기능을 제공하지 않으므로 시스템의 다른 프로세스가 측정을 방해합니다. This page gnu C 라이브러리에있는 모순 된 것처럼 보일지 만, 중단 점을 vC++에 넣을 때 디버깅 된 프로세스는 일시 중지 되었더라도 클럭 틱이 많이 발생합니다 (gnu에서 테스트하지 않았습니다). 이에 대한 대체 카운터가 누락 되었습니까? 아니면 최소한 특수한 라이브러리 또는 클래스가 필요합니까? 그렇지 않다면이 예제에서 클럭이 충분합니까? 아니면 QueryPerformanceCounter를 사용할 이유가 있습니까?

  2. 디버깅, 분해 및 프로파일 링 도구가 없으면 무엇을 알 수 있습니까? 실제로 일어나는 일이 있습니까? 함수 호출이 인라인 되었습니까? 디버거를 체크인 할 때 실제로 바이트가 바뀌지 만 테스팅보다는 왜 이론적으로 알 수 있습니다.

모든 방향에 감사드립니다.swapBytes 기능이 이제 빨리 표준 리버스 ::로 실행 tojas에서 hint

갱신

감사합니다. 나는 바이트의 경우 임시 복사본이 단지 레지스터 여야한다는 것을 깨닫지 못했고 따라서 매우 빠르다. 우아함은 당신을 눈 멀게 할 수 있습니다. 공정 저점 Windows Management Instrumentation :

inline 
void 
swapBytes(byte* in, size_t n) 
{ 
    byte t; 

    for(int i=0; i<7-i; ++i) 
    { 
     t  = in[i]; 
     in[i] = in[7-i]; 
     in[7-i] = t; 
    } 
} 

덕분에 ChrisW에서 tip에 난 창문에 당신이 (당신의 읽기)를 소비하는 실제 CPU 시간을 얻을 수있는 것으로 나타났습니다. 이것은 정밀도가 높은 카운터보다 확실히 더 흥미롭게 보입니다.

+0

어떤 OS에 대해 궁금하십니까? 이전에 타이밍 코드를 썼을 때 다양한 OS에는 올바른 시계에 대해 다른 API 호출이있었습니다. –

+0

WindowsXP에서 테스트 중이지만 다른 OS에 대한 정보는 – nus

+0

입니다. Profiler를 처음 시도한 후에는 Profiler없이 시도할만한 가치가 있습니다. –

답변

4

우리는 우리의 코드의 CPU 시간이 아닌 실제 시간을 측정 좋아하지만 것까지 내가, 이러한 기능을 이해 이 기능을 제공하지 마십시오. 따라서 시스템의 다른 프로세스가 측정을 방해합니다. 시간의 상당 길이

  • 테스트, 즉 몇 초 (A 테스트하여 예 : 그 벽 시계 시간 및 CPU 시간을 보장하기 위해, 두 가지를

약 같은 일이다 반복의 많은 수천의 반복)

  • 내가 테스트하고있는 것을 제외하고는 기계가 다소 상대적으로 유휴 상태 일 때 테스트. 당신이 스레드 당/더 정확하게 CPU 시간을 측정하려면

  • 는 다른 방법으로, 그 성능 카운터로 사용할 수 (예를 들어 perfmon.exe 참조).

    디버깅, 분해 및 프로파일 링 도구가 없으면 무엇을 알 수 있습니까?

    I/O가 상대적으로 느린 경우를 제외하면 거의 아무것도 아닙니다.

    +0

    perfmon, 네, 나를 상기시켜 주셔서 감사합니다. 나는 그것이 존재한다는 것을 알았고, 그것은 매우 편리하다.하지만 우리 프로그램에서이 정보를 얻기 위해 사용할 수있는 시스템 호출이 있는지 아는가? – nus

    +0

    @ufotds - 오래 전에, 나는 털이 많은 호출을 사용하여 레지스트리의 숨겨진 "성능"섹션을 읽었습니다 (호출은 쉽지만 반환 된 바이너리 데이터를 구문 분석하지 않았습니다). 요즘은 "WMI"API로 추상화 될지 모릅니다. – ChrisW

    1

    프로필러에 대해 뭔가 있습니까? 그들은 1 톤을 돕는다. WinXP를 사용하고 있으므로 vtune을 시험 사용해보십시오. 콜 그래프 샘플링 테스트를 시도하고 호출되는 함수의 자체 시간과 총 시간을 살펴보십시오. 프로그램을 조정할 수있는 더 좋은 방법은 없기 때문에 어셈블리 천재가 아닌 가장 빠른 프로그램을 만들 수 있습니다.

    일부 사람들은 프로필러에게 알레르기가있는 것 같습니다. 나는 그 중 하나 였고 내 핫스팟이 어디에 있는지 잘 알고 있다고 생각했습니다. 나는 종종 알고리즘의 비효율적 인 부분에 대해서는 올바르지 만, 더 많은 미세 최적화의 경우에 대해서는 항상 부정확합니다. 논리를 변경하지 않고 함수를 다시 작성하는 것만으로 (예 : 순서 재 지정, 예외적 인 사례 코드를 별도의 인라인되지 않은 함수에 넣는 것 등) 함수를 12 배 빠르게 만들 수 있으며 최상의 분해 전문가조차도 일반적으로이를 예측할 수 없습니다 프로파일 러없이.

    단순한 타이밍 테스트만으로도 매우 문제가됩니다. 현재 테스트는 그렇게 나쁘지는 않지만 최적화 프로그램이 데드 코드를 최적화하고 본질적으로 nop을 수행하거나 심지어는 전혀 수행하지 않는 데 걸리는 시간을 테스트하는 방식으로 타이밍 테스트를 작성하는 것은 매우 일반적인 실수입니다. 컴파일러가이 작업을 수행하지 않도록 해시를 해석 할 수있는 지식이 있어야합니다.

    또한 이와 같은 타이밍 테스트에는 동일한 루프에서 코드를 반복해서 실행해야하기 때문에 테스트 결과를 크게 편향시키는 경향이 있습니다.이 코드는 코드의 효과를 테스트하는 경향이 있습니다. 모든 분기 예측이 완벽하게 작동하는 캐시 평균, 실제 사례를 보여주지 않고 최상의 사례 시나리오를 보여주는 경우가 많습니다.

    현실 세계 타이밍 테스트에 따라 조금 더 좋습니다. 귀하의 응용 프로그램이 높은 수준에서 무엇을 할 것인가에 더 가깝습니다. 어느 정도의 시간이 걸릴지에 대한 구체적인 정보는 제공하지 않지만 프로파일 러의 의도와 정확히 일치합니다.

    +0

    전체 프로그램의 성능을 최적화하기 위해 프로파일 러를 사용했지만 몇 가지 간단한 기능에 대한 호기심을 가지므로 몇 가지 타이머를 호출하는 것은 설명서를 선택, 다운로드, 설치, 읽기, 프로파일 러 작업을하는 것보다 번거롭지 않습니다. 전체적으로, 이와 같은 기본 사항을 이해하고 소프트웨어가 합리적인 성능을 발휘하게하는 것 사이에는 차이가 있습니다. 후자의 경우 기꺼이 프로파일 러를 사용하고 표준 역의 속도는 : 역으로 거의 걱정하지 않을 것입니다. 만약 내가 기가 바이트를 역전시키지 않는다면 ... – nus

    +0

    단지 성능이 뛰어나고 예외적 인 성능이 아니라면 타이밍 테스트가 할 수 있습니다. 그러나 프로파일 러는 배우는 데 약간의 시간이 걸릴 수 있지만 실제로는 한 번만해야 할 일임을 명심해야합니다. vtune에서 호출 그래프 샘플링 마법사를 사용하여 exe 파일을 선택하고 실행하십시오.유일한 까다로운 부분은 프로젝트 설정을 수정해야한다는 것입니다 (http://software.intel.com/en-us/articles/performance-tools-for-software-developers-using-the-intel-compilers-with- vtune-analyzer-or-intel-thread-profiler /). 그 후에 그래프를보고 실행하십시오. – stinky472

    +0

    ... self time은 다른 함수/메소드에 대한 호출을 제외하고 주어진 함수/클래스 메소드에서 cpu가 소비 한 시간을 알려주고 총 시간은 함수/메소드를 포함하여 함수/메소드에서 소비 한 총 시간을 나타냅니다. 다른 함수/메소드를 호출하는 데 소요 된 시간. 그것은 당신이 테스트에 호출 된 모든 함수에 소비 된 시간을 얻는 것을 제외하고는 타이밍 테스트와 같습니다. 메인에서 보낸 총 시간을 포함합니다. – stinky472

    1

    귀하의 질문에 모두 답변 할 수있는 능력이있는 사람이라면 귀하의 모든 질문에 답하기에는 너무 바쁠 것입니다. 실제로는 잘 정의 된 단일 질문을하는 것이 더 효과적 일 수 있습니다. 그런 식으로 당신이 수집하고 지혜로가는 길에있을 수있는 잘 정의 된 해답을 얻기를 바랍니다.

    어쨌든, 아마도 Windows에서 사용할 시계에 대한 질문에 대답 할 수 있습니다.

    clock()은 고정밀 클럭으로 간주되지 않습니다. CLOCKS_PER_SEC의 값을 보면 1 밀리 초의 분해능을 볼 수 있습니다. 이것은 매우 긴 루틴이나 10000 반복의 루프를 타이밍하는 경우에만 적합합니다. 클럭()으로 측정 할 수있는 시간을 얻기 위해 간단한 방법 10000을 반복해서 시도해 본다면 컴파일러는 모든 것을 멀리 떨어 뜨리고 최적화해야 할 책임이 있습니다.

    그래서, 정말, 사용하는 유일한 시계 QueryPerformanceCounter에()이다

    분명히
    2

    주요 질문에 답하기 위해 "역방향"알고리즘은 요소를 배열에서 바꾸고 배열의 요소에서 작동하지 않습니다.

    2

    두 가지 질문을하는 것이 안전합니까?

    • 어느 것이 더 빠르고 어느 정도입니까?

    • 왜 더 빠릅니까?

    처음에는 고정밀 타이머가 필요하지 않습니다. "충분히 오래"실행하고 정밀도가 낮은 타이머로 측정해야합니다. (저는 구식이고 손목 시계는 멈춤 기능을 가지고 있습니다.)

    둘째로, 확실히 디버거에서 코드를 실행하고 지시에 따라 단계를 수행 할 수 있습니다 수평. 기본 작업은 매우 간단하므로 기본주기에 필요한 지침의 수를 대략적으로 쉽게 확인할 수 있습니다.

    간단합니다. 성과는 어려운 주제가 아닙니다. 일반적으로, 사람들은 this is a simple approach을 표시하려고합니다.

    +0

    네, 2 이상 ...하지만 어떤 이유로 시각적 인 디버거가 std :: reverse로 들어갈 수는 없지만 릴리스 모드에서만 시도했습니다. 이제는 디버그에서 작동하고 실제로 pooners 등을 검증하는 것 외에 swapBytes의 업데이트에서 작성한 내용을 정확히 볼 수 있습니다. – nus

    2

    고해상도 타이밍이 필요한 경우 Windows에서 QueryPerformanceCounter를 사용하십시오. 카운터 정확도는 CPU에 따라 다르지만 클럭 펄스 당 올라갈 수 있습니다. 그러나 실제 작업에서의 프로파일 링은 항상 더 좋은 아이디어입니다.

    +0

    또한 호출 시점에 따라 다릅니다. 많은 CPU가 클럭 주파수를 동적으로 변경합니다. –

    -3

    뭐? 프로파일 러없이 속도를 측정하는 방법은 무엇입니까? 속도가 인 바로 그 행동은 프로파일로입니다! 질문은 "어떻게 내 프로파일 러를 쓸 수 있습니까?"하지"그리고 그 대답은 분명하다. "게다가

    , 당신은 처음부터 완전 무효화이 모든 의미가 추구. 무의미에 대한

    -1에 std::swap를 사용한다.

    +0

    std :: reverse는 std :: swap 주위의 래퍼입니다. – nus

    +2

    나는 downvote하지 않았지만, 내가 배운 한 가지는 사람들에게 쉬운 일입니다. 우리 모두는 서로 다른 수준의 배경을 가지고 있으며 다른 사람들의 지혜를 나눌 수 있습니다. 분명히 당신은 공유 할 수있는 지혜가 있습니다. 그것은 SO에 대한 좋은 점입니다. –

    +0

    마이크 : 요점. 너는 내가있는 것보다 더 참을성이있어. 그건 제쳐두고,이 질문은 유효하다고 생각합니까? 나는 현명한 질문이 여기에 드물다는 것을 빨리 배우고 있습니다. 최적화 질문만으로도이 사람들이 프로그래밍하고있는 응용 프로그램에 대해 걱정할 필요가 있습니다. 내 은행이 프로그래머를 고용하여 자신의 std :: swap을 실행해야하는지 궁금하지 않기를 바랍니다. :) – John

    2

    (이 대답은 Windows XP와 32 비트 VC++ 컴파일러에만 해당됩니다.)

    작은 비트의 코드 타이밍을 내리는 가장 쉬운 방법은 CPU의 타임 스탬프 카운터입니다.이 값은 64 비트 값이고, 지금까지 실행되는 CPU 사이클 수의 대략적인 수치이며, 실제로 얻을 수있는 해상도와 거의 같습니다. 그 (것)들이 서 있기 때문에 특히 유용하지 않다, 그러나 당신이 그 (것)들을 그 방법으로 비교할 수있는 각종 경쟁적인 접근의 몇몇 뛰기를 평균하면. 결과는 약간 시끄 럽지만 비교 목적으로는 여전히 유효합니다. (완료 대기 완료되지 않은 지침이되지 않도록하기 위해이되는 cpuid 명령.)

    LARGE_INTEGER tsc; 
    __asm { 
        cpuid 
        rdtsc 
        mov tsc.LowPart,eax 
        mov tsc.HighPart,edx 
    } 
    

    :

    는, 타임 스탬프 카운터를 읽어 다음과 같은 코드를 사용하려면 이 접근법에 주목할 가치가있는 네 가지가 있습니다.

    첫째, 인라인 어셈블리 언어로 인해 MS의 x64 컴파일러에서 그대로 작동하지 않습니다. (기능이있는 .ASM 파일을 만들어야합니다. 독자를위한 연습으로 세부 사항을 알지 못합니다.)

    두 번째로주기 카운터가 서로 다른 동기화되지 않는 문제를 방지하려면 코어/쓰레드/무엇을 가지고 있느냐에 따라 하나의 특정 실행 단위에서만 실행되도록 프로세스의 선호도를 설정해야 할 수도 있습니다. (다시 ... 그렇지 않을 수도 있습니다.)

    셋째, 생성 된 어셈블리 언어를 확인하여 컴파일러가 대략 예상 한 코드를 생성하는지 확인하십시오. 제거되는 코드의 일부를 조심하십시오. 함수가 인라인됩니다.

    마지막으로, 결과는 다소 시끄 럽습니다. 이 사이클은 모든 프로세스에 걸리는 시간을 계산하는 데 사용됩니다. 예를 들어 캐시 대기, 다른 프로세스 실행에 소비 된 시간, OS 자체에서 소비 된 시간 등이 있습니다. 불행히도 Windows 프로세스에서는 프로세스를 수행하는 것이 불가능합니다. 따라서 테스트 할 코드를 여러 번 (수만 개) 실행하고 평균을 산출하는 것이 좋습니다. 이것은 매우 교활하지는 않지만, 어떤면에서도 필자에게 유용한 결과를 가져다 준 것으로 보인다.

    +0

    안녕하세요,이 스 니펫을 보내 주셔서 감사합니다. 분명히 WMI를 사용하여 프로세스를 측정 할 수 있기 때문에이 목적을위한 실용적인 가치는 아닌데 간단한 C++ 프로그램에 붙여 넣은 그대로 그대로 작동합니다. 내 어셈블러 지식이 다소 황폐하기 때문에 인라인 어셈블러를 사용한 것은 처음입니다 ... – nus

    관련 문제