ROBOT PAYMENT TECH-BLOG

株式会社ROBOT PAYMENTのテックブログです

Visual Studioでプロファイル分析やってみた

どうも!ペイメントシステム課の川上です。
開発したシステムを運用していく中で「特定の操作をすると処理に時間がかかる」「長時間使用すると動作が遅くなる」等の問題に直面することがあります。 リリース前のテストで問題がなくても、データが蓄積されることで顕在化するケースもあり、厄介な問題です。
再現手順がはっきりしている場合は、Visual Studioのパフォーマンス プロファイラーで原因を探ることができるかもしれません。

実際にやってみよう

今回は Visual Studio Professional 2022 Version 17.4.2 を使用します(パフォーマンス プロファイラーはCommunity版でも使用できます)。
パフォーマンス プロファイラーには多くの機能がありとてもすべてを紹介しきることができません。
そこで今回は、もっとも分かりやすいと思われるオブジェクトの割り当てに注目してみます。

サンプルコードの準備

まずは実験用に適当なコードを用意します。
なるべくシンプルな構成にするためにWPFでデスクトップアプリケーションを作成します。

  1. Visual Studioを起動
  2. 「新しいプロジェクトの作成」を選択
  3. プロジェクトテンプレートから「WPF アプリケーション」(C#)を選択して「次へ」を押下
  4. . プロジェクト名や場所などはそのままで「次へ」を押下
  5. フレームワークはそのままで「作成」を押下 以下のような画面が表示されたらまずはOKです。

次にボタンを追加します。
MainWindow.xamlの10行目に以下のコードを追加します。

<Button HorizontalAlignment="Center" VerticalAlignment="Center" Content="GO!!" Click="Button_Click" />

次はMainWindow.xaml.csです。
先ほど追加したボタンを押下するたびに、100~200MBの範囲でランダムにメモリーを確保する処理を追加します。

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private List<MyObject> _bufferList = new();

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        _bufferList.Add(new MyObject());
    }
}

internal class MyObject
{
    private byte[] _buffer = new byte[new Random().Next(100_000_000, 200_000_000)];
    public int BufferSize => _buffer.Length;
}

これで準備完了です!!

プロファイリングしてみる

Visual Studioの「デバッグ」メニューから「パフォーマンス プロファイラー」を選択すると以下の画面が表示されます。
「.NETオブジェクト割り当て追跡」チェックをONにして「開始」ボタンを押下すると、先ほど作成したWPFアプリケーションが起動して分析がスタートします。

今回は以下のような操作を行ってみました。

  1. 分析開始から10秒経ったらアプリの「GO!!」ボタンを押下する
  2. 以降、2秒ごとにボタンを押下する
  3. 全部で5回ボタンを押したらアプリを終了する

結果を確認してみる

グラフを順に見ていきましょう。
一番上のグラフは「ライブ オブジェクト」。その名の通りその時点でのオブジェクト数を表しています。
真ん中の「オブジェクトの差分」は、そのタイミングでオブジェクトがどのくらい変化したかを表しています。
一番下の「プライベート バイト」はアプリに割り当てられたメモリー量です。

もっとも分かりやすいのはプライベート バイトでしょうか。最初の2秒くらいで初期化処理が行われ、10秒を過ぎたくらいから5回グラフが上がっています。これはボタンを押下するたびにメモリーが確保される実装と一致しています。

ライブ オブジェクトを見ると、2回目のボタン押下のタイミングでグッとオブジェクトが減っていることが分かります。
オブジェクトの差分には赤色のバーが現れており、カーソルを合わせると -56.27% という値が見て取れます。

赤色のバーはガベージコレクションを表すので、この時点でガベージコレクションが実行されて半数以上のオブジェクトが回収されたことが分かります。
ガベージコレクションのタイミングでは、プライベート バイトのグラフは下がっていません。サンプルコードでは _bufferList で強参照を保持しているので期待通りの動作ですね。

呼び出しツリーも見てみましょう。こちらではメモリーを多く割り当てているメソッドがホット パス(炎のアイコン)として確認できます。
アプリの操作全体を通じて、MyObjectクラスのコンストラクタで約711MBのメモリーが確保されたことが分かりますね。

さらにツリーを展開してみます。MyObjectクラスは、初期化時に100~200MBの範囲でランダムにメモリーを確保するように実装しましたが、そのとおりの動作をしたことがツリーから確認できます。

グラフをドラッグすると分析の範囲を制限することもできます。
1回目のボタン押下では113MB確保されたことが分かります。

終わりに

プロファイラーはアプリの動作を可視化するものですので、自動的にバグを見つけれくれたりするわけではありません。 しかし分析を行うことで、

  • 「この処理が何度も実行されるのはおかしいぞ?」
  • 「不要になったはずのオブジェクトがガベージコレクション後も存在している!」

といった気付きを得ることができるかもしれません。
高機能な有償ツールを導入するのも一案ですが、Visual Studioでもこれだけできます。
リファクタリングのお供にもいかがでしょうか。

ではまた!!



We are hiring!!

ROBOT PAYMENTでは一緒に働く仲間を募集しています!!!

speakerdeck.com
www.robotpayment.co.jp
🎉twitter採用担当アカウント開設!🎉どんどん情報発信していきます!!