c++ - 프로파일 - 진단 도구 메모리 사용량




Linux에서 실행되는 C++ 코드를 프로파일 링하는 방법은 무엇입니까? (8)

GCC를 사용하고 있다고 가정합니다. 표준 해결책은 gprof 로 프로파일 링하는 것입니다.

프로파일 링 전에 -pg 를 컴파일에 추가해야합니다.

cc -o myprog myprog.c utils.c -g -pg

아직 시도하지는 않았지만 google-perftools 에 대한 좋은 소식을 들었습니다. 그것은 시험할만한 가치가 있습니다.

here 관련된 질문입니다.

gprofValgrind , Intel VTune , Sun DTrace 같은 다른 유행어들.

Linux에서 실행되는 C ++ 응용 프로그램이 있습니다.이 응용 프로그램을 최적화하는 중입니다. 코드의 어느 부분이 느리게 실행되는지 정확히 어떻게 확인할 수 있습니까?


단일 스레드 프로그램의 경우 igprof , Ignominous Profiler : https://igprof.org/ 사용할 수 있습니다.

샘플링 프로파일 러는 Mike Dunlavey가 작성한 샘플링 프로파일 러입니다. Mike Dunlavey는 각 함수에서 소비 한 시간 또는 메모리가 누적 된 또는 숨겨진 호출 스택 트리에 결과를 래핑합니다. 기능별.


이것은 Nazgob의 Gprof 답변에 대한 답변 입니다.

저는 Gprof를 지난 며칠 동안 사용해 왔으며 세 가지 중요한 제한 사항을 발견했습니다. 그 중 하나는 다른 곳에서는 아직 문서화되지 않았습니다.

  1. workaround 을 사용하지 않으면 멀티 스레드 코드에서 제대로 작동하지 않습니다.

  2. 함수 그래프에 의해 콜 그래프가 혼란스러워집니다. 예 : multithread() 라는 함수가 있는데,이 함수는 지정된 배열 (둘 다 인수로 전달됨)을 통해 지정된 함수를 멀티 스레드 할 수있게합니다. 그러나 Gprof는 multithread() 대한 모든 호출을 자식으로 보낸 시간을 계산할 때 동등한 것으로 봅니다. multithread() 전달하는 일부 함수는 다른 함수보다 훨씬 오래 걸리기 때문에 콜 그래프는 거의 쓸모가 없습니다. (threading이 문제가되는지 궁금한 분들은 : no, multithread() 는 선택적으로이 경우 호출 스레드에서만 모든 것을 순차적으로 실행할 수 있습니다).

  3. here 에 "통화 횟수는 카운팅을 통해 얻어지며 샘플링이 아니라 완전히 정확합니다 ..."라고되어 있습니다. 그럼에도 불구하고 제 전화 번호가 직접 호출이라고 가정되는 가장 많이 호출되는 함수에 대한 호출 통계와 두 번째 재귀 호출 (모두 자체적 인 것입니다)으로 내 호출 그래프에서 5345859132 + 784984078이라는 호출 통계를 제공합니다. 이것은 버그가 있음을 암시하기 때문에 긴 (64 비트) 카운터를 코드에 넣고 다시 실행했습니다. 나의 카운트 : 5345859132 직접 및 78094395406 자기 재귀 호출. 거기에 숫자가 많아서 Gprof의 784m 대 78bn 인 재귀 호출을 지적하겠다. 이는 100 가지 요인이다. 두 가지 실행 모두 단일 스레드 및 최적화되지 않은 코드였습니다. 하나는 컴파일 된 -g 이고 다른 하나는 -pg 입니다.

이것은 64 비트 데비안 레니에서 실행되는 GNU Gprof (Debian의 GNU Binutils) 2.18.0.20080103이었습니다.


이들은 내 코드 속도를 높이기 위해 사용하는 두 가지 방법은 다음과 같습니다.

CPU 바운드 애플리케이션 :

  1. DEBUG 모드에서 프로파일 러를 사용하여 코드의 의심스러운 부분을 식별하십시오.
  2. 그런 다음 RELEASE 모드로 전환하고 성능 변경 사항이 나타날 때까지 코드의 의심스러운 부분을 주석 처리하십시오 (아무 것도 붙이지 마십시오).

I / O 바운드 응용 프로그램의 경우 :

  1. RELEASE 모드에서 프로파일 러를 사용하여 코드의 의심스러운 부분을 식별하십시오.

주의

프로파일 러가없는 경우 불쌍한 사람의 프로파일 러를 사용하십시오. 응용 프로그램을 디버깅하는 동안 일시 중지를 누르십시오. 대부분의 개발자 스위트는 주석 처리 된 행 번호로 어셈블리를 시작합니다. 통계적으로 CPU주기의 대부분을 차지하는 지역에 착륙 할 가능성이 있습니다.

CPU의 경우 DEBUG 모드에서 프로파일 링을하는 이유는 RELEASE 모드에서 프로파일 링을 시도하면 컴파일러가 수학을 줄이거 나 루프를 벡터화하고 어셈블 할 때 코드를 un-mappable mess으로 변환하기 때문입니다. un-mappable mess은 어셈블리가 최적화 상태에서 소스 코드와 일치하지 않을 수 있기 때문에 프로파일 러가 너무 오래 걸리는 부분을 명확하게 식별 할 수 없음을 의미합니다 . RELEASE 모드의 성능 (예 : 타이밍 감지)이 필요한 경우 사용 가능한 성능을 유지하는 데 필요한만큼 디버거 기능을 비활성화하십시오.

I / O 바인딩의 경우, 프로파일 러는 I / O 작업이 외부에서 공유 라이브러리 (대부분의 경우)에 링크되어 있거나 최악의 경우 sys- 호출 인터럽트 벡터 (프로파일 러에 의해 쉽게 식별 가능).


프로파일 러를 사용하는 것이 목표라면, 제안 된 프로파일 중 하나를 사용하십시오.

그러나 서둘러서 주관적으로 느려지는 동안 디버거에서 수동으로 프로그램을 중단 할 수있는 경우 성능 문제를 찾는 간단한 방법이 있습니다.

여러 번 중지하고 매번 호출 스택을 살펴보십시오. 시간의 일부 비율, 20 % 또는 50 %를 낭비하는 코드가있는 경우, 이는 각 샘플의 행위에서이를 포착 할 확률입니다. 그래서 대략 보는 샘플의 비율입니다. 교양있는 추측이 필요하지 않습니다. 문제가 무엇인지 추측하면이 사실이 입증되거나 반증됩니다.

크기가 다른 여러 성능 문제가있을 수 있습니다. 당신이 그 중 하나를 청소하면 나머지 것들은 더 큰 비율을 차지할 것이고, 후속 패스에서 더 쉽게 발견 될 것입니다. 이 확대 효과 는 여러 문제에 걸쳐 복합적으로 적용될 경우 엄청난 속도 향상 요인이 될 수 있습니다.

주의 사항 : 프로그래머는 스스로 사용하지 않는 한이 기술에 회의적 인 경향이 있습니다. 그들은 프로파일 러가이 정보를 제공한다고 말하지만, 전체 호출 스택을 샘플링 한 다음 임의의 샘플 세트를 검사하게하는 경우에만 해당됩니다. 요약은 통찰력이 손실되는 곳입니다. 콜 그래프는 동일한 정보를 제공하지 않습니다.

  1. 그들은 지시 수준에서 요약하지 않으며,
  2. 재귀가 발생하면 혼란스런 요약을 제공합니다.

그들은 장난감 프로그램에서만 작동한다고 말하고 실제로 프로그램에서 작동 할 때 더 큰 프로그램에서 더 잘 작동하는 것으로 보인다. 찾기가 더 어려워지는 경향이 있기 때문이다. 그들은 때로는 문제가되지 않는 것을 발견한다고 말하지만, 한 번만 본다면 사실 일뿐입니다. 하나 이상의 샘플에서 문제가 발생하면 실제입니다.

추신 : Java 에서처럼 특정 시점에 스레드 풀의 호출 스택 샘플을 수집하는 방법이 있으면 다중 스레드 프로그램에서도이 작업을 수행 할 수 있습니다.

조달청 거친 일반성으로, 소프트웨어에서보다 많은 추상화 계층을 사용할수록 성능 문제의 원인 인 속도가 빨라지고 속도가 빨라질 수 있습니다.

추가 : 명확하지는 않지만 스택 샘플링 기술은 재귀가 발생하더라도 똑같이 잘 작동합니다. 그 이유는 명령을 제거하여 저장되는 시간은 샘플 내에서 발생할 수있는 횟수와 관계없이 명령을 포함하는 샘플의 비율로 근사화되기 때문입니다.

내가 자주 듣는 또 다른 반대는 : " 그것은 임의의 장소를 무작위로 멈출 것이고, 진짜 문제를 놓치게 될 것 "이라고합니다. 이것은 실제 문제가 무엇인지에 대한 선행 개념을 가짐으로써 생깁니다. 성능 문제의 핵심 속성은 기대치를 무시한다는 것입니다. 샘플링은 문제가 있음을 알려주고 첫 번째 반응은 불신입니다. 그것은 자연스럽지 만 문제가 실제로 발생하면 확실하게 알 수 있으며 그 반대의 경우도 마찬가지입니다.

ADDED : 어떻게 작동하는지 베이지안 설명을하자. 호출 (call)에있는 몇 가지 명령어 (call 또는 그렇지 않으면)가 시간의 일부 f 를 스택한다고 가정하면 (따라서 많은 비용이 들게됩니다). 단순화를 위해, 우리는 f 가 무엇인지 알지 못하지만, 0.1, 0.2, 0.3, ... 0.9, 1.0 중 하나라고 가정하고, 이들 가능성 각각의 사전 확률은 0.1이므로 이러한 모든 비용은 동일합니다 아마 선험적이다.

그리고 우리는 단지 2 개의 스택 샘플을 취한다고 가정하고, 두 샘플 모두에 대해 지침 I 를 관찰 o=2/2 합니다. 이것은 다음과 같이 I 의 주파수 f 에 대한 새로운 추정치를 제공합니다.

Prior                                    
P(f=x) x  P(o=2/2|f=x) P(o=2/2&&f=x)  P(o=2/2&&f >= x)  P(f >= x)

0.1    1     1             0.1          0.1            0.25974026
0.1    0.9   0.81          0.081        0.181          0.47012987
0.1    0.8   0.64          0.064        0.245          0.636363636
0.1    0.7   0.49          0.049        0.294          0.763636364
0.1    0.6   0.36          0.036        0.33           0.857142857
0.1    0.5   0.25          0.025        0.355          0.922077922
0.1    0.4   0.16          0.016        0.371          0.963636364
0.1    0.3   0.09          0.009        0.38           0.987012987
0.1    0.2   0.04          0.004        0.384          0.997402597
0.1    0.1   0.01          0.001        0.385          1

                  P(o=2/2) 0.385                

마지막 열에는 예를 들어 f > = 0.5 일 확률이 92 %로 이전 가정의 60 %보다 높습니다.

이전 가정이 다른 경우를 가정 해보십시오. P (f = 0.1)가 .991 (거의 확실 함)이라고 가정하고 다른 모든 가능성은 거의 불가능합니다 (0.001). 즉, 우리의 이전 확신은 I 싸다는 것입니다. 그럼 우리는 얻는다.

Prior                                    
P(f=x) x  P(o=2/2|f=x) P(o=2/2&& f=x)  P(o=2/2&&f >= x)  P(f >= x)

0.001  1    1              0.001        0.001          0.072727273
0.001  0.9  0.81           0.00081      0.00181        0.131636364
0.001  0.8  0.64           0.00064      0.00245        0.178181818
0.001  0.7  0.49           0.00049      0.00294        0.213818182
0.001  0.6  0.36           0.00036      0.0033         0.24
0.001  0.5  0.25           0.00025      0.00355        0.258181818
0.001  0.4  0.16           0.00016      0.00371        0.269818182
0.001  0.3  0.09           0.00009      0.0038         0.276363636
0.001  0.2  0.04           0.00004      0.00384        0.279272727
0.991  0.1  0.01           0.00991      0.01375        1

                  P(o=2/2) 0.01375                

현재 P (f> = 0.5)는 26 %로 이전 가정의 0.6 %보다 높습니다. 그래서 Bayes는 우리가 추정 한 I 의 추정치를 업데이트 할 수있게 해줍니다. 데이터 양이 적 으면 비용이 무엇인지 정확히 알려주지 않으며 수정 만 할만 큼 큰 것입니다.

그것을 보는 또 다른 방법은 승계규칙 이라고합니다. 동전을 2 번 뒤집어 두 번 올린다면 동전의 가중치에 대해 어떻게 알 수 있습니까? 존중받는 대답은 평균값 (조회수 + 1) / (시도 횟수 + 2) = (2 + 1) / (2 + 2) = 75 % 인 베타 배포판이라고 말할 수 있습니다.

(핵심은 우리가 한 번 이상을 본다는 것입니다. 우리가 한 번만 본다면, f > 0을 제외하고는 많이 알려주지 않습니다.)

따라서 샘플 수가 매우 적어도 지침의 비용에 대해 많은 것을 알 수 있습니다. (그리고 그것들은 주파수에 평균적으로 그들의 비용에 비례하여 보일 것입니다 n 샘플을 nf+/-sqrt(nf(1-f)) f 를 비용으로한다면, nf+/-sqrt(nf(1-f)) 샘플에 나타납니다. , n=10 , f=0.3 , 즉 3+/-1.4 샘플).

ADDED : 측정 및 무작위 스택 샘플링의 차이에 대해 직관적 인 느낌을줍니다.
벽 시계 시간에도 스택을 샘플링하는 프로파일 러가 있지만 측정 값 (또는 핫 경로 또는 "병목 현상"이 쉽게 숨길 수있는 핫 스폿)이 나옵니다 . 그들이 당신을 보여주지 않는 (그리고 그들은 쉽게) 실제 표본 자체입니다. 그리고 목표가 병목 현상을 찾는 것이라면, 평균적 으로 2 분의 1을 소요 시간으로 나눈 것입니다. 따라서 30 %의 시간이 소요되면 평균적으로 2 / .3 = 6.7 개의 샘플이 표시되고 20 개의 샘플이 표시되는 확률은 99.2 %입니다.

다음은 측정 값 검사와 스택 샘플 검사의 차이점을 보여주는 오프 캐프 (off-the-cuff) 그림입니다. 병목 현상은 이와 같이 하나의 큰 얼룩이거나 여러 작은 얼룩일 수 있습니다.

측정은 수평입니다. 특정 루틴에 걸리는 시간을 알려줍니다. 샘플링은 수직입니다. 그 순간 전체 프로그램이 수행하는 것을 피할 수있는 방법이 있고 두 번째 샘플 에서 볼 수 있다면 병목 현상을 발견했습니다. 그것이 차이를 만드는 것입니다. 얼마나 많은 시간을 낭비했는지에 대한 이유를 모두 보았습니다.


필자는 Valgrind와 Callgrind를 프로파일 링 도구 모음의 기반으로 사용할 것입니다. 중요한 것은 Valgrind가 기본적으로 가상 머신이라는 것입니다.

(위키 백과) Valgrind는 본질적으로 동적 재 컴파일을 포함하여 JIT (Just-In-Time) 컴파일 기술을 사용하는 가상 시스템입니다. 원래 프로그램의 어떤 것도 호스트 프로세서에서 직접 실행되지 않습니다. Valgrind는 먼저 프로그램을 중립적 인 SSA 기반 형식 인 중간 표현 (IR)이라는 임시적이고 간단한 형식으로 변환합니다. 변환 후 Valgrind가 IR을 기계어 코드로 변환하고 호스트 프로세서가이를 실행할 수있게하기 전에 IR (도구) (아래 참조)은 IR에서 원하는 모든 변환을 자유롭게 수행 할 수 있습니다.

Callgrind는 프로파일 러를 기반으로합니다. 주요 이점은 신뢰할 수있는 결과를 얻기 위해 몇 시간 동안 응용 프로그램을 실행할 필요가 없다는 점입니다. Callgrind가 프로빙 프로파일 러가 아니기 때문에 단 1 초만이라도 견고하고 안정적인 결과를 얻으려면 충분합니다.

Valgrind의 또 다른 도구는 Massif입니다. 힙 메모리 사용량을 프로파일 링하는 데 사용합니다. 그것은 위대한 작품. 그것이하는 일은 메모리 사용량의 스냅 샷을 제공한다는 것인데, WHAT가 메모리의 어떤 비율을 차지하고 WHO는 그것을 저장했는지에 대한 자세한 정보를 제공합니다. 이러한 정보는 응용 프로그램 실행 시점에 따라 다릅니다.


valgrind --tool=callgrind 실행에 대한 대답은 몇 가지 옵션 없이는 완전하지 않습니다. 우리는 일반적으로 Valgrind에서 느린 시작 시간의 10 분을 프로파일 링하고 싶지 않으며 어떤 작업을 수행 할 때 프로그램을 프로파일 링하려고합니다.

그래서 이것이 내가 추천하는 것입니다. 프로그램을 먼저 실행하십시오.

valgrind --tool=callgrind --dump-instr=yes -v --instr-atstart=no ./binary > tmp

이제 작동하고 프로파일 링을 시작하려면 다른 창에서 실행해야합니다.

callgrind_control -i on

이것은 프로파일 링을 켭니다. 이 기능을 끄고 전체 작업을 중지하려면 다음을 사용하십시오.

callgrind_control -k

이제 callgrind.out. *이라는 파일이 현재 디렉토리에 있습니다. 프로파일 링 결과를 보려면 다음을 사용하십시오.

kcachegrind callgrind.out.*

다음 창에서 "자체"열 머리글을 클릭하는 것이 좋습니다. 그렇지 않으면 "main ()"이 가장 시간이 많이 걸리는 작업임을 나타냅니다. "자아"는 각 기능 자체가 부양 가족과 함께가 아니라 시간이 얼마나 걸렸는지를 보여줍니다.


Valgrind, callgrind 및 kcachegrind를 사용하십시오.

valgrind --tool=callgrind ./(Your binary)

callgrind.out.x를 생성합니다. kcachegrind를 사용하여 읽으십시오.

gprof (add -pg) 사용 :

cc -o myprog myprog.c utils.c -g -pg 

(다중 스레드, 함수 포인터에는 좋지 않음)

google-perftools 사용 :

시간 샘플링, I / O 및 CPU 병목 현상을 사용합니다.

인텔 VTune이 최고입니다 (교육 목적으로는 무료).

기타 : AMD Codeanalyst (이후 AMD CodeXL로 대체), OProfile, 'perf'도구 (apt-get install linux-tools)





profiling