【備忘録】PythonとC/C++におけるプロファイリングとか調査とか

概要

PythonとC/C++におけるプロファイリングやメモリリーク調査とかの方法をまとめた備忘録。

※随時更新。
※自分への備忘録のため、第三者がみて分かりづらい表現や曖昧さがある表現、厳密性に欠ける表現、誤った解釈があるかと思いますがご了承ください。
※明らかに誤りである情報を見つけた場合はご連絡ください。→ Twitter (X) or Email: contact@himazin331.com

ツール導入

後述するツールの導入方法を記述しておく。

Valgrindインストール

$ sudo apt install valgrind

graphviz(dot), gprof2dotインストール

$ sudo apt install graphviz
$ pip3 install gprof2dot

ツール導入と関係ないが、C/C++においては前提として、デバッグオプションを付けてビルドする。最適化も無効にする。

$ g++ -o {output_program} {soruce_file} -g -O0

ただし、パフォーマンス測定においてはこの限りではない。

プロファイリング

プログラムのプロファイリング方法についてまとめる。

Pythonでのプロファイリング

$ python -m cProfile -o {output_file_path}.pstats {target_script}.py

コマンドライン引数がある場合は、対象スクリプトパスの後ろにそのまま付加する。

Flatプロファイリング
flat-profileとかかっこいい名前してるが、要はテキストベースでプロファイリング結果出すだけ。

下のコードでpstatsファイルを読み取り出力する。

1
from pstats import Stats
2
3
pstats_path = "./test_cv_script.pstats"
4
stats = Stats(pstats_path)
5
stats.sort_stats("tottime") # 各関数の実行に要した時間(サブ関数の呼び出し時間除く)でソート
6
stats.print_stats(10) # 上位10件を出力

出力サンプル ↓

Wed Aug 23 20:50:41 2023 ./sample_py.pstats
1006 function calls in 0.000 seconds
Ordered by: internal time
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.000 0.000 {built-in method builtins.print}
...

各項目の説明は下の表の通り。

項目説明
ncalls関数が呼び出された回数
tottime関数の実行に要した時間 (サブ関数の呼び出し時間除く)
percalltottime / ncalls
cumtime関数の実行に要した時間 (サブ関数の呼び出し時間含む)
percallcumtime / ncalls
filename:lineno(function)ファイル名:行数(関数名)

コールグラフ作成

$ gprof2dot -f pstats {input_file_path}.pstats | dot -Tpng -o {output_file_path}.png

出力サンプル ↓
Pythonコールグラフ

C/C++でのプロファイリング

$ valgrind --tool=callgrind --callgrind-out-file={output_file_path} {target_exec}

コマンドライン引数がある場合は、対象実行ファイルパスの後ろにそのまま付加する。

コールグラフ作成

$ gprof2dot -f callgrind {input_file_path} | dot -Tpng -o {output_file_path}.png

出力形式はPythonのと同じ。

パフォーマンス測定

プログラムの性能調査として、処理時間やメモリ使用量、CPU使用率を監視することがある。

プロセスまたはシステムの監視に際して、各コマンドの使用法をまとめる。

処理時間計測

time コマンド

結局これが一番シンプルで使いやすい。

$ time {target_exec}
real 0m0.001s <-- プログラム呼び出しから終了までの時間
user 0m0.000s <-- プログラム単体の処理時間
sys 0m0.001s <-- OSの処理時間

メモリ使用量計測

free コマンド

システム全体におけるメモリ使用量などを確認できる。

$ free -h
total used free shared buff/cache available
Mem: 24Gi 227Mi 24Gi 0.0Ki 69Mi 24Gi
Swap: 7.0Gi 0B 7.0Gi
項目説明備考
total総メモリ量
usedOSやプロセスによるメモリ使用量
free使用されていない空きメモリ量
shared共有メモリ量
buff/cacheカーネルバッファ、キャッシュによるメモリ使用量buff : カーネルバッファ
cache : 高速化のためのキャッシュ
available実質的な空きメモリ量free + 解放可能なbuff/cache

buffとcacheを分けて表示

$ free -h -w
total used free shared buffers cache available
Mem: 24Gi 227Mi 24Gi 0.0Ki 13Mi 55Mi 24Gi
Swap: 7.0Gi 0B 7.0Gi

任意の時間間隔で表示

$ free -h -s {duration_sec}

freeコマンドによるシステム監視の注意点
freeコマンドにより、システムの空きメモリ量を監視する際はavailableをみること。

理由は下の通り。

このコマンドで、メモリの空き状況を確認したいのであれば、free を見るよりも available を見ましょう。

というのも、Linux の特性として「空きメモリを無駄にしない」という設計思想の元、free の領域は徐々に、でも着実に buff/cache の領域に割り当てられてしまうからです。

引用元: Linuxのfree コマンドの見方とオプション ~availableやbuff/cacheの定義~

vmstatコマンド

仮想メモリやCPU、ディスクI/Oの統計情報を確認できる。

$ vmstat -w
procs -----------------------memory---------------------- ---swap-- -----io---- -system-- --------cpu--------
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 25708836 15048 89544 0 0 1 30 0 1 0 0 100 0 0
第1項目第2項目説明備考
procsr実行中と実行待ちのプロセス数数が多いほどシステムが重いと言える
b割り込み不可能なスリープ状態にあるプロセス数数が多いほどシステムが重いと言える
(ディスクやネットワークのオーバーヘッドによるもの)
memoryswpd使用中の仮想メモリ量
free空きメモリ量freeコマンドのfreeと同値
buffバッファによるメモリ使用量freeコマンドのbuffと同値
cacheキャッシュによるメモリ使用量freeコマンドのcacheと同値
inact非アクティブなメモリ量-aを追加でbuffの代わりに表示
activeアクティブなメモリ量-aを追加でcacheの代わりに表示
swapsiディスクからスワップインしているメモリ量基本は常に0
そうでない場合はメモリ不足と言える
soディスクからスワップアウトしているメモリ量基本は常に0
そうでない場合はメモリ不足と言える
iobiブロックデバイス(HDD)から受け取ったブロック数接頭辞はblocks/s
boブロックデバイスに送られたブロック数接頭辞はblocks/s
systemin1秒あたりの割り込み回数
cs1秒あたりのコンテキストスイッチの回数コンテキストスイッチ : プログラムの実行を切り替えること
回数が多いほどロス(オーバーヘッド)が大きい
cpuusCPUの総処理時間に対する、ユーザによる処理時間の割合
syCPUの総処理時間に対する、OSの処理時間の割合
idCPUの総処理時間に対する、アイドル時間の割合
waCPUの総処理時間に対する、I/O待ち時間の割合
stCPUの総処理時間に対する、仮想マシンから盗まれた時間の割合仮想環境上で実行している際、
ホストOSにCPUリソースを割り当ててもらえなかった時間の割合

※ memory内の各項目の接頭辞は"KiB"。
※ cpu内の各項目の接頭辞は"%"。

任意の時間間隔で表示

$ vmstat -w -n {duration_sec}

-nはヘッダを1度のみ表示するオプション

タイムスタンプを表示

$ vmstat -w -t
procs -----------------------memory---------------------- ---swap-- -----io---- -system-- --------cpu-------- -----timestamp-----
r b swpd free buff cache si so bi bo in cs us sy id wa st JST
0 0 0 25708528 15048 89544 0 0 0 12 0 1 0 0 100 0 0 2023-10-09 02:40:58

指定した接頭辞で表示
kはKB、KはKiB、mはMB、MはMiB。

$ vmstat -w -S {k/K/m/M}

psコマンド

プロセスIDやCPU使用率、メモリ使用量などの情報を確認できる。

基本形

$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 916 532 ? Sl 05:56 0:00 /init
root 11 0.0 0.0 1272 360 ? Ss 05:56 0:00 /init
root 12 0.0 0.0 1272 368 ? R 05:56 0:00 /init
himazin 13 0.0 0.0 10056 5056 pts/0 Ss 05:56 0:00 -bash
himazin 239 0.0 0.0 10616 3264 pts/0 R+ 06:24 0:00 ps aux
項目説明
USER所有ユーザ名
PIDプロセスID
%CPUCPU使用率
%MEM全体におけるメモリ使用量の割合
VSZ仮想メモリ使用量
RSS物理メモリ使用量
TTY制御端末の種類/番号
? : 端末なし
STAT状態
R : 実行中/実行可能状態
S : 割り込み可なスリープ中
D : 割り込み不可なスリープ中
T : 停止またはトレース中(Ctrl+Zで停止している時など)
Z : ゾンビプロセス(終了したがメモリが解放されていない)
W : スワップアウトしている
I : アイドル中
N : ナイス値が正
STARTプロセス開始の時刻
TIMECPUの使用時間
COMMANDコマンド名

実行中のプロセスだけを表示

$ ps aux r
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
himazin 253 0.0 0.0 10616 3228 pts/0 R+ 06:55 0:00 ps aux r

実行ファイル名を指定して表示1
指定する実行ファイル名は完全一致であること。(ただし./-は不要)

$ ps u -C {exec_file_name}
$ ps u -C ps
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
himazin 885 0.0 0.0 10616 3356 pts/0 R+ 07:03 0:00 ps u -C ps

実行ファイル名を指定して表示2
結局これが一番確実。

$ ps aux | grep {exec_file_name}

指定の項目でソートして表示
項目は小文字で指定する。

$ ps aux --sort {[-]lower_column[,lower_column]}
$ ps aux --sort rss
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 11 0.0 0.0 1272 360 ? Ss 05:56 0:00 /init
root 12 0.0 0.0 1272 368 ? S 05:56 0:00 /init
root 1 0.0 0.0 916 524 ? Sl 05:56 0:00 /init
himazin 903 0.0 0.0 10616 3208 pts/0 R+ 07:07 0:00 ps aux --sort rss
himazin 13 0.0 0.0 10056 5064 pts/0 Ss 05:56 0:00 -bash

項目名の前に-をつけることで降順になる。

$ ps aux --sort -rss
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
himazin 13 0.0 0.0 10056 5068 pts/0 Ss 05:56 0:00 -bash
himazin 1234 0.0 0.0 10616 3288 pts/0 R+ 07:19 0:00 ps aux --sort -rss
root 1 0.0 0.0 916 516 ? Sl 05:56 0:00 /init
root 12 0.0 0.0 1272 368 ? S 05:56 0:00 /init
root 11 0.0 0.0 1272 360 ? Ss 05:56 0:00 /init

また、複数指定が可能。

$ ps aux --sort rss,vsz
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 11 0.0 0.0 1272 360 ? Ss 05:56 0:00 /init
root 12 0.0 0.0 1272 368 ? S 05:56 0:00 /init
root 1 0.0 0.0 916 516 ? Sl 05:56 0:00 /init
himazin 1237 0.0 0.0 10616 3216 pts/0 R+ 07:28 0:00 ps aux --sort rss,vsz
himazin 13 0.0 0.0 10056 5068 pts/0 Ss 05:56 0:00 -bash

プロセスを階層表示
プロセスが階層表示され、なにがなんの子プロセスなのかがわかる。

$ ps aux f
root 1280 0.0 0.0 123048 5488 ? Ss 10月01 0:00 login -- root
root 2145 0.0 0.0 25300 4816 tty1 Ss+ 10月01 0:00 \_ -bash
root 56089 0.0 0.8 866640 70972 tty1 Sl 10月01 0:01 \_ npm run start
root 56250 0.0 2.2 22027424 175188 tty1 Sl 10月01 1:36 \_ node /var/www/homepage-blog/node_modules/.bin/next start

項目を絞って表示
項目は小文字で指定する。

$ ps ax o {lower_column[,lower_column]}
$ ps ax o pid,rss,command
PID RSS COMMAND
1 516 /init
11 360 /init
12 368 /init
13 5072 -bash
1250 3124 ps ax o pid,rss,command

/proc/$pid/status

プロセスID$pidのプロセスの詳細情報で、プロセスのステータスやリソース使用量などの情報を確認できる。

$ cat /proc/4062/status
Name: python
Umask: 0022
State: S (sleeping)
...
Pid: 4062
PPid: 1402
...
VmPeak: 63164 kB
VmSize: 62004 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 60416 kB
VmRSS: 59104 kB
RssAnon: 47364 kB
RssFile: 11740 kB
RssShmem: 0 kB
VmData: 47116 kB
VmStk: 132 kB
VmExe: 4 kB
VmLib: 7652 kB
VmPTE: 156 kB
VmSwap: 0 kB
...
項目説明備考
Nameプロセス名
State状態R : 実行中/実行可能状態
S : 割り込み可なスリープ中
D : 割り込み不可なスリープ中
T : 停止またはトレース中(Ctrl+Zで停止している時など)
Z : ゾンビプロセス(終了したがメモリが解放されていない)
W : スワップアウトしている
PidプロセスID
PPid親プロセスID
VmPeak仮想メモリのピークサイズ
VmSize仮想メモリサイズスワップ領域を含むサイズ
VmLckロックされたメモリサイズスワップアウトされない
VmPin固定メモリサイズスワップアウトされない
VmHWM物理メモリのピークサイズ
VmRSS物理メモリサイズスワップ領域を含まないサイズ
RssAnon物理メモリサイズのうち、匿名メモリが占めるサイズ匿名メモリ : ファイルに関連付けられていないメモリ(ヒープ領域など)
RssFile物理メモリサイズのうち、ファイルに関連付けられたページが占めるサイズプロセスがファイル読込/書込した際に占有されたメモリ量
RssShmem物理メモリサイズのうち、共有メモリが占めるサイズ
VmData仮想メモリサイズのうち、初期化済みデータ領域が占めるサイズ静的変数や初期化済みグローバル変数など
VmStk仮想メモリサイズのうち、スタック領域が占めるサイズ関数内ローカル変数やコール情報など
VmExe仮想メモリサイズのうち、実行可能なコードが占めるサイズ実行可能なコード=テキスト領域
VmLib仮想メモリサイズのうち、共有ライブラリが占めるサイズ
VmPTEページテーブルエントリが占めるサイズページテーブル : 仮想メモリと物理メモリの対応を管理するデータ構造
VmSwapスワップ領域にスワップアウトされているメモリサイズ
Threads使用しているCPUスレッド数

pmap コマンド

プロセスのメモリマッピング情報を確認できるコマンド。pmapではps/proc/$pid/statusと比べて正確な値を確認することができる。
pmap/proc/$pid/smapsを基に見やすい形で出力される。

基本形

$ pmap -x {pid}

各セグメントのアドレス範囲、権限、メモリサイズ、マップされているファイルやデバイス情報がわかる。

$ pmap -x 331843
331843: python3.9 start.py
Address Kbytes RSS Dirty Mode Mapping
0000000000400000 124 124 0 r---- python3.9
...
000000000098a000 144 136 136 rw--- [ anon ]
...
00007fd15401c000 12 12 0 r---- libgcc_s.so.1
...
---------------- ------- ------- -------
total kB 200888 44100 34716

※ Dirty: 最後にディスクから読み取られてから変更されたメモリの量

詳細出力

$ pmap -X {pid}

より詳細な情報が表示される。各項目の説明は後述。

$ pmap -X 331843
331843: python3.9 start.py
Address Perm Offset Device Inode Size Rss Pss Referenced Anonymous LazyFree ShmemPmdMapped FilePmdMapped Shared_Hugetlb Private_Hugetlb Swap SwapPss Locked THPeligible Mapping
00400000 r--p 00000000 fd:00 414154 124 124 124 124 0 0 0 0 0 0 0 0 0 0 python3.9
...
0098a000 rw-p 00000000 00:00 0 144 136 136 128 136 0 0 0 0 0 0 0 0 0
...
7fd15401c000 r--p 00000000 fd:00 396148 12 12 1 12 0 0 0 0 0 0 0 0 0 0 libgcc_s.so.1
...
====== ===== ===== ========== ========= ======== ============== ============= ============== =============== ==== ======= ====== ===========
201020 44108 41291 35920 34724 0 0 0 0 0 0 0 0 0 KB
項目説明備考
Addressアドレス範囲
Permメモリアクセス権限
Offsetファイル内オフセットオフセット : マッピングされたメモリ領域がファイルの先頭からどれだけ離れているかを示す
Deviceマッピングされているデバイスの情報
Inodeiノード番号Iノード : ファイル名などのメタデータに対するポインタのようなもの
Size合計メモリサイズ
Rss物理メモリサイズ
Pss物理メモリサイズで、共有メモリを均等分割したサイズ
Referenced現在参照またはアクセスされているメモリサイズ
Anonymous匿名メモリサイズ匿名メモリ : ファイルに関連付けられていないメモリ(ヒープ領域など)
LazyFree遅延して解放がされたかのフラグ
ShmemPmdMapped共有メモリが大きなページにマッピングされているかのフラグ
FilePmdMappedファイルが大きなページにマッピングされているかのフラグ
Shared_Hugetlb共有されている大きなページのサイズ
Private_Hugetlbプライベートな大きなページのサイズ
Swapスワップ領域に存在するメモリサイズ
SwapPssスワップ領域が使用している物理メモリの推定値
Lockedロックされているかのフラグロック時、スワップアウトはされない
THPeligible大きなTHPページを使用できるかのフラグ
Mappingマップされているファイルやデバイス情報

CPU使用率計測

ps コマンド

メモリ使用量計測 > ps コマンドで既出の%CPUでCPU使用率が確認できる。

mpstat コマンド

CPUごとに使用率や割り込みの統計情報を表示できるコマンド。

インストール必須。
Ubuntu

$ sudo apt install sysstat

CentOS

$ sudo dnf install sysstat

基本形

$ mpstat -P ALL
Linux 6.1.21-v8+ (HostName) 02/12/23 _aarch64_ (4 CPU)
14:46:28 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
14:46:28 all 0.56 0.00 0.20 0.03 0.00 0.04 0.00 0.00 0.00 99.18
14:46:28 0 0.24 0.00 0.17 0.03 0.00 0.12 0.00 0.00 0.00 99.44
14:46:28 1 0.72 0.00 0.22 0.02 0.00 0.01 0.00 0.00 0.00 99.03
14:46:28 2 0.75 0.00 0.23 0.03 0.00 0.01 0.00 0.00 0.00 98.98
14:46:28 3 0.56 0.00 0.16 0.02 0.00 0.01 0.00 0.00 0.00 99.26
項目説明
%usrユーザレベルのプロセスによるCPU使用率
%niceユーザレベルの優先度の低いプロセスによるCPU使用率
%sysシステムレベルのプロセスによるCPU使用率
%iowaitCPUがI/Oの完了を待っている時間の割合
%irqハードウェア割り込みが処理されているときのCPU使用率
%softソフトウェア割り込みが処理されているときのCPU使用率
%steal仮想マシンから盗まれた時間の割合
%quest仮想マシン上のプロセスによるCPU使用率
%gnice仮想マシン上の優先度の低いプロセスによるCPU使用率
%idleCPUがアイドルである時間の割合

top コマンド

各プロセスによるリソース使用量をリアルタイム監視できるコマンド。

ここではプロセスIDpidのCPU使用率をseconds秒ごとに出力するコマンドを掲載する。

$ top -d {seconds} -b -p {pid} | awk '{if($1 == {pid}) print $9}'

例えば、プロセスID 535のCPU使用率を1秒ごとにみるには以下のコマンドで実現できる。

$ top -d 1 -b -p 535 | awk '{if($1 == 535) print $9}'
0.0
0.0
2.0
1.0
3.0
2.0
2.0

末尾にリダイレクト演算子をつけて外部ファイルに書き込めば、エクセルとかでグラフが簡単に作れて便利。
$9を任意の列番号に変えればCPU使用率以外の情報も単一の情報として出力できる。

メモリリーク調査

Memcheck(Valgrind)

Valgrindに収録されてるメモリリーク検出ツール。

最小限コマンド

$ valgrind --tool=memcheck --log-file={log_file_path} --leak-check=full {target_exec}

私的推奨コマンド

$ valgrind --tool=memcheck --log-file={log_file_path} --leak-check=full --show-leak-kinds=all --keep-debuginfo=yes -s {target_exec}

オプション説明

オプション説明
--log-fileログファイル出力パス
--leak-checkメモリリーク検索の有効/無効
full : 検索有効
summary : 検索無効 (検知数のみ出力)
--show-leak-kinds表示するリーク項目
definite : 確実なロスト
indirect : 間接的なロスト
possible : 可能性のあるロスト
reachable : 到達可能
all : すべての項目
--keep-debuginfoデバッグ情報を保持するか
yes : 保持する
-sプログラム実行中のエラーをリスト表示する--show-error-list=yesと同等

ヒープサマリ

プログラム終了時点で確保されてるメモリ量と、プログラム実行中に確保・解放した回数と割り当てられた総バイト数が出力されている。

==340== HEAP SUMMARY:
==340== in use at exit: 573,983 bytes in 4,575 blocks <--- プログラム終了時点で約560.5KB(4,575 blocks)確保されている。
==340== total heap usage: 257,566 allocs, 252,991 frees, 1,706,413,665 bytes allocated <--- 257,566回メモリ確保し、252,991回解放した。総バイト数約1.59GB。

理想はin use at exitが0バイト/0ブロックでallocs <= freesとなっていることだが...非常に簡単なコードでない限りそれは無理だと思う...
少なくともOpenCVなどの外部ライブラリを使うと出力サンプルのような結果になる。

しかし通常、これらのメモリはプログラム終了後に解放される"はず"なので、ここでの結果が理想と違っても即座に問題とはならない
そしてこれらのメモリブロックのほとんどは後述するstill reachableに該当するブロックである。

※極稀なケースとして、プログラム終了後にも関わらずメモリ解放されない→メモリリークを起こしている可能性も考えられるが、Memcheckでは実行終了後のメモリ操作を捕捉できないためここではどうしようもない

リークサマリ

名前の通り、リーク概要。
確保したメモリブロックに到達不可能であることを"lost"と言ってる。

※メモリブロックを参照する術を失い、ブロックだけ取り残され(メモリリーク)、メモリ領域を食い潰していきクラッシュする

==340== LEAK SUMMARY:
==340== definitely lost: 0 bytes in 0 blocks <--- 確実なロスト。
==340== indirectly lost: 0 bytes in 0 blocks <--- 間接的なロスト。
==340== possibly lost: 10,088 bytes in 32 blocks <--- 可能性のあるロスト。
==340== still reachable: 563,895 bytes in 4,543 blocks <--- 到達可能(Not lost)。
==340== of which reachable via heuristic: <--- ヒューリスティック分析によるオブジェクト内訳
==340== newarray : 1,536 bytes in 16 blocks <--- 1.5KBものarrayが確保され到達可能。
==340== suppressed: 0 bytes in 0 blocks <--- Memcheck実行時に意図的に検査を無視(警告抑制)した分のブロック。

各ロスト項目の説明は以下の通り。

  • definitely lost
    確実にロストしたメモリブロック。
    これが0 bytesでないとアウト。
  • indirectly lost
    間接的にロストしたメモリブロック。
    definitely lostとされるメモリブロックに関連付けられたメモリブロックが道連れとなりロストしたケース。
  • possibly lost
    ロストの可能性があるメモリブロック。
    <ブロックには到達可能であるが、参照ポインタが開始ポインタではなく内部ポインタである/が含まれる
    即座に問題とはならないが、意図的に内部ポインタとしていない場合は問題となる。(偶然、ポインタがそのブロックを指し示しているだけの可能性があるため)

    ※開始ポインタとは、ブロックの先頭を指し示すポインタ。通常すべてのポインタは開始ポインタである。
    ※内部ポインタとは、ブロックの先頭以外を指し示すポインタ。
     意図的あるいは強制的に先頭部から移動させたり、new[]で割り当てた配列へのポインタ、多重継承を使用したオブジェクト内部へのポインタなどがそれに該当する。
  • still reachable
    到達可能なメモリブロック。
    すなわちメモリリークの原因とならないブロックである。

    このうち、ブロック参照ポインタが内部ポインタであったメモリブロックで、ヒューリスティック分析により健全であると判断されれば、
    of which reachable via heuristic:にその実体(オブジェクト)とメモリサイズが出力される。

    つまり通常、内部ポインタである/が含む場合はpossibly lostとされるが、ヒューリスティック分析により意図的に内部ポインタになったと確実に言える場合であればstill reachableと判断される。
    stdstring(文字列), length64(なにこれ), newarray(配列), multipleinheritance(多重継承オブジェクト)などは健全な内部ポインタである。
  • suppressed
    警告抑制により無視されたメモリブロック。
    Memcheckは誤検出をしばしば起こすもので、明らかにバグでないときでもロストしたと警告することがある。
    そういった場合に、作業者が"suppression"と呼ばれる警告抑制のファイルを作成し、意図的の特定の処理のメモリリーク検査を無視させることがある。
    そこで無視されたメモリブロックがここに出力される。

各ロスト項目のイメージ↓
Memcheckロストイメージ

注意

Memcheck実行により得た結果からメモリリークの有無を判断するにあたって、動作環境に差異があることを念頭におくべきである。

Memcheckでは、malloc()といったメモリ操作関連の関数が、標準ライブラリの関数からMemcheckがもつライブラリの関数に置き換えられる
またMemcheckはその動作の性質上、ネイティブの実行環境と差異が発生するために、通常のプログラム実行では起こり得なかったエラーがMemcheck実行により発生することがしばしばある。(その逆もある)

以上のような環境差異を考慮したうえで適切な判断をするよう心がける。

出力サンプル ↓

==340== Memcheck, a memory error detector
==340== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==340== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==340== Command: _old/test_cv_program data/test_video.mp4
==340== Parent PID: 17
==340==
==340==
==340== HEAP SUMMARY:
==340== in use at exit: 573,983 bytes in 4,575 blocks
==340== total heap usage: 257,566 allocs, 252,991 frees, 1,706,413,665 bytes allocated
==340==
==340== 1 bytes in 1 blocks are still reachable in loss record 1 of 3,800
==340== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==340== by 0x8E0FAFE: VSIStrdup (in /usr/lib/libgdal.so.26.0.4)
==340== by 0x8E0FE9A: VSIStrdupVerbose (in /usr/lib/libgdal.so.26.0.4)
==340== by 0x8DBADB6: CSLAddStringMayFail (in /usr/lib/libgdal.so.26.0.4)
==340== by 0x8DBAE5C: CSLAddString (in /usr/lib/libgdal.so.26.0.4)
==340== by 0x88A30EB: GDALMultiDomainMetadata::SetMetadata(char**, char const*) (in /usr/lib/libgdal.so.26.0.4)
==340== by 0x88A323C: GDALMultiDomainMetadata::SetMetadataItem(char const*, char const*, char const*) (in /usr/lib/libgdal.so.26.0.4)
==340== by 0x87DD695: GDALRegister_VRT (in /usr/lib/libgdal.so.26.0.4)
==340== by 0x850CBB6: GDALAllRegister (in /usr/lib/libgdal.so.26.0.4)
==340== by 0x588C9A4: ??? (in /usr/lib/x86_64-linux-gnu/libopencv_imgcodecs.so.4.2.0)
==340== by 0x5880B28: ??? (in /usr/lib/x86_64-linux-gnu/libopencv_imgcodecs.so.4.2.0)
==340== by 0x587D009: ??? (in /usr/lib/x86_64-linux-gnu/libopencv_imgcodecs.so.4.2.0)
~ 中略 ~
==340== LEAK SUMMARY:
==340== definitely lost: 0 bytes in 0 blocks
==340== indirectly lost: 0 bytes in 0 blocks
==340== possibly lost: 10,088 bytes in 32 blocks
==340== still reachable: 563,895 bytes in 4,543 blocks
==340== of which reachable via heuristic:
==340== newarray : 1,536 bytes in 16 blocks
==340== suppressed: 0 bytes in 0 blocks
==340==
==340== ERROR SUMMARY: 20 errors from 20 contexts (suppressed: 0 from 0)

AddressSanitizer(ASan)

gcc(g++)に組み込まれてるメモリリーク検出ツール。
プログラムのビルド時にフラグを立てる必要がある。

g++でビルド

$ g++ -o sample_prof sample_prof.cpp -g -O0 -fsanitize=address

mesonでビルド

$ meson builddir --buildtype=debug -Db_sanitize=address -Db_lundef=false && ninja -C builddir

あとは通常通りプログラムを実行するだけ。

出力サンプル ↓

$ ./sample_prog
=================================================================
==3913==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 4 byte(s) in 1 object(s) allocated from:
#0 0x7f8033f65587 in operator new(unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cc:104
#1 0x55d1a752c27e in main /home/himazin/work/DemoScript/sample_prog.cpp:5
#2 0x7f803393c082 in __libc_start_main ../csu/libc-start.c:308
SUMMARY: AddressSanitizer: 4 byte(s) leaked in 1 allocation(s).

メモリリークもなく、特にエラーもなければそのまま終了される。

mtrace

mcheck.hのmtrace()を使ったメモリリーク検出。
怪しい箇所に絞って検証することができる。

サンプルコード

#include <iostream>
#include <mcheck.h>
int main() {
mtrace(); // メモリリークの検出開始
int* dynamicInt = new int;
*dynamicInt = 42;
// delete dynamicInt; // 解放漏れ
muntrace(); // メモリリークの検出終了
return 0;
}

検出結果を確認する流れは以下の通り。

$ export MALLOC_TRACE="/home/himazin/work/DemoScript/sample_mtrace.log" <-- 検出結果の出力先
$ ./sample_prog
$ mtrace sample_prog sample_mtrace.log <-- 結果の解析
Memory not freed:
-----------------
Address Size Caller
0x0000557ee9cc32a0 0x4 at 0x7faa444d8b29

うーん... 4 Byteなにかがリークしてるっていうのはわかるけど...って感じだ。

今回はコードが超簡単なので原因がすぐわかるのだが、小規模なコードでも原因を追うのは難しそう...

参考