Linux에서 실행되는 C ++ 응용 프로그램이 있는데 최적화 과정에 있습니다. 코드의 어느 영역이 느리게 실행되고 있는지 어떻게 알 수 있습니까?
code
프로파일 러입니다. 그러나 우선 순위 반전, 캐시 앨리어싱, 리소스 경합 등은 모두 최적화 및 성능의 요소가 될 수 있습니다. 사람들이 내 느린 코드 로 정보를 읽는다고 생각합니다 . FAQ는이 스레드를 참조하고 있습니다.
Linux에서 실행되는 C ++ 응용 프로그램이 있는데 최적화 과정에 있습니다. 코드의 어느 영역이 느리게 실행되고 있는지 어떻게 알 수 있습니까?
code
프로파일 러입니다. 그러나 우선 순위 반전, 캐시 앨리어싱, 리소스 경합 등은 모두 최적화 및 성능의 요소가 될 수 있습니다. 사람들이 내 느린 코드 로 정보를 읽는다고 생각합니다 . FAQ는이 스레드를 참조하고 있습니다.
답변:
프로파일 러를 사용하는 것이 목표라면 제안 된 프로파일 러 중 하나를 사용하십시오.
그러나 서두르고 주관적으로 느리게 진행되는 동안 디버거에서 프로그램을 수동으로 중단 할 수있는 경우 성능 문제를 찾는 간단한 방법이 있습니다.
여러 번 중지하고 매번 호출 스택을보십시오. 시간의 일부, 20 % 또는 50 %를 낭비하는 코드가있는 경우 각 샘플의 동작에서 코드를 잡을 확률이 있습니다. 이것은 대략 샘플의 비율입니다. 교육받은 추측은 필요하지 않습니다. 문제가 무엇인지 추측하면 문제를 증명하거나 반증합니다.
크기가 다른 여러 성능 문제가있을 수 있습니다. 그중 하나를 청소하면 나머지 패스는 나머지 패스에서 더 큰 비율을 차지하고 쉽게 찾을 수 있습니다. 이 확대 효과 는 여러 문제로 인해 복합화 될 때 실제로 막대한 속도 향상 요소로 이어질 수 있습니다.
주의 사항 : 프로그래머는 스스로 사용하지 않는 한이 기술에 회의적입니다. 프로파일 러가이 정보를 제공한다고 말하지만 전체 호출 스택을 샘플링 한 다음 임의의 샘플 세트를 검사 할 수있는 경우에만 해당됩니다. (요약은 통찰력을 잃는 곳입니다.) 호출 그래프는 동일한 정보를 제공하지 않습니다.
또한 실제로는 모든 프로그램에서 작동 할 때 장난감 프로그램에서만 작동한다고 말하고 더 큰 프로그램에서 더 잘 작동하는 것으로 보입니다. 그들은 때때로 문제가되지 않는 것을 발견한다고 말할 것입니다. 그러나 당신이 무언가를 한 번 본다면 그것은 사실 입니다. 둘 이상의 샘플에 문제가 있으면 실제로 발생합니다.
PS Java에서와 같이 특정 시점에 스레드 풀의 콜 스택 샘플을 수집하는 방법이있는 경우 다중 스레드 프로그램에서도 수행 할 수 있습니다.
PPS 대략적으로, 소프트웨어에 추상화 계층이 많을수록 성능 문제의 원인이되고 속도를 높일 수있는 기회가 될 가능성이 높습니다.
추가 : 분명하지는 않지만 스택 샘플링 기술은 재귀가있을 때 똑같이 잘 작동합니다. 그 이유는 명령을 제거하여 저장되는 시간은 샘플 내에서 발생할 수있는 횟수에 관계없이 명령을 포함하는 샘플의 비율에 의해 근사되기 때문입니다.
내가 종종 듣게되는 또 다른 반대는 " 임의의 장소를 무작위로 멈출 것이고 실제 문제를 놓치게 될 것이다 "입니다. 이것은 실제 문제가 무엇인지에 대한 사전 개념을 가지고 있습니다. 성능 문제의 주요 속성은 기대를 무시한다는 것입니다. 샘플링은 문제가 있다고 말하고 첫 번째 반응은 불신입니다. 그것은 당연하지만, 문제가 발견되면 그것이 진짜인지, 그 반대인지를 확신 할 수 있습니다.
추가 : 작동 방식에 대한 베이지안 설명을하겠습니다. I
호출 스택에 약간 f
의 시간이 걸리므로 (많은 비용이 소요되는 ) 명령 (호출 또는 기타) 이 있다고 가정하십시오 . 간단히하기 위해, 우리가 무엇인지 f
모르지만 그것이 0.1, 0.2, 0.3, ... 0.9, 1.0이고 각각의 가능성에 대한 사전 확률이 0.1이라고 가정하면, 모든 비용이 동등하게 선험적으로.
그런 다음 스택 샘플을 2 개만 가져 와서 I
관찰로 지정된 두 샘플에 대한 명령 을 봅니다 o=2/2
. 이것 에 따르면 의 빈도 f
에 대한 새로운 추정치를 제공 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 | o=2/2)
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
60보다 큰 사전 확률보다 > = 0.5 일 확률 이 92 % 라고 말합니다 .
이전의 가정이 다르다고 가정하십시오. 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 | o=2/2)
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)
0.6 %의 이전 가정보다 26 % 라고 합니다. 따라서 Bayes를 사용하면 예상 비용의 추정치를 업데이트 할 수 있습니다 I
. 데이터의 양이 적 으면 비용이 얼마인지 정확하게 알려주지 않고 수정해야 할만큼 큰 것만 알려줍니다.
또 다른 방법으로 승계 규칙 이라고합니다 . 동전을 두 번 뒤집어서 두 번 머리를 올리면 동전의 가능한 무게에 대해 무엇을 알 수 있습니까? 존중받는 대답은 평균값을 가진 베타 배포판이라고 말하는 것입니다 (number of hits + 1) / (number of tries + 2) = (2+1)/(2+2) = 75%
.
(핵심은 우리가 I
두 번 이상 보는 것입니다. 한 번만 보는 경우 f
> 0을 제외하고는 많은 것을 알려주지 않습니다. )
따라서 아주 적은 수의 샘플조차도 지침 비용에 대해 많은 것을 알 수 있습니다. (그리고 만약 그것이. 원가에 비례 평균 주파수 그들을 볼 n
샘플을 촬영하고, f
그 후, 비용은 I
에 나타날 것이다 nf+/-sqrt(nf(1-f))
샘플. 예 n=10
, f=0.3
즉 3+/-1.4
샘플).
추가 : 측정 및 임의 스택 샘플링의 차이에 대한 직관적 인 느낌을주고 :
프로파일이 있습니다 지금 샘플도 벽 시계 시간에 스택,하지만 무엇을 제공하는 것입니다 측정 (또는 핫 경로 또는 핫 스팟, 어떤에서 "병목 현상"은 쉽게 숨길 수 있습니다). 그들이 당신에게 보여주지 않는 (그리고 그들은 쉽게 할 수있는) 실제 샘플 자체입니다. 병목 현상 을 찾는 것이 목표라면 평균적 으로 2를 소요 시간의 일부로 나눈 값 을 볼 수 있습니다 . 따라서 30 %의 시간이 걸리면 평균적으로 2 / .3 = 6.7 샘플이 표시되며 20 샘플이 표시 할 확률은 99.2 %입니다.
다음은 측정 검사와 스택 샘플 검사의 차이점에 대한 설명입니다. 병목 현상은 이와 같은 하나의 큰 얼룩이거나 수많은 작은 것이므로 차이가 없습니다.
측정은 수평입니다. 특정 루틴에 소요되는 시간을 알려줍니다. 샘플링은 수직입니다. 전체 프로그램이 그 순간에 수행하는 작업을 피할 수있는 방법이 있고 두 번째 샘플 에서 볼 경우 병목 현상을 발견했습니다. 그것이 차이를 만드는 이유입니다. 시간이 얼마나 걸리는가에 대한 전체 이유를 보는 것입니다.
다음 옵션과 함께 Valgrind 를 사용할 수 있습니다
valgrind --tool=callgrind ./(Your binary)
라는 파일을 생성합니다 callgrind.out.x
. 그런 다음 kcachegrind
도구를 사용 하여이 파일을 읽을 수 있습니다 . 어떤 라인에 얼마의 비용이 드는지와 같은 결과로 사물의 그래픽 분석을 제공합니다.
gprof2dot
지금 여기에 있습니다 : github.com/jrfonseca/gprof2dot
GCC를 사용한다고 가정합니다. 표준 솔루션은 gprof 로 프로파일 링하는 것 입니다.
-pg
프로파일 링 전에 컴파일 에 추가해야합니다 .
cc -o myprog myprog.c utils.c -g -pg
아직 시도하지 않았지만 google-perftools 에 대한 좋은 소식을 들었습니다 . 시도해 볼 가치가 있습니다.
관련 질문은 여기에 있습니다 .
Valgrind , Intel VTune , Sun DTracegprof
: 당신을 위해 일을하지 않으면 몇 가지 다른 유행어 .
최신 커널 (예 : 최신 Ubuntu 커널)에는 새로운 'perf'도구 ( apt-get install linux-tools
) AKA perf_events가 제공 됩니다.
여기 에는 멋진 타임 차트 뿐만 아니라 클래식 샘플링 프로파일 러 ( 맨 페이지 )가 함께 제공됩니다 !
중요한 것은 이러한 도구가 프로세스 프로파일 링이 아니라 시스템 프로파일 링 이 될 수 있다는 것입니다. 스레드, 프로세스 및 커널 간의 상호 작용을 보여주고 프로세스 간의 스케줄링 및 I / O 종속성을 이해할 수 있습니다.
perf report
호출 부모와 함께 기능 이름을 제공하는 것 같습니다 ... (그래서 역 나비 모양의 일종)
gprof2dot
및 을 사용할 수 있습니다 perf script
. 아주 좋은 도구!
perf
에 존재 archive.li/9r927#selection-767.126-767.271 합니다 (SO 신들은 SO 지식베이스에서 해당 페이지를 삭제하기로 결정 왜 .... 나를 넘어)
Valgrind와 Callgrind를 프로파일 링 도구 모음의 기반으로 사용합니다. 알아야 할 중요한 것은 Valgrind가 기본적으로 가상 머신이라는 것입니다.
(wikipedia) Valgrind는 본질적으로 동적 재 컴파일을 포함하여 JIT (Just-In-Time) 컴파일 기술을 사용하는 가상 머신입니다. 원래 프로그램의 어떤 것도 호스트 프로세서에서 직접 실행되지 않습니다. 대신 Valgrind는 먼저 프로그램을 중립적 표현 (IR)이라고하는 임시적이고 간단한 형식으로 변환합니다.이 형식은 프로세서 중립적 인 SSA 기반 형식입니다. 변환 후 Valgrind가 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.*
다음 창에서 "Self"열 머리글을 클릭하는 것이 좋습니다. 그렇지 않으면 "main ()"이 가장 많은 시간이 걸리는 작업임을 나타냅니다. "자기"는 각 기능 자체가 종속 자와 함께가 아니라 시간이 얼마나 걸렸는지를 보여줍니다.
CALLGRIND_TOGGLE_COLLECT
활성화 / 비활성화 수집 프로그램; stackoverflow.com/a/13700817/288875
이것은 Nazgob의 Gprof 답변에 대한 답변 입니다.
지난 며칠 동안 Gprof를 사용해 왔으며 이미 세 가지 중요한 제한 사항을 발견했습니다. 그 중 하나는 아직 다른 곳에서는 문서화되지 않은 것입니다.
해결 방법 을 사용하지 않으면 멀티 스레드 코드에서 제대로 작동하지 않습니다.
함수 그래프에 의해 호출 그래프가 혼동됩니다. 예 : multithread()
지정된 배열 (인수로 전달됨)에서 지정된 함수를 멀티 스레드 할 수 있는 함수 가 있습니다. 그러나 Gprof는 모든 통화 multithread()
시간은 어린이의 시간을 계산할 목적으로 동등한 것으로 간주합니다. 일부 함수 multithread()
는 다른 함수 보다 훨씬 오래 걸리기 때문에 호출 그래프는 대부분 쓸모가 없습니다. (스레딩이 문제인지 궁금한 사람들에게 : 아니오, multithread()
선택적으로,이 경우에는 호출 스레드에서만 모든 것을 순차적으로 실행할 수 있습니다).
그것은 말한다 여기 ... "수 - 중 - 통화 수치는 샘플링, 계산에 의해하지 파생됩니다. 그들은 정확 ...이다"고. 그러나 내 호출 그래프는 5345859132 + 784984078을 가장 많이 호출되는 함수에 대한 호출 통계로 제공합니다. 첫 번째 숫자는 직접 호출이고 두 번째 재귀 호출은 모두 자체 호출입니다. 이것은 버그가 있음을 암시했기 때문에 긴 (64 비트) 카운터를 코드에 넣고 다시 실행했습니다. 내 수 : 5345859132 직통 및 78094395406 자체 재귀 호출. 여기에는 많은 자릿수가 있으므로 Gprof의 784m에 비해 내가 측정하는 재귀 호출은 780 억입니다 .100 가지 요소입니다. 두 실행 모두 단일 스레드 및 최적화되지 않은 코드로, 하나는 컴파일 -g
되고 다른 하나는 실행되었습니다 -pg
.
이것은 64 비트 데비안 Lenny에서 실행되는 GNU Gprof (GNU Binutils for Debian) 2.18.0.20080103입니다.
Valgrind, callgrind 및 kcachegrind를 사용하십시오.
valgrind --tool=callgrind ./(Your binary)
callgrind.out.x를 생성합니다. kcachegrind를 사용하여 읽으십시오.
gprof 사용 (-pg 추가) :
cc -o myprog myprog.c utils.c -g -pg
(멀티 스레드, 함수 포인터에는 좋지 않습니다)
google-perftools를 사용하십시오.
시간 샘플링을 사용하여 I / O 및 CPU 병목 현상이 드러납니다.
Intel VTune이 최고입니다 (교육 목적으로 무료).
기타 : AMD Codeanalyst (AMD CodeXL로 대체 된 이후), OProfile, 'perf'도구 (apt-get install linux-tools)
C ++ 프로파일 링 기술 조사
이 답변에서는 여러 도구를 사용하여 몇 가지 매우 간단한 테스트 프로그램을 분석하여 해당 도구의 작동 방식을 구체적으로 비교합니다.
다음 테스트 프로그램은 매우 간단하며 다음을 수행합니다.
main
통화 fast
및 maybe_slow
3 회, maybe_slow
통화 중 하나 가 느림
의 느린 호출 maybe_slow
은 10 배 더 길며 자식 함수 호출을 고려하면 런타임을 지배합니다 common
. 이상적으로 프로파일 링 도구는 특정 느린 호출을 가리킬 수 있습니다.
모두 fast
와 maybe_slow
전화 common
프로그램 실행의 대부분을 차지하고,
프로그램 인터페이스는 다음과 같습니다
./main.out [n [seed]]
프로그램은 O(n^2)
총 루프를 수행합니다. seed
런타임에 영향을 미치지 않고 다른 출력을 얻는 것입니다.
main.c
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
uint64_t __attribute__ ((noinline)) common(uint64_t n, uint64_t seed) {
for (uint64_t i = 0; i < n; ++i) {
seed = (seed * seed) - (3 * seed) + 1;
}
return seed;
}
uint64_t __attribute__ ((noinline)) fast(uint64_t n, uint64_t seed) {
uint64_t max = (n / 10) + 1;
for (uint64_t i = 0; i < max; ++i) {
seed = common(n, (seed * seed) - (3 * seed) + 1);
}
return seed;
}
uint64_t __attribute__ ((noinline)) maybe_slow(uint64_t n, uint64_t seed, int is_slow) {
uint64_t max = n;
if (is_slow) {
max *= 10;
}
for (uint64_t i = 0; i < max; ++i) {
seed = common(n, (seed * seed) - (3 * seed) + 1);
}
return seed;
}
int main(int argc, char **argv) {
uint64_t n, seed;
if (argc > 1) {
n = strtoll(argv[1], NULL, 0);
} else {
n = 1;
}
if (argc > 2) {
seed = strtoll(argv[2], NULL, 0);
} else {
seed = 0;
}
seed += maybe_slow(n, seed, 0);
seed += fast(n, seed);
seed += maybe_slow(n, seed, 1);
seed += fast(n, seed);
seed += maybe_slow(n, seed, 0);
seed += fast(n, seed);
printf("%" PRIX64 "\n", seed);
return EXIT_SUCCESS;
}
gprof
gprof는 계측으로 소프트웨어를 다시 컴파일해야하며, 계측과 함께 샘플링 방식도 사용합니다. 따라서 정확도 (샘플링이 항상 정확하지는 않으며 기능을 건너 뛸 수 있음)와 실행 속도 저하 (계측 및 샘플링은 실행 속도를 크게 저하시키지 않는 비교적 빠른 기술) 사이의 균형을 유지합니다.
gprof는 GCC / binutils에 내장되어 있으므로 -pg
gprof를 활성화 하는 옵션으로 컴파일 하면됩니다. 그런 다음 몇 초 ( 10000
) 의 적당한 기간 동안 실행되는 size CLI 매개 변수를 사용하여 프로그램을 정상적으로 실행합니다 .
gcc -pg -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
time ./main.out 10000
교육상의 이유로 최적화를 사용하지 않고 실행도 수행합니다. 일반적으로 최적화 된 프로그램의 성능 최적화에만 관심이 있기 때문에 실제로는 쓸모가 없습니다.
gcc -pg -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
./main.out 10000
첫째, time
유무에 관계없이 실행 시간 -pg
이 동일 하다는 것을 알 수 있습니다 . 그러나이 티켓에 나와있는 것처럼 복잡한 소프트웨어에서 2x-3x 속도 저하가 발생했습니다 .
로 컴파일 했으므로 -pg
프로그램을 실행 gmon.out
하면 프로파일 링 데이터가 포함 된 파일 파일이 생성 됩니다.
우리는 그래픽으로 해당 파일을 관찰 할 수 gprof2dot
에서 질문과 같이 이 gprof은 결과의 그래픽 표현을 얻을 수 있습니까?
sudo apt install graphviz
python3 -m pip install --user gprof2dot
gprof main.out > main.gprof
gprof2dot < main.gprof | dot -Tsvg -o output.svg
여기서, gprof
공구가 판독 gmon.out
추적 정보와의 판독 가능한 보고서 생성 main.gprof
, gprof2dot
다음 그래프를 생성하기 위해 판독한다.
gprof2dot의 출처는 https://github.com/jrfonseca/gprof2dot입니다.
-O0
실행에 대해 다음을 관찰합니다 .
그리고 -O3
실행을 위해 :
-O0
출력은 거의 자명하다. 예를 들어, 3 개의 maybe_slow
호출과 해당 자식 호출이 총 런타임의 97.56 %를 차지하지만 maybe_slow
자식없이 자체 실행 하는 것이 총 실행 시간의 0.00 %를 차지합니다. 즉, 해당 함수에 소비 된 거의 모든 시간이 어린이 전화.
TODO : 왜 GDB에서 출력을 볼 수 있는데 출력 main
에서 누락 되었습니까? GProf 출력에서 누락 된 기능 나는 gprof가 컴파일 된 계측 외에도 샘플링을 기반으로하고 있기 때문에 너무 빠르며 샘플 이 없기 때문이라고 생각합니다 .-O3
bt
-O3
main
SVG는 Ctrl + F로 검색 가능하고 파일 크기는 약 10 배 작을 수 있기 때문에 PNG 대신 SVG 출력을 선택합니다. 또한 생성 된 이미지의 너비와 높이는 복잡한 소프트웨어의 경우 수만 개의 픽셀로 eog
가득 차 있으며 PNG의 경우 그놈 3.28.1 버그가 발생하지만 SVG는 브라우저에서 자동으로 열립니다. 김프 2.8도 잘 작동했습니다.
그러나 그때까지도 이미지를 많이 드래그하여 원하는 것을 찾을 수 있습니다. 예를 들어이 티켓 에서 가져온 "실제"소프트웨어 예의 이미지를 참조하십시오 .
모든 분류되지 않은 작은 스파게티 라인이 서로 연결되어 가장 중요한 통화 스택을 쉽게 찾을 수 있습니까? 더 나은 dot
옵션 이있을 수 있지만 지금은 가고 싶지 않습니다. 우리에게 정말로 필요한 것은 그것을위한 적절한 전용 뷰어이지만 아직 찾지 못했습니다.
그러나 색상 맵을 사용하여 이러한 문제를 약간 완화 할 수 있습니다. 예를 들어, 이전의 거대한 이미지에서 나는 녹색이 빨간색을 쫓은 화려한 공제를했을 때 마침내 왼쪽에서 중요한 경로를 찾았습니다.
또는 gprof
이전에 저장 한 내장 binutils 도구 의 텍스트 출력을 관찰 할 수도 있습니다 .
cat main.gprof
기본적으로 출력 데이터의 의미를 설명하는 매우 자세한 출력을 생성합니다. 그보다 더 잘 설명 할 수 없기 때문에 직접 읽어 보도록하겠습니다.
데이터 출력 형식을 이해 한 후에는 -b
옵션 없이 튜토리얼없이 데이터 만 표시하도록 상세도를 줄일 수 있습니다 .
gprof -b main.out
이 예에서 출력은 다음과 -O0
같습니다.
Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls s/call s/call name
100.35 3.67 3.67 123003 0.00 0.00 common
0.00 3.67 0.00 3 0.00 0.03 fast
0.00 3.67 0.00 3 0.00 1.19 maybe_slow
Call graph
granularity: each sample hit covers 2 byte(s) for 0.27% of 3.67 seconds
index % time self children called name
0.09 0.00 3003/123003 fast [4]
3.58 0.00 120000/123003 maybe_slow [3]
[1] 100.0 3.67 0.00 123003 common [1]
-----------------------------------------------
<spontaneous>
[2] 100.0 0.00 3.67 main [2]
0.00 3.58 3/3 maybe_slow [3]
0.00 0.09 3/3 fast [4]
-----------------------------------------------
0.00 3.58 3/3 main [2]
[3] 97.6 0.00 3.58 3 maybe_slow [3]
3.58 0.00 120000/123003 common [1]
-----------------------------------------------
0.00 0.09 3/3 main [2]
[4] 2.4 0.00 0.09 3 fast [4]
0.09 0.00 3003/123003 common [1]
-----------------------------------------------
Index by function name
[1] common [4] fast [3] maybe_slow
그리고 -O3
:
Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls us/call us/call name
100.52 1.84 1.84 123003 14.96 14.96 common
Call graph
granularity: each sample hit covers 2 byte(s) for 0.54% of 1.84 seconds
index % time self children called name
0.04 0.00 3003/123003 fast [3]
1.79 0.00 120000/123003 maybe_slow [2]
[1] 100.0 1.84 0.00 123003 common [1]
-----------------------------------------------
<spontaneous>
[2] 97.6 0.00 1.79 maybe_slow [2]
1.79 0.00 120000/123003 common [1]
-----------------------------------------------
<spontaneous>
[3] 2.4 0.00 0.04 fast [3]
0.04 0.00 3003/123003 common [1]
-----------------------------------------------
Index by function name
[1] common
각 섹션에 대한 매우 빠른 요약 예 :
0.00 3.58 3/3 main [2]
[3] 97.6 0.00 3.58 3 maybe_slow [3]
3.58 0.00 120000/123003 common [1]
들여 쓰기 된 기능을 중심으로합니다 ( maybe_flow
). [3]
해당 함수의 ID입니다. 함수 위에는 호출자가 있고 그 아래에는 호출자가 있습니다.
에 대해서는 -O3
그래픽 출력에서 maybe_slow
와 fast
같이 알려진 부모가없는 문서를 참조하십시오 <spontaneous>
.
gprof로 라인 별 프로파일 링을 수행하는 좋은 방법이 있는지 확실하지 않습니다. 특정 코드 라인에서 보낸 ' gprof '시간
valgrind callgrind
valgrind는 valgrind 가상 머신을 통해 프로그램을 실행합니다. 이렇게하면 프로파일 링이 매우 정확 해지지 만 프로그램 속도가 크게 느려집니다. 나는 또한 kcachegrind에 대해 언급했다 : 코드의 그림 함수 호출 그래프를 얻는 도구
callgrind는 코드를 프로파일 링하는 valgrind의 도구이고 kcachegrind는 cachegrind 출력을 시각화 할 수있는 KDE 프로그램입니다.
먼저 -pg
일반 컴파일로 돌아가려면 플래그 를 제거해야합니다 . 그렇지 않으면 실행이 실제로 실패 Profiling timer expired
하고 그렇습니다. 그렇습니다.
따라서 다음과 같이 컴파일하고 실행합니다.
sudo apt install kcachegrind valgrind
gcc -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
time valgrind --tool=callgrind valgrind --dump-instr=yes \
--collect-jumps=yes ./main.out 10000
--dump-instr=yes --collect-jumps=yes
이것은 또한 추가 된 오버 헤드 비용으로 어셈블리 라인 당 성능 저하를 볼 수있는 정보를 덤프하기 때문에 가능 합니다.
박쥐 time
가 프로그램을 실행하는 데 29.5 초가 걸렸다 고 알려주므로이 예제에서는 약 15 배의 속도가 느려졌습니다. 분명히이 둔화는 더 큰 워크로드에 심각한 제한이 될 것입니다. 여기 에 언급 된 "실제 소프트웨어 예" 에서 80 배의 속도 저하를 관찰했습니다.
런라는 이름의 프로파일 데이터 파일 생성 callgrind.out.<pid>
예를 들어 callgrind.out.8554
내 경우를. 우리는 그 파일을 다음과 같이 봅니다 :
kcachegrind callgrind.out.8554
텍스트 gprof 출력과 유사한 데이터를 포함하는 GUI를 보여줍니다.
또한 오른쪽 하단의 "콜 그래프"탭으로 이동하면 마우스 오른쪽 버튼을 클릭하여 내보낼 수있는 콜 그래프를 볼 수 있습니다.
fast
kcachegrind가 호출에 너무 적은 시간이 걸리기 때문에 시각화를 단순화해야했기 때문에 해당 그래프에 표시되지 않는다고 생각 합니다. 실제 프로그램에서 원하는 동작 일 것입니다. 마우스 오른쪽 버튼 메뉴에는 이러한 노드를 컬링하는시기를 제어하는 몇 가지 설정이 있지만 빠른 시도 후 짧은 호출을 표시하지 못했습니다. fast
왼쪽 창을 클릭 하면와 함께 호출 그래프가 표시되어 fast
실제로 스택이 캡처되었습니다. 아직 완전한 그래프 호출 그래프를 표시하는 방법을 찾지 못했습니다 : callgrind가 kcachegrind 호출 그래프에서 모든 함수 호출을 표시하도록하십시오.
복잡한 C ++ 소프트웨어의 TODO에서 유형의 일부 항목을 볼 수 있습니다 <cycle N>
. 예를 들어 <cycle 11>
함수 이름이 필요한 위치는 무엇입니까? "Cycle Detection (사이클 감지)"버튼을 사용하여이 기능을 켜고 끌 수 있지만 그 의미는 무엇입니까?
perf
...에서 linux-tools
perf
독점적으로 Linux 커널 샘플링 메커니즘을 사용하는 것 같습니다. 따라서 설정이 매우 간단하지만 완전히 정확하지는 않습니다.
sudo apt install linux-tools
time perf record -g ./main.out 10000
이것은 실행에 0.2 초를 추가 했으므로 시간이 좋지만 common
키보드 오른쪽 화살표로 노드를 확장 한 후에도 여전히 관심이 없습니다 .
Samples: 7K of event 'cycles:uppp', Event count (approx.): 6228527608
Children Self Command Shared Object Symbol
- 99.98% 99.88% main.out main.out [.] common
common
0.11% 0.11% main.out [kernel] [k] 0xffffffff8a6009e7
0.01% 0.01% main.out [kernel] [k] 0xffffffff8a600158
0.01% 0.00% main.out [unknown] [k] 0x0000000000000040
0.01% 0.00% main.out ld-2.27.so [.] _dl_sysdep_start
0.01% 0.00% main.out ld-2.27.so [.] dl_main
0.01% 0.00% main.out ld-2.27.so [.] mprotect
0.01% 0.00% main.out ld-2.27.so [.] _dl_map_object
0.01% 0.00% main.out ld-2.27.so [.] _xstat
0.00% 0.00% main.out ld-2.27.so [.] __GI___tunables_init
0.00% 0.00% main.out [unknown] [.] 0x2f3d4f4944555453
0.00% 0.00% main.out [unknown] [.] 0x00007fff3cfc57ac
0.00% 0.00% main.out ld-2.27.so [.] _start
그런 다음 -O0
프로그램 을 벤치마킹하여 그것이 무엇인가를 나타내는 지 확인 하려고합니다 . 마지막으로 콜 그래프가 표시됩니까?
Samples: 15K of event 'cycles:uppp', Event count (approx.): 12438962281
Children Self Command Shared Object Symbol
+ 99.99% 0.00% main.out [unknown] [.] 0x04be258d4c544155
+ 99.99% 0.00% main.out libc-2.27.so [.] __libc_start_main
- 99.99% 0.00% main.out main.out [.] main
- main
- 97.54% maybe_slow
common
- 2.45% fast
common
+ 99.96% 99.85% main.out main.out [.] common
+ 97.54% 0.03% main.out main.out [.] maybe_slow
+ 2.45% 0.00% main.out main.out [.] fast
0.11% 0.11% main.out [kernel] [k] 0xffffffff8a6009e7
0.00% 0.00% main.out [unknown] [k] 0x0000000000000040
0.00% 0.00% main.out ld-2.27.so [.] _dl_sysdep_start
0.00% 0.00% main.out ld-2.27.so [.] dl_main
0.00% 0.00% main.out ld-2.27.so [.] _dl_lookup_symbol_x
0.00% 0.00% main.out [kernel] [k] 0xffffffff8a600158
0.00% 0.00% main.out ld-2.27.so [.] mmap64
0.00% 0.00% main.out ld-2.27.so [.] _dl_map_object
0.00% 0.00% main.out ld-2.27.so [.] __GI___tunables_init
0.00% 0.00% main.out [unknown] [.] 0x552e53555f6e653d
0.00% 0.00% main.out [unknown] [.] 0x00007ffe1cf20fdb
0.00% 0.00% main.out ld-2.27.so [.] _start
TODO : -O3
처형은 어떻게 되었습니까? 그것은 단순히인가 maybe_slow
하고 fast
너무 빨리했다 및 샘플을하지 않았다? 실행 -O3
하는 데 시간이 오래 걸리는 더 큰 프로그램에서 잘 작동합니까 ? CLI 옵션이 누락 되었습니까? 나는에 대해 알게 -F
헤르츠의 샘플 주파수를 제어하는,하지만 난의 기본적으로 허용 최대로 켜져 -F 39500
(증가 될 수있다 sudo
)와 나는 아직도 선명한 통화가 표시되지 않습니다.
멋진 점 중 하나 perf
는 Brendan Gregg의 FlameGraph 도구입니다.이 도구는 통화 스택 타이밍을 매우 깔끔하게 표시하여 큰 통화를 빠르게 볼 수 있도록합니다. 이 도구는에서 확인할 수 있습니다 https://github.com/brendangregg/FlameGraph 또한 그의 반환 한 튜토리얼에 언급 : http://www.brendangregg.com/perf.html#FlameGraphs 내가 달릴 때 perf
없이 sudo
내가 가진 ERROR: No stack counts found
너무에 대한 이제 나는 그것을 할 것이다 sudo
:
git clone https://github.com/brendangregg/FlameGraph
sudo perf record -F 99 -g -o perf_with_stack.data ./main.out 10000
sudo perf script -i perf_with_stack.data | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > flamegraph.svg
그러나 그러한 간단한 프로그램에서는 출력을 이해하기가 쉽지 maybe_slow
않습니다 fast
. 그래프에서 그래프 도 볼 수 없기 때문입니다.
보다 복잡한 예에서는 그래프의 의미가 분명해집니다.
TODO이 [unknown]
예제 에는 함수 로그가 있는데 왜 그럴까요?
다른 perf GUI 인터페이스에는 다음이 포함됩니다.
Eclipse Trace Compass 플러그인 : https://www.eclipse.org/tracecompass/
그러나 이것은 먼저 데이터를 Common Trace Format으로 변환해야한다는 단점이 perf data --to-ctf
있지만 빌드 타임에 활성화해야 perf
하거나 충분히 새로 워야합니다. 우분투 18.04
https://github.com/KDAB/hotspot
이것의 단점은 우분투 패키지가없는 것 같으며 Qt 5.10이 필요하지만 Ubuntu 18.04는 Qt 5.9입니다.
gperftools
이전에 "Google 성능 도구"라고하는 출처 : https://github.com/gperftools/gperftools 샘플 기반.
먼저 다음을 사용하여 gperftools를 설치하십시오.
sudo apt install google-perftools
그런 다음 gperftools CPU 프로파일 러를 런타임 또는 빌드 타임의 두 가지 방법으로 활성화 할 수 있습니다.
런타임시, 우리는 설정 통과해야 LD_PRELOAD
가리 키도록 libprofiler.so
당신이 찾을 수있는 locate libprofiler.so
, 예를 들어 내 시스템 :
gcc -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libprofiler.so \
CPUPROFILE=prof.out ./main.out 10000
또는 링크 타임에 라이브러리를 빌드하여 LD_PRELOAD
런타임에 전달 하지 않아도됩니다.
gcc -Wl,--no-as-needed,-lprofiler,--as-needed -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
CPUPROFILE=prof.out ./main.out 10000
내가 지금까지 찾은이 데이터를 보는 가장 좋은 방법은 pprof 출력을 kcachegrind가 입력 (예 : Valgrind-project-viewer-tool)과 동일한 형식으로 만들고 kcachegrind를 사용하여 다음을 확인하는 것입니다.
google-pprof --callgrind main.out prof.out > callgrind.out
kcachegrind callgrind.out
이러한 방법 중 하나를 사용하여 실행 한 후 prof.out
프로파일 데이터 파일을 출력으로 얻습니다 . 다음을 사용하여 해당 파일을 그래픽으로 SVG로 볼 수 있습니다.
google-pprof --web main.out prof.out
다른 도구와 마찬가지로 친숙한 콜 그래프를 제공하지만 초 단위가 아닌 수 많은 샘플 단위로 구성됩니다.
또는 다음과 같은 텍스트 데이터를 얻을 수도 있습니다.
google-pprof --text main.out prof.out
이것은 다음을 제공합니다.
Using local file main.out.
Using local file prof.out.
Total: 187 samples
187 100.0% 100.0% 187 100.0% common
0 0.0% 100.0% 187 100.0% __libc_start_main
0 0.0% 100.0% 187 100.0% _start
0 0.0% 100.0% 4 2.1% fast
0 0.0% 100.0% 187 100.0% main
0 0.0% 100.0% 183 97.9% maybe_slow
도 참조 : 어떻게 도구를 규칙적 구글 사용
우분투 18.04, gprof2dot 2019.11.30, valgrind 3.13.0, perf 4.15.18, Linux 커널 4.15.0, FLameGraph 1a0dc6985aad06e76857cf2a354bd5ba0c9ce96b, gperftools 2.5-2에서 테스트되었습니다.
-fno-omit-frame-pointer
플래그 로 컴파일 하거나 시나리오 와 함께 --call-graph "dwarf"
또는 --call-graph "lbr"
시나리오에 따라 다른 대안을 사용하는 것 입니다.
단일 스레드 프로그램의 경우 igprof , Ignominous Profiler : https://igprof.org/를 사용할 수 있습니다 .
Mike Dunlavey의 답변에 따르면 ... 길이를 따라 샘플링 프로파일 러입니다.이 결과는 누적 또는 기능별.
또한 언급 할 가치가 있습니다
HPCToolkit 및 VTune을 사용했으며 텐트에서 장대를 찾는 데 매우 효과적이며 코드를 다시 컴파일 할 필요가 없습니다 (의미있는 출력을 얻으려면 CMake에서 -g -O 또는 RelWithDebInfo 유형 빌드를 사용해야 함) . TAU의 기능이 비슷하다고 들었습니다.
다음은 코드 속도를 높이기 위해 사용하는 두 가지 방법입니다.
CPU 바운드 응용 프로그램의 경우 :
I / O 바운드 애플리케이션의 경우 :
NB
프로파일 러가없는 경우 가난한 사람의 프로파일 러를 사용하십시오. 응용 프로그램을 디버깅하는 동안 일시 중지를 누르십시오. 대부분의 개발자 제품군은 주석이 달린 줄 번호로 조립됩니다. 통계적으로 대부분의 CPU 사이클을 먹는 지역에 착륙 할 가능성이 있습니다.
CPU의 경우 DEBUG 모드 에서 프로파일 링하는 이유 는 RELEASE 모드 에서 프로파일 링을 시도한 경우 컴파일러가 계산할 때 코드를 매핑 할 수없는 엉망으로 만드는 경향이있는 수학, 루프 및 인라인 함수를 줄이려고하기 때문입니다. 매핑 할 수없는 엉망은 어셈블리가 최적화중인 소스 코드와 일치하지 않을 수 있기 때문에 프로파일 러가 너무 오래 걸린 것을 명확하게 식별 할 수 없음을 의미합니다 . RELEASE 모드 의 성능 (예 : 타이밍 감지)이 필요한 경우 사용 가능한 성능을 유지하는 데 필요한 디버거 기능을 비활성화하십시오.
I를 들어 / O 바인딩은 프로파일 러는 여전히 / O 작업을 I을 식별 할 수 RELEASE의 내가 / O 작업이 중 외부 공유 라이브러리에 연결되어 있습니다 (대부분의 시간) 또는 최악의 경우, sys- 발생하기 때문 모드 호출 인터럽트 벡터 (프로파일 러로 쉽게 식별 할 수 있음).
iprof 라이브러리를 사용할 수 있습니다.
https://gitlab.com/Neurochrom/iprof
https://github.com/Neurochrom/iprof
크로스 플랫폼이며 애플리케이션 성능을 실시간으로 측정 할 수 없습니다. 라이브 그래프와 결합 할 수도 있습니다. 완전 면책 조항 : 나는 저자입니다.
loguru
프로파일 링에 훌륭하게 사용할 수있는 타임 스탬프 및 총 가동 시간이 포함되어 있으므로 로깅 프레임 워크 를 사용할 수 있습니다.
직장에서 우리는 스케줄링 측면에서 원하는 것을 모니터링하는 데 도움이되는 멋진 도구를 가지고 있습니다. 이것은 여러 번 유용했습니다.
C ++로되어 있으며 필요에 따라 사용자 정의해야합니다. 불행히도 코드를 공유 할 수없고 개념 만 공유 할 수 있습니다. volatile
포스트 모템을 덤프하거나 로깅 시스템을 중지 한 후 (예를 들어 파일에 덤프) 타임 스탬프 및 이벤트 ID가 포함 된 "큰" 버퍼를 사용합니다 .
모든 데이터가 포함 된 이른바 큰 버퍼를 검색하고 작은 인터페이스가이를 구문 분석하고 오실로스코프가 색상 ( .hpp
파일로 구성)과 같이 이름 (위 / 아래 + 값)으로 이벤트를 표시합니다 .
원하는 것에 만 집중하도록 생성 된 이벤트 양을 사용자 정의합니다. 초당 기록 된 이벤트 수에 따라 원하는 CPU 양을 사용하면서 문제를 예약하는 데 많은 도움이되었습니다.
3 개의 파일이 필요합니다.
toolname.hpp // interface
toolname.cpp // code
tool_events_id.hpp // Events ID
개념은 다음 tool_events_id.hpp
과 같이 이벤트를 정의하는 것입니다.
// EVENT_NAME ID BEGIN_END BG_COLOR NAME
#define SOCK_PDU_RECV_D 0x0301 //@D00301 BGEEAAAA # TX_PDU_Recv
#define SOCK_PDU_RECV_F 0x0302 //@F00301 BGEEAAAA # TX_PDU_Recv
또한 다음에서 몇 가지 기능을 정의합니다 toolname.hpp
.
#define LOG_LEVEL_ERROR 0
#define LOG_LEVEL_WARN 1
// ...
void init(void);
void probe(id,payload);
// etc
코드 어디에서나 사용할 수 있습니다 :
toolname<LOG_LEVEL>::log(EVENT_NAME,VALUE);
이 probe
함수는 몇 개의 어셈블리 라인을 사용하여 클럭 타임 스탬프를 최대한 빨리 검색 한 다음 버퍼에 항목을 설정합니다. 또한 로그 이벤트를 저장할 인덱스를 안전하게 찾기 위해 원자 단위로 증가합니다. 물론 버퍼는 원형입니다.
샘플 코드가 부족하여 아이디어가 난독 화되지 않기를 바랍니다.
실제로 google / benchmark 에 대해 언급하지 않은 많은 사람들이 놀랐습니다 . 특히 코드베이스가 조금 큰 경우 특정 코드 영역을 고정하는 것이 약간 번거롭지 만,callgrind
병목 현상을 일으키는 부분을 식별하는 IMHO가 핵심입니다. 그러나 먼저 다음 질문에 답하고 그에 따라 도구를 선택합니다.
valgrind
의 조합 callrind
과 kcachegrind
위의 점에서 괜찮은 평가를 제공해야하고이 코드의 일부 섹션에 문제가 있다는 것을 설립 된 후에는, 내가 마이크로 벤치 마크를 수행 좋을 것은 google benchmark
시작하기에 좋은 장소입니다.
-pg
코드를 컴파일하고 링크 할 때 플래그를 사용 하고 실행 파일을 실행하십시오. 이 프로그램이 실행되는 동안 프로파일 링 데이터는 파일 a.out에 수집됩니다.
프로파일 링에는 두 가지 유형이 있습니다
1-플랫 프로파일 링 :
명령 gprog --flat-profile a.out
을 실행하여
함수에 소요 된 전체 시간의 백분율, 함수에 소요 된 시간 (
하위 함수에 대한 호출 포함 및 제외)
- 통화
- 통화 당 평균 시간입니다.
2- 그래프
는 명령 gprof --graph a.out
을 프로파일 링 하여 다음을 포함하는 각 기능에 대한 다음 데이터를 얻습니다
.-각 섹션에서 하나의 기능은 색인 번호로 표시됩니다.
-위의 함수에는 함수를 호출하는 함수 목록이 있습니다.
-함수 아래에 함수가 호출하는 함수 목록이 있습니다.
자세한 정보는 https://sourceware.org/binutils/docs-2.32/gprof/를 참조하십시오.
디버깅 소프트웨어 를 사용하여 코드가 느리게 실행되는 위치를 식별하는 방법은 무엇입니까?
운동 중에 장애물이 있다고 생각하면 속도가 느려집니다.
원치 않는 재 할당의 루핑, 버퍼 오버플로, 검색, 메모리 누수 등의 작업은 더 많은 실행 전력을 소비하므로 코드 성능에 부정적인 영향을 미칩니다. 프로파일 링 전에 컴파일에 -pg를 추가하십시오.
g++ your_prg.cpp -pg
또는 cc my_program.cpp -g -pg
컴파일러에 따라
아직 시도하지 않았지만 google-perftools에 대한 좋은 소식을 들었습니다. 시도해 볼 가치가 있습니다.
valgrind --tool=callgrind ./(Your binary)
gmon.out 또는 callgrind.out.x라는 파일을 생성합니다. 그런 다음 kcachegrind 또는 디버거 도구를 사용하여이 파일을 읽을 수 있습니다. 어떤 라인에 얼마의 비용이 드는지와 같은 결과로 사물의 그래픽 분석을 제공합니다.
나도 그렇게 생각해