C++ Docker Linux

ltraceを用いてstd::vector::push_backの動作を見える化する

投稿日:

仕事で使用したltraceの使い方を、メモがてら残しておきます。

概要

C++標準ライブラリの動作の多くは、ユーザが意識しなくても良いように隠蔽されています。
しかし、(組み込み業界のように)プログラムの実行速度を極限まで小さくしたいという要求から、標準ライブラリが実際にどのような動作をしているのか、把握したいことがあります。

今回は、例としてstd::vector::push_backを挙げ、ltraceを用いた標準ライブラリの動作を見える化する方法を記述します。

環境

  • gcc version 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04.3)
  • Target: x86_64-linux-gnu
  • (Docker上で動作)

設計・実装

cpprefjpによれば、std::vector::push_backでは「再確保の際にそれら要素がぴったり収まるサイズを確保するのではなく、少し多めの1.5倍や2倍といったサイズのメモリを確保し、再確保の回数を減らしている」とあります。

参考:

今回は、std::vector::push_backを適当な回数行い、メモリの再確保が行われているかを確認します。

実装は以下になります。

#include <iostream>
#include <vector>

using namespace std;

int main () {

  vector<int> v;
  int count = 1;

  while (count < 10) {
    printf("======%d, into the vector.======\n", count);
    v.push_back(count);
    count++;
  }

  return 0;
}

int型の変数を格納できるvectorに対して、int型変数1〜9を格納します。

このソースをコンパイルして、実行ファイル(a.out)を生成しておきます。

g++ -Wall -g main.cpp -std=c++11

 

ltraceを用いたdynamic libraryのトレース

確認方法として、ltraceを用います。
ltraceは、dynamic libraryの動作を追跡し、用事してくれるツールです。
今回は、ltraceを用いてglibcの動きを追跡し、newやdelete等のメモリ操作関数の動きをトレースします。

今回はC++のトレースを行いますので、関数をデマングルするため、-Cオプションを使用して実行します。

user@hostname:/tmp# ltrace -C ./a.out 
__libc_start_main(0x400a9d, 1, 0x7ffea9953038, 0x4015f0 <unfinished ...>
std::ios_base::Init::Init()(0x6030a9, 0xffff, 0x7ffea9953048, 3)                    = 0
__cxa_atexit(0x400930, 0x6030a9, 0x6030a0, 6)                                       = 0
printf("======%d, into the vector.======"..., 1======1, into the vector.======
)                                    = 32
operator new(unsigned long)(4, 1, 0, 1)                                             = 0x604010
printf("======%d, into the vector.======"..., 2======2, into the vector.======
)                                    = 32
operator new(unsigned long)(8, 2, 0, 2)                                             = 0x604030
memmove(0x604030, "\001\0\0\0", 4)                                                  = 0x604030
operator delete(void*)(0x604010, 0x604010, 1, 0x604010)                             = 0
printf("======%d, into the vector.======"..., 3======3, into the vector.======
)                                    = 32
operator new(unsigned long)(16, 4, 0, 4)                                            = 0x604010
memmove(0x604010, "\001\0\0\0\002\0\0\0", 8)                                        = 0x604010
operator delete(void*)(0x604030, 0x604030, 2, 0x604030)                             = 0
printf("======%d, into the vector.======"..., 4======4, into the vector.======
)                                    = 32
printf("======%d, into the vector.======"..., 5======5, into the vector.======
)                                    = 32
operator new(unsigned long)(32, 8, 0, 8)                                            = 0x604050
memmove(0x604050, "\001\0\0\0\002\0\0\0\003\0\0\0\004\0\0\0", 16)                   = 0x604050
operator delete(void*)(0x604010, 0x604010, 4, 0x604010)                             = 0x604020
printf("======%d, into the vector.======"..., 6======6, into the vector.======
)                                    = 32
printf("======%d, into the vector.======"..., 7======7, into the vector.======
)                                    = 32
printf("======%d, into the vector.======"..., 8======8, into the vector.======
)                                    = 32
printf("======%d, into the vector.======"..., 9======9, into the vector.======
)                                    = 32
operator new(unsigned long)(64, 16, 0, 16)                                          = 0x604080
memmove(0x604080, "\001\0\0\0\002\0\0\0\003\0\0\0\004\0\0\0\005\0\0\0\006\0\0\0\a\0\0\0\b\0\0\0"..., 32) = 0x604080
operator delete(void*)(0x604050, 0x604050, 8, 0x604050)                             = 0
operator delete(void*)(0x604080, 0x604080, 16, 0x604080)                            = 0
std::ios_base::Init::~Init()(0x6030a9, 0, 160, 0x7f6a22827f10)                      = 0x7f6a22d44020
+++ exited (status 0) +++

要素数が2のべき乗ごとに、operator newが走っているのが確認できます。
また、memmoveで、古い領域から再確保した領域にデータを移して、operator deleteで古い領域を削除していることも確認できます。

ltraceは、-cオプションを用いることで、サマリーを表示することもできます。

user@hostname:/tmp# ltrace -c -C ./a.out 
======1, into the vector.======
======2, into the vector.======
======3, into the vector.======
======4, into the vector.======
======5, into the vector.======
======6, into the vector.======
======7, into the vector.======
======8, into the vector.======
======9, into the vector.======
% time     seconds  usecs/call     calls      function
------ ----------- ----------- --------- --------------------
 39.62    0.006597         733         9 printf
 23.80    0.003962         792         5 operator new(unsigned long)
 15.06    0.002507         501         5 operator delete(void*)
 13.20    0.002198         549         4 memmove
  3.66    0.000610         610         1 std::ios_base::Init::~Init()
  2.79    0.000465         465         1 std::ios_base::Init::Init()
  1.87    0.000311         311         1 __cxa_atexit
------ ----------- ----------- --------- --------------------
100.00    0.016650                    26 total

operator new等が呼ばれた回数を確認することができます。

終わりに

今回は、ltraceを用いてstd::vector::push_backの動作を見える化しました。
新しいライブラリを使用したくなったら、ltraceで動作をテストすることで、詳細動作を把握してから導入の是非を検討しましょう。

メモ

なお、Docker上でltraceをそのまま使用しようとすると、以下のようなエラーがでる事があります。

Could not attach to process.  If your uid matches the uid of the target
process, check the setting of /proc/sys/kernel/yama/ptrace_scope, or try
again as the root user.  For more details, see /etc/sysctl.d/10-ptrace.conf
failed to initialize process 44: No such file or directory
couldn't open program './a.out': No such file or directory

その場合には、docker run 時に以下のオプションを追加してください。

--security-opt seccomp:unconfined

 

-C++, Docker, Linux
-, ,

執筆者:


comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)

関連記事

systemdからdockerコンテナを起動+timerで定期実行

今回はdockerコンテナをsystemdから起動し、かつtimerを使用して定期実行する方法を紹介します。 目次1 背景2 要求と仕様3 systemdについて4 設計・実装4.1 テストアプリとs …

cmake, googletestを利用したC++開発環境の構築テスト

自学用メモ。 業務で使用しているcmakeの勉強がてら、業務に近いソースツリーを作成した。 googletestを用いた単体テストも合わせて書けるような構成になっている。 テンプレートとして、今後のC …

no image

cmakeでのDEBUG/RELEASEモード別・サブディレクトリ別のコンパイルオプションの分け方

仕事で使うので備忘録として。 対象となるソースコードツリーは、拙作のcppのプロジェクトのテンプレートで行う shinjikirino/cpp_project_templatehttps://gith …

Mackerelでサーバ内ホスト・Dockerコンテナを監視する

サーバ監視ソフトを入れたいなと考えていたところ、ひょんなことからMackerelというサーバ監視サービスを発見したため、導入してみました。 環境は以下です。 OS:Ubuntu16.04 目次1 要件 …

Dockerを使用した簡単なC++実行環境の構築

今回は、C++の機能を調査するための簡単なテスト環境を、Dockerを用いて構築する手順を解説します。 目次1 要件2 仕様3 実装3.1 元になるイメージ3.2 Dockerfileの作成3.3 実 …