C ++ profil oluşturma tekniklerinin incelenmesi
Bu cevapta, bu araçların nasıl çalıştığını somut bir şekilde karşılaştırmak için birkaç çok basit test programını analiz etmek için birkaç farklı araç kullanacağım.
Aşağıdaki test programı çok basittir ve aşağıdakileri yapar:
main
aramalar fast
ve maybe_slow
3 kez, bir maybe_slow
yavaş olmak aramalar
Yavaş çağrısı maybe_slow
10 kat daha uzundur ve alt işleve çağrıları dikkate alırsak çalışma zamanına hakim olur common
. İdeal olarak, profil oluşturma aracı bizi belirli yavaş aramaya yönlendirebilir.
her ikisi de fast
ve maybe_slow
çağrı common
, hangi program yürütme toplu hesap
Program arayüzü:
./main.out [n [seed]]
ve program O(n^2)
toplamda döngüler yapar . seed
sadece çalışma zamanını etkilemeden farklı çıktılar elde etmektir.
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, yazılımın enstrümantasyon ile derlenmesini gerektirir ve ayrıca bu enstrümantasyon ile birlikte bir örnekleme yaklaşımı kullanır. Bu nedenle doğruluk (örnekleme her zaman tam olarak doğru değildir ve işlevleri atlayabilir) ve yürütme yavaşlaması (enstrümantasyon ve örnekleme, yürütmeyi çok yavaşlatmayan nispeten hızlı teknikler) arasında bir denge kurar.
gprof, GCC / binutils'e entegre edilmiştir, bu yüzden tek yapmamız gereken -pg
gprof'u etkinleştirme seçeneğiyle derlemektir . Programı normalde birkaç saniyelik makul bir süre üreten bir boyut CLI parametresiyle çalıştırırız ( 10000
):
gcc -pg -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
time ./main.out 10000
Eğitimsel nedenlerden dolayı, optimizasyonlar etkinleştirilmeden de bir çalışma yapacağız. Normalde yalnızca optimize edilmiş programın performansını optimize etmeyi önemsediğiniz için, bunun pratikte işe yaramaz olduğuna dikkat edin:
gcc -pg -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
./main.out 10000
İlk olarak, time
bize ve olmadan yürütme süresinin -pg
aynı olduğunu söyler , bu harika: yavaşlama yok! Bununla birlikte, karmaşık yazılımda 2x - 3x yavaşlama hesaplarını gördüm, örneğin bu bilette gösterildiği gibi .
Derlediğimiz için -pg
, programı çalıştırmak gmon.out
profil oluşturma verilerini içeren bir dosya dosyası üretir .
Bu dosyayı şu şekilde grafiksel gprof2dot
olarak gözlemleyebiliriz : gprof sonuçlarının grafik temsili mümkün müdür?
sudo apt install graphviz
python3 -m pip install --user gprof2dot
gprof main.out > main.gprof
gprof2dot < main.gprof | dot -Tsvg -o output.svg
Burada gprof
araç, gmon.out
izleme bilgilerini okur ve içinde okunabilir bir rapor oluşturur main.gprof
; bu rapor gprof2dot
daha sonra bir grafik oluşturmak için okunur.
Gprof2dot kaynağı şuradadır: https://github.com/jrfonseca/gprof2dot
-O0
Koşu için aşağıdakileri gözlemliyoruz :
ve -O3
koşu için:
-O0
Çıkış hemen hemen kendi kendini açıklayıcı. Örneğin, 3 maybe_slow
çağrının ve onların çocuk çağrısının toplam çalışma zamanının% 97,56'sını aldığını gösterir, ancak maybe_slow
çocuksuz kendisinin yürütülmesi toplam yürütme süresinin% 0,00'ünü oluşturur , yani bu işlevde harcanan neredeyse tüm zaman çocuk çağrıları.
YAPILACAKLAR: GDB'de görebilmeme rağmen neden çıktıda main
eksik ? GProf çıkışında eksik işlev, gprof'un derlenmiş enstrümantasyonuna ek olarak örnekleme temelli olması ve çok hızlı olması ve örnek almaması nedeniyle olduğunu düşünüyorum .-O3
bt
-O3
main
PNG yerine SVG çıktısını seçiyorum çünkü SVG Ctrl + F ile aranabilir ve dosya boyutu yaklaşık 10 kat daha küçük olabilir. Ayrıca, oluşturulan görüntünün genişliği ve yüksekliği, karmaşık yazılımlar için on binlerce pikselle eog
komik olabilir ve bu durumda PNG'ler için GNOME 3.28.1 hata verirken SVG'ler tarayıcım tarafından otomatik olarak açılır. gimp 2.8 iyi çalıştı, ayrıca bkz:
ancak o zaman bile, istediğinizi bulmak için görüntüyü çok fazla sürükleyeceksiniz, örneğin bu biletten alınan "gerçek" bir yazılım örneğinden bu resmi inceleyin :
Tüm bu küçük sıralanmamış spagetti hatları birbirinin üzerinden geçerek en kritik çağrı yığınını kolayca bulabilir misiniz? dot
Eminim daha iyi seçenekler olabilir , ama şimdi oraya gitmek istemiyorum. Gerçekten ihtiyacımız olan şey, bunun için uygun bir görüntüleyicidir, ancak henüz bir tane bulamadım:
Ancak bu sorunu biraz azaltmak için renk haritasını kullanabilirsiniz. Örneğin, önceki büyük görüntüde, yeşilin kırmızıdan sonra geldiği parlak kesinti, ardından daha koyu ve daha koyu maviyi çıkardığımda sonunda soldaki kritik yolu bulmayı başardım.
Alternatif olarak, gprof
daha önce kaydettiğimiz yerleşik binutils aracının metin çıktısını da gözlemleyebiliriz :
cat main.gprof
Varsayılan olarak, bu çıktı verilerinin ne anlama geldiğini açıklayan son derece ayrıntılı bir çıktı üretir. Bundan daha iyi açıklayamadığım için, kendiniz okumanıza izin vereceğim.
Veri çıkış formatını anladıktan sonra, -b
seçenekle birlikte öğretici olmadan yalnızca verileri göstermek için ayrıntı düzeyini azaltabilirsiniz :
gprof -b main.out
Örneğimizde, çıktılar -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
ve için -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
Her bölüm için çok hızlı bir özet olarak, örneğin:
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]
girintili bırakılan işlev ( maybe_flow
) etrafında ortalanır . [3]
bu işlevin kimliği. Fonksiyonun üstünde, arayanlar ve altında callees vardır.
Bunun için -O3
, grafik çıktısında olduğu gibi maybe_slow
ve fast
bilinen bir ebeveyne sahip olmadığına bakın, bu belgelerin ne <spontaneous>
anlama geldiğini söylüyor .
Gprof ile satır satır profil oluşturmanın güzel bir yolu olup olmadığından emin değilim: belirli kod satırlarında harcanan `gprof` zamanı
valgrind callgrind
valgrind programı valgrind sanal makinesi üzerinden çalıştırır. Bu, profil oluşturmayı çok doğru hale getirir, ancak programın çok büyük bir yavaşlamasını da sağlar. Ben de daha önce kcachegrind bahsetmiştim: Resimli bir fonksiyon kodu kodunu almak için Araçlar
callgrind, valgrind'in profil kodu aracıdır ve kcachegrind, önbellek çıktısını görselleştirebilen bir KDE programıdır.
İlk önce -pg
normal derlemeye geri dönmek için bayrağı kaldırmalıyız , aksi takdirde çalışma aslında başarısız olurProfiling timer expired
ve evet, bu çok yaygındır ve bunun için bir Stack Overflow sorusu vardı.
Bu yüzden derliyor ve şöyle çalışıyoruz:
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
Etkinleştiririm --dump-instr=yes --collect-jumps=yes
çünkü bu aynı zamanda nispeten düşük ek maliyetle montaj başına performans dökümünü görmemizi sağlayan bilgileri döküyor.
Yarasadan, time
programın yürütülmesi için 29.5 saniye sürdüğünü söylüyor, bu nedenle bu örnekte yaklaşık 15x'lik bir yavaşlama yaşadık. Açıkçası, bu yavaşlama daha büyük iş yükleri için ciddi bir sınırlama olacaktır. Burada adı geçen "gerçek dünya yazılım örneği" nde 80x'lik bir yavaşlama gözlemledim.
Çalışma, callgrind.out.<pid>
örneğin callgrind.out.8554
benim durumumda adlı bir profil veri dosyası oluşturur . Bu dosyayı şu şekilde görüntülüyoruz:
kcachegrind callgrind.out.8554
Bu, metinsel gprof çıktısına benzer veriler içeren bir GUI'yi gösterir:
Ayrıca, sağ alt "Arama Grafiği" sekmesine gidersek, makul olmayan miktarda beyaz sınırla aşağıdaki resmi elde etmek için sağ tıklayarak dışa aktarabileceğimiz bir arama grafiği görürüz :-)
Bence fast
bu grafikte gösterilmiyor çünkü kcachegrind görselleştirmeyi basitleştirmiş olmalı, çünkü bu çağrı çok az zaman alıyor, bu muhtemelen gerçek bir programda istediğiniz davranış olacak. Sağ tıklama menüsü, bu tür düğümleri ne zaman iptal edeceğinizi kontrol etmek için bazı ayarlara sahiptir, ancak hızlı bir denemeden sonra böyle kısa bir çağrı gösteremedim. fast
Sol pencereye tıklarsam, bir çağrı grafiği gösterilir fast
, böylece yığın gerçekten yakalanır. Hiç kimse henüz tam grafik çağrı grafiğini göstermenin bir yolunu bulamamıştı : callgrind'in kcachegrind çağrı grafiğindeki tüm fonksiyon çağrılarını göstermesini sağlayın
Karmaşık C ++ yazılımında YAPILACAKLAR, bazı tür girişler görüyorum <cycle N>
, örneğin <cycle 11>
fonksiyon isimlerini beklediğim yerde, bu ne anlama geliyor? Bunu açıp kapatmak için bir "Döngü Algılama" düğmesi olduğunu fark ettim, ama bu ne anlama geliyor?
perf
itibaren linux-tools
perf
yalnızca Linux çekirdek örnekleme mekanizmalarını kullanıyor gibi görünüyor. Bu, kurulumu çok basit hale getirir, ancak tam olarak doğru değildir.
sudo apt install linux-tools
time perf record -g ./main.out 10000
Bu, yürütmeye 0,2 saniye ekledi, bu yüzden zaman açısından iyiyiz, ancak common
düğümü klavyenin sağ okuyla genişlettikten sonra hala çok fazla ilgi görmüyorum :
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
O zaman -O0
programı bir şey gösterip göstermediğini görmek için karşılaştırmaya çalışıyorum ve sadece şimdi, bir arama grafiği görüyorum:
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
YAPILACAKLAR: İnfazda ne oldu -O3
? Basitçe bu mu maybe_slow
ve fast
çok hızlıydı ve herhangi bir örnek almadı mı? Yürütülmesi -O3
daha uzun süren daha büyük programlarda iyi çalışır mı ? Bazı CLI seçeneklerini kaçırdım mı? -F
Hertz'de örnek frekansını kontrol etmeyi öğrendim , ancak varsayılan olarak izin verilen maksimum değere getirdim -F 39500
(artırılabilir sudo
) ve hala net çağrılar göremiyorum.
Hakkında harika bir şey perf
, büyük çağrıları hızlı bir şekilde görmenizi sağlayan çağrı yığını zamanlamalarını çok düzgün bir şekilde görüntüleyen Brendan Gregg'in FlameGraph aracıdır. Aracı mevcuttur: https://github.com/brendangregg/FlameGraph ve ayrıca onun perf öğretici üzerinde söz edilir: http://www.brendangregg.com/perf.html#FlameGraphs koştum zaman perf
olmadan sudo
aldım ERROR: No stack counts found
bu yüzden için şimdi bunu yapacağım 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
ancak bu kadar basit bir programda, çıktıyı anlamak çok kolay değildir, çünkü ne grafikte ne maybe_slow
de fast
grafikte kolayca göremeyiz :
Daha karmaşık bir örnekte, grafiğin ne anlama geldiği açıktır:
YAPILACAKLAR [unknown]
Bu örnekte bir fonksiyon günlüğü var , neden?
Buna değer olabilecek başka bir GUI arayüzleri şunları içerir:
Eclipse Trace Compass eklentisi: https://www.eclipse.org/tracecompass/
Ancak bu, önce verileri Ortak İz Biçimi'ne dönüştürmeniz gereken dezavantaja sahiptir perf data --to-ctf
, ancak derleme sırasında etkinleştirilmesi gerekir / perf
yeterince yeni olması gerekir ; Ubuntu 18.04
https://github.com/KDAB/hotspot
Bunun dezavantajı, Ubuntu paketi yok gibi görünüyor ve Ubuntu 18.04 Qt 5.9'da iken, Qt 5.10 gerektiriyor.
gperftools
Daha önce "Google Performans Araçları" olarak adlandırılan kaynak: https://github.com/gperftools/gperftools Örnek tabanlı.
İlk önce gperftools'u aşağıdakilerle kurun:
sudo apt install google-perftools
Ardından, gperftools CPU profil oluşturucuyu iki şekilde etkinleştirebiliriz: çalışma zamanında veya derleme zamanında.
Çalışma zamanında, örneğin sistemimde bulabileceğiniz LD_PRELOAD
noktaya işaret etmeyi geçmeliyiz: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
Alternatif olarak, kütüphaneyi bağlantı zamanında kurabiliriz LD_PRELOAD
ve çalışma zamanında dağıtılır:
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
Ayrıca bakınız: gperftools - profil dosyası dökülmedi
Şimdiye kadar bulduğum bu verileri görüntülemenin en güzel yolu, pprof çıktısını kcachegrind'in girdi olarak aldığı biçim ile (evet, Valgrind-proje-görüntüleyici-aracı) yapmak ve kcachegrind'i kullanmaktır:
google-pprof --callgrind main.out prof.out > callgrind.out
kcachegrind callgrind.out
Bu yöntemlerden herhangi biriyle çalıştıktan sonra prof.out
çıktı olarak bir profil veri dosyası alırız . Bu dosyayı grafiksel olarak bir SVG olarak görüntüleyebiliriz:
google-pprof --web main.out prof.out
diğer araçlar gibi tanıdık bir çağrı grafiği verir, ancak saniyeden ziyade örneklerin saydam birimi ile.
Alternatif olarak, aşağıdakilerle bazı metin verileri de alabiliriz:
google-pprof --text main.out prof.out
hangi verir:
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
Ayrıca bkz: Google perf araçlarını kullanma
Ubuntu 18.04, gprof2dot 2019.11.30, valgrind 3.13.0, perf 4.15.18, Linux çekirdeği 4.15.0, FLameGraph 1a0dc6985aad06e76857cf2a354bd5ba0c9ce96b, gperftools 2.5-2'de test edilmiştir.