Linux 性能解析ツールperfを使ってみた

![perf-system.png](https://qiita-image-store.s3.amazonaws.com/0/258841/ded3a269-2349-433b-c87c-ccb02124fe1e.png)


## 背景

システムコールから先のボトルネック箇所を知りたい事がありまして、その際にperfなるものを教えてもらいました。
使ってみると色々と便利なツールで勉強になったことも多々あるのでそちらのご紹介です。

## 概要

perf(Performance analysis tools for Linux)とはLinuxカーネル2.6.31以降で使用可能なLinuxの性能解析ツールです。
ざっくりいえば下記

* 実行されるプログラムと OS とのイベントを見れる
* OS とハードウェアのイベントを見れる

カーネルのカウンタで計測されるイベントだったりプロセッサで計測されるイベントが知れる
例えばL1のヒット状況やTLBの様子なども観察できます。
(CPUサイクル数、分岐の数、分岐予測ミス数、キャッシュ参照数、キャッシュミス数など豊富な機能)

## 導入

導入はRedHat,centOSならyum,ubuntuならaptで間単にインストールできます。
今回はRHEL7.5にインストール(カーネル3.10.0-862)

``` bash
$ sudo yum install perf

$ perf --version
perf version 3.10.0-862.el7.x86_64.debug
```

## 使い方

perf [サブコマンド]でプロファイルの実行だったり閲覧だったりが出来ます
サブコマンドは下記

サブコマンド|概要
------------|----
stat        |プログラムやある時点のシステムのトータルイベントカウントを計測する
top         |関数単位でCPU使用率を閲覧
record      |一つのプログラムのイベントをサンプリング
report      |recordで生成されたファイルを分析する
list        |調査可能なイベント一覧

メインとして使われるのは上5つとのこと他にも色々機能がありますがこちらはほぼ未調査

``` bash
$ perf --help

 usage: perf [--version] [--help] [OPTIONS] COMMAND [ARGS]

 The most commonly used perf commands are:
   annotate        Read perf.data (created by perf record) and display annotated code
   archive         Create archive with object files with build-ids found in perf.data file
   bench           General framework for benchmark suites
   buildid-cache   Manage build-id cache.
   buildid-list    List the buildids in a perf.data file
   c2c             Shared Data C2C/HITM Analyzer.
   config          Get and set variables in a configuration file.
   data            Data file related processing
   diff            Read perf.data files and display the differential profile
   evlist          List the event names in a perf.data file
   ftrace          simple wrapper for kernel's ftrace functionality
   inject          Filter to augment the events stream with additional information
   kallsyms        Searches running kernel for symbols
   kmem            Tool to trace/measure kernel memory properties
   kvm             Tool to trace/measure kvm guest os
   list            List all symbolic event types
   lock            Analyze lock events
   mem             Profile memory accesses
   record          Run a command and record its profile into perf.data
   report          Read perf.data (created by perf record) and display the profile
   sched           Tool to trace/measure scheduler properties (latencies)
   script          Read perf.data (created by perf record) and display trace output
   stat            Run a command and gather performance counter statistics
   test            Runs sanity tests.
   timechart       Tool to visualize total system behavior during a workload
   top             System profiling tool.
   probe           Define new dynamic tracepoints
   trace           strace inspired tool

 See 'perf help COMMAND' for more information on a specific command.
```

ちなみにmanを見るときはそれぞれper-[サブコマンド]の形で見れます


``` bash
perf-stat(1), perf-top(1), perf-record(1), perf-report(1), perf-list(1)
```

## 実行

#### perf recordを使う

とりあえず簡単なプログラムを書いて実行例を示します。
mmapしてwriteするだけのプログラム。
これに対してperf recordを実行し実行時間が特にかかっている処理を確認します。

``` bash
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <string.h>

#define FILE_SIZE 1024

int main(void) {
    int fd;
    char *map;
    char c = 0;
    long page_size, map_size;

    fd = open("writeFile", O_CREAT | O_RDWR, 0666);
    if(fd < 0) {
        printf("Error : can't open file\n");
        return -1;
    }

    page_size = getpagesize();
    map_size = (FILE_SIZE / page_size + 1) * page_size;

    lseek(fd, map_size, SEEK_SET);
    write(fd, &c, sizeof(char));
    lseek(fd, 0, SEEK_SET);

    map = (char*)mmap(NULL, map_size, PROT_WRITE, MAP_SHARED, fd, 0);
    if(map == MAP_FAILED) {
        printf("Error : mmap failed\n");
        return -1;
    }

    strcat(map, "perf tools test\n");
    strcat(map, "aaaaaaaaaaaaaaa\n");
    strcat(map, "bbbbbbbbbbbbbbb\n");

    msync(map, map_size, 0);

    close(fd);
    munmap(map, map_size);

    return 0;
}
```

上記をコンパイルし実行。perf record, reportを実施

``` bash
# 測定開始
$ perf record ./test
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.014 MB perf.data (8 samples) ]
# 測定結果を確認する
$ perf report | head -n 20
# To display the perf.data header info, please use --header/--header-only options.
#
#
# Total Lost Samples: 0
#
# Samples: 8  of event 'cycles:u'
# Event count (approx.): 441279
#
# Overhead  Command  Shared Object  Symbol
# ........  .......  .............  .......................
#
    59.13%  test     ld-2.17.so     [.] _dl_relocate_object
    34.89%  test     ld-2.17.so     [.] dl_main
     5.98%  test     ld-2.17.so     [.] _start
```

共有ライブラリld-2.17.soの「_dl_relocate_object」が特に時間がかかっていることが分かる。
このように具体的になんの処理が遅いのかの切り分けが出来るようになります。

またperfにはキャッシュミス回数の測定などにも使えるとのこと
下記がとても参考になるので是非ご覧ください。
http://laysakura.hateblo.jp/entry/20111015/1318738362

#### perf statを使う

``` bash
$ perf stat dd if=/dev/zero of=./aaa bs=1M count=32
32+0 records in
32+0 records out
33554432 bytes (34 MB) copied, 0.0183909 s, 1.8 GB/s

 Performance counter stats for 'dd if=/dev/zero of=./aaa bs=1M count=32':

    2440.034235  task-clock-msecs         #      0.134 CPUs
            428  context-switches         #      0.000 M/sec
              4  CPU-migrations           #      0.000 M/sec
            420  page-faults              #      0.000 M/sec
     3857327327  cycles                   #   1580.850 M/sec
      822921345  instructions             #      0.213 IPC
       21888683  cache-references         #      8.971 M/sec
        2781896  cache-misses             #      1.140 M/sec
```

|項目名|概要|
-------|----
task-clock-msecs|CPU使用時間
context-switches|コンテキストスイッチした回数
CPU-migrations|別のCPUに移動した回数
page-faults|ページフォルトした回数
cycles|使ったCPUのクロック数
instructions|キャンセル命令数
cache-references|キャッシュヒット回数
cache-misses|キャッシュミス回数

CPU使用時間は当然ながらコンテキストスイッチの回数だったりキャッシュミス回数だったりが
可視化される。perf_event_open(2)を使用することでイベントカウンタのfdを入手しこれをread()することで
カウント数などを得ている


以下のようなオプションを使うとユーザレベルでの命令回数などが簡単に取得できる

Modifiers|Description|Example
---------|-----------|-------
u        |monitor at priv level 3, 2, 1 (user)|event:u
k        |monitor at priv level 0 (kernel)|event:k
h        |monitor hypervisor events on a virtualization environment|event:h
H        |monitor host machine on a virtualization environment|event:H
G        |monitor guest machine on a virtualization environment|event:G

``` bash:実行例
$ perf stat -e instructions:u dd if=/dev/zero of=/dev/null count=100000
100000+0 records in
100000+0 records out
51200000 bytes (51 MB) copied, 0.0371708 s, 1.4 GB/s

 Performance counter stats for 'dd if=/dev/zero of=/dev/null count=100000':

        40,062,814      instructions:u

       0.038015773 seconds time elapsed
```

ここら辺の仕組みの理解で良いまとめがあれば教えてください。

## まとめ

とりあえずperfのインストールから使い方や考え方について色々調べてみました。
ただ使えてもそこから何を見出すかや傾向分析なんかは経験が一番必要というお話をされました。
(現に使ってみてカーネルのどの部分が遅いなど分かっても対策案を出すにはいたりませんでした)


## 参考リンク

http://www.brendangregg.com/perf.html
http://int.main.jp/txt/perf.html
http://nbisco.hatenablog.com/entry/2016/12/12/000000
http://d.hatena.ne.jp/kumagi/20110327
https://www.ibm.com/developerworks/jp/linux/library/l-analyzing-performance-perf-annotate-trs/index.html