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 -m cProfile -o {output_file_path}.pstats {target_script}.py
コマンドライン引数がある場合は、対象スクリプトパスの後ろにそのまま付加する。
Flatプロファイリング
flat-profileとかかっこいい名前してるが、要はテキストベースでプロファイリング結果出すだけ。
下のコードでpstatsファイルを読み取り出力する。
1from pstats import Stats23pstats_path = "./test_cv_script.pstats"4stats = Stats(pstats_path)5stats.sort_stats("tottime") # 各関数の実行に要した時間(サブ関数の呼び出し時間除く)でソート6stats.print_stats(10) # 上位10件を出力
出力サンプル ↓
Wed Aug 23 20:50:41 2023 ./sample_py.pstats1006 function calls in 0.000 secondsOrdered by: internal timencalls tottime percall cumtime percall filename:lineno(function)1 0.000 0.000 0.000 0.000 {built-in method builtins.print}...
各項目の説明は下の表の通り。
項目 | 説明 |
---|---|
ncalls | 関数が呼び出された回数 |
tottime | 関数の実行に要した時間 (サブ関数の呼び出し時間除く) |
percall | tottime / ncalls |
cumtime | 関数の実行に要した時間 (サブ関数の呼び出し時間含む) |
percall | cumtime / ncalls |
filename:lineno(function) | ファイル名:行数(関数名) |
コールグラフ作成
$ gprof2dot -f pstats {input_file_path}.pstats | dot -Tpng -o {output_file_path}.png
出力サンプル ↓
$ 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 {target_exec}real 0m0.001s <-- プログラム呼び出しから終了までの時間user 0m0.000s <-- プログラム単体の処理時間sys 0m0.001s <-- OSの処理時間
システム全体におけるメモリ使用量などを確認できる。
$ free -htotal used free shared buff/cache availableMem: 24Gi 227Mi 24Gi 0.0Ki 69Mi 24GiSwap: 7.0Gi 0B 7.0Gi
項目 | 説明 | 備考 |
---|---|---|
total | 総メモリ量 | |
used | OSやプロセスによるメモリ使用量 | |
free | 使用されていない空きメモリ量 | |
shared | 共有メモリ量 | |
buff/cache | カーネルバッファ、キャッシュによるメモリ使用量 | buff : カーネルバッファ cache : 高速化のためのキャッシュ |
available | 実質的な空きメモリ量 | free + 解放可能なbuff/cache |
buffとcacheを分けて表示
$ free -h -wtotal used free shared buffers cache availableMem: 24Gi 227Mi 24Gi 0.0Ki 13Mi 55Mi 24GiSwap: 7.0Gi 0B 7.0Gi
任意の時間間隔で表示
$ free -h -s {duration_sec}
freeコマンドによるシステム監視の注意点
freeコマンドにより、システムの空きメモリ量を監視する際はavailable
をみること。
理由は下の通り。
このコマンドで、メモリの空き状況を確認したいのであれば、free を見るよりも available を見ましょう。
というのも、Linux の特性として「空きメモリを無駄にしない」という設計思想の元、free の領域は徐々に、でも着実に buff/cache の領域に割り当てられてしまうからです。
仮想メモリやCPU、ディスクI/Oの統計情報を確認できる。
$ vmstat -wprocs -----------------------memory---------------------- ---swap-- -----io---- -system-- --------cpu--------r b swpd free buff cache si so bi bo in cs us sy id wa st1 0 0 25708836 15048 89544 0 0 1 30 0 1 0 0 100 0 0
第1項目 | 第2項目 | 説明 | 備考 |
---|---|---|---|
procs | r | 実行中と実行待ちのプロセス数 | 数が多いほどシステムが重いと言える |
b | 割り込み不可能なスリープ状態にあるプロセス数 | 数が多いほどシステムが重いと言える (ディスクやネットワークのオーバーヘッドによるもの) | |
memory | swpd | 使用中の仮想メモリ量 | |
free | 空きメモリ量 | freeコマンドのfreeと同値 | |
buff | バッファによるメモリ使用量 | freeコマンドのbuffと同値 | |
cache | キャッシュによるメモリ使用量 | freeコマンドのcacheと同値 | |
inact | 非アクティブなメモリ量 | -a を追加でbuffの代わりに表示 | |
active | アクティブなメモリ量 | -a を追加でcacheの代わりに表示 | |
swap | si | ディスクからスワップインしているメモリ量 | 基本は常に0 そうでない場合はメモリ不足と言える |
so | ディスクからスワップアウトしているメモリ量 | 基本は常に0 そうでない場合はメモリ不足と言える | |
io | bi | ブロックデバイス(HDD)から受け取ったブロック数 | 接頭辞はblocks/s |
bo | ブロックデバイスに送られたブロック数 | 接頭辞はblocks/s | |
system | in | 1秒あたりの割り込み回数 | |
cs | 1秒あたりのコンテキストスイッチの回数 | コンテキストスイッチ : プログラムの実行を切り替えること 回数が多いほどロス(オーバーヘッド)が大きい | |
cpu | us | CPUの総処理時間に対する、ユーザによる処理時間の割合 | |
sy | CPUの総処理時間に対する、OSの処理時間の割合 | ||
id | CPUの総処理時間に対する、アイドル時間の割合 | ||
wa | CPUの総処理時間に対する、I/O待ち時間の割合 | ||
st | CPUの総処理時間に対する、仮想マシンから盗まれた時間の割合 | 仮想環境上で実行している際、 ホストOSにCPUリソースを割り当ててもらえなかった時間の割合 |
※ memory内の各項目の接頭辞は"KiB"。
※ cpu内の各項目の接頭辞は"%"。
任意の時間間隔で表示
$ vmstat -w -n {duration_sec}
※ -n
はヘッダを1度のみ表示するオプション
タイムスタンプを表示
$ vmstat -w -tprocs -----------------------memory---------------------- ---swap-- -----io---- -system-- --------cpu-------- -----timestamp-----r b swpd free buff cache si so bi bo in cs us sy id wa st JST0 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}
プロセスIDやCPU使用率、メモリ使用量などの情報を確認できる。
基本形
$ ps auxUSER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMANDroot 1 0.0 0.0 916 532 ? Sl 05:56 0:00 /initroot 11 0.0 0.0 1272 360 ? Ss 05:56 0:00 /initroot 12 0.0 0.0 1272 368 ? R 05:56 0:00 /inithimazin 13 0.0 0.0 10056 5056 pts/0 Ss 05:56 0:00 -bashhimazin 239 0.0 0.0 10616 3264 pts/0 R+ 06:24 0:00 ps aux
項目 | 説明 |
---|---|
USER | 所有ユーザ名 |
PID | プロセスID |
%CPU | CPU使用率 |
%MEM | 全体におけるメモリ使用量の割合 |
VSZ | 仮想メモリ使用量 |
RSS | 物理メモリ使用量 |
TTY | 制御端末の種類/番号 ? : 端末なし |
STAT | 状態 R : 実行中/実行可能状態 S : 割り込み可なスリープ中 D : 割り込み不可なスリープ中 T : 停止またはトレース中(Ctrl+Zで停止している時など) Z : ゾンビプロセス(終了したがメモリが解放されていない) W : スワップアウトしている I : アイドル中 N : ナイス値が正 |
START | プロセス開始の時刻 |
TIME | CPUの使用時間 |
COMMAND | コマンド名 |
実行中のプロセスだけを表示
$ ps aux rUSER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMANDhimazin 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 psUSER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMANDhimazin 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 rssUSER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMANDroot 11 0.0 0.0 1272 360 ? Ss 05:56 0:00 /initroot 12 0.0 0.0 1272 368 ? S 05:56 0:00 /initroot 1 0.0 0.0 916 524 ? Sl 05:56 0:00 /inithimazin 903 0.0 0.0 10616 3208 pts/0 R+ 07:07 0:00 ps aux --sort rsshimazin 13 0.0 0.0 10056 5064 pts/0 Ss 05:56 0:00 -bash
項目名の前に-
をつけることで降順になる。
$ ps aux --sort -rssUSER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMANDhimazin 13 0.0 0.0 10056 5068 pts/0 Ss 05:56 0:00 -bashhimazin 1234 0.0 0.0 10616 3288 pts/0 R+ 07:19 0:00 ps aux --sort -rssroot 1 0.0 0.0 916 516 ? Sl 05:56 0:00 /initroot 12 0.0 0.0 1272 368 ? S 05:56 0:00 /initroot 11 0.0 0.0 1272 360 ? Ss 05:56 0:00 /init
また、複数指定が可能。
$ ps aux --sort rss,vszUSER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMANDroot 11 0.0 0.0 1272 360 ? Ss 05:56 0:00 /initroot 12 0.0 0.0 1272 368 ? S 05:56 0:00 /initroot 1 0.0 0.0 916 516 ? Sl 05:56 0:00 /inithimazin 1237 0.0 0.0 10616 3216 pts/0 R+ 07:28 0:00 ps aux --sort rss,vszhimazin 13 0.0 0.0 10056 5068 pts/0 Ss 05:56 0:00 -bash
プロセスを階層表示
プロセスが階層表示され、なにがなんの子プロセスなのかがわかる。
$ ps aux froot 1280 0.0 0.0 123048 5488 ? Ss 10月01 0:00 login -- rootroot 2145 0.0 0.0 25300 4816 tty1 Ss+ 10月01 0:00 \_ -bashroot 56089 0.0 0.8 866640 70972 tty1 Sl 10月01 0:01 \_ npm run startroot 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,commandPID RSS COMMAND1 516 /init11 360 /init12 368 /init13 5072 -bash1250 3124 ps ax o pid,rss,command
プロセスID$pid
のプロセスの詳細情報で、プロセスのステータスやリソース使用量などの情報を確認できる。
$ cat /proc/4062/statusName: pythonUmask: 0022State: S (sleeping)...Pid: 4062PPid: 1402...VmPeak: 63164 kBVmSize: 62004 kBVmLck: 0 kBVmPin: 0 kBVmHWM: 60416 kBVmRSS: 59104 kBRssAnon: 47364 kBRssFile: 11740 kBRssShmem: 0 kBVmData: 47116 kBVmStk: 132 kBVmExe: 4 kBVmLib: 7652 kBVmPTE: 156 kBVmSwap: 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
ではps
や/proc/$pid/status
と比べて正確な値を確認することができる。
pmap
は/proc/$pid/smaps
を基に見やすい形で出力される。
基本形
$ pmap -x {pid}
各セグメントのアドレス範囲、権限、メモリサイズ、マップされているファイルやデバイス情報がわかる。
$ pmap -x 331843331843: python3.9 start.pyAddress Kbytes RSS Dirty Mode Mapping0000000000400000 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 331843331843: python3.9 start.pyAddress Perm Offset Device Inode Size Rss Pss Referenced Anonymous LazyFree ShmemPmdMapped FilePmdMapped Shared_Hugetlb Private_Hugetlb Swap SwapPss Locked THPeligible Mapping00400000 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 | マッピングされているデバイスの情報 | |
Inode | iノード番号 | Iノード : ファイル名などのメタデータに対するポインタのようなもの |
Size | 合計メモリサイズ | |
Rss | 物理メモリサイズ | |
Pss | 物理メモリサイズで、共有メモリを均等分割したサイズ | |
Referenced | 現在参照またはアクセスされているメモリサイズ | |
Anonymous | 匿名メモリサイズ | 匿名メモリ : ファイルに関連付けられていないメモリ(ヒープ領域など) |
LazyFree | 遅延して解放がされたかのフラグ | |
ShmemPmdMapped | 共有メモリが大きなページにマッピングされているかのフラグ | |
FilePmdMapped | ファイルが大きなページにマッピングされているかのフラグ | |
Shared_Hugetlb | 共有されている大きなページのサイズ | |
Private_Hugetlb | プライベートな大きなページのサイズ | |
Swap | スワップ領域に存在するメモリサイズ | |
SwapPss | スワップ領域が使用している物理メモリの推定値 | |
Locked | ロックされているかのフラグ | ロック時、スワップアウトはされない |
THPeligible | 大きなTHPページを使用できるかのフラグ | |
Mapping | マップされているファイルやデバイス情報 |
メモリ使用量計測 > ps コマンドで既出の%CPU
でCPU使用率が確認できる。
CPUごとに使用率や割り込みの統計情報を表示できるコマンド。
インストール必須。
Ubuntu
$ sudo apt install sysstat
CentOS
$ sudo dnf install sysstat
基本形
$ mpstat -P ALLLinux 6.1.21-v8+ (HostName) 02/12/23 _aarch64_ (4 CPU)14:46:28 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle14:46:28 all 0.56 0.00 0.20 0.03 0.00 0.04 0.00 0.00 0.00 99.1814:46:28 0 0.24 0.00 0.17 0.03 0.00 0.12 0.00 0.00 0.00 99.4414:46:28 1 0.72 0.00 0.22 0.02 0.00 0.01 0.00 0.00 0.00 99.0314:46:28 2 0.75 0.00 0.23 0.03 0.00 0.01 0.00 0.00 0.00 98.9814: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使用率 |
%iowait | CPUがI/Oの完了を待っている時間の割合 |
%irq | ハードウェア割り込みが処理されているときのCPU使用率 |
%soft | ソフトウェア割り込みが処理されているときのCPU使用率 |
%steal | 仮想マシンから盗まれた時間の割合 |
%quest | 仮想マシン上のプロセスによるCPU使用率 |
%gnice | 仮想マシン上の優先度の低いプロセスによるCPU使用率 |
%idle | CPUがアイドルである時間の割合 |
各プロセスによるリソース使用量をリアルタイム監視できるコマンド。
ここではプロセス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.00.02.01.03.02.02.0
末尾にリダイレクト演算子をつけて外部ファイルに書き込めば、エクセルとかでグラフが簡単に作れて便利。
$9
を任意の列番号に変えればCPU使用率以外の情報も単一の情報として出力できる。
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実行時に意図的に検査を無視(警告抑制)した分のブロック。
各ロスト項目の説明は以下の通り。
new[]
で割り当てた配列へのポインタ、多重継承を使用したオブジェクト内部へのポインタなどがそれに該当する。of which reachable via heuristic:
にその実体(オブジェクト)とメモリサイズが出力される。stdstring
(文字列), length64
(なにこれ), newarray
(配列), multipleinheritance
(多重継承オブジェクト)などは健全な内部ポインタである。各ロスト項目のイメージ↓
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)
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 leaksDirect 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:308SUMMARY: AddressSanitizer: 4 byte(s) leaked in 1 allocation(s).
メモリリークもなく、特にエラーもなければそのまま終了される。
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 Caller0x0000557ee9cc32a0 0x4 at 0x7faa444d8b29
うーん... 4 Byteなにかがリークしてるっていうのはわかるけど...って感じだ。
今回はコードが超簡単なので原因がすぐわかるのだが、小規模なコードでも原因を追うのは難しそう...