none
引数を変えながらメソッドのパフォーマンス分析を行い、自動でレポートを生成する RRS feed

  • 質問

  • Visual Studio 2012 Premium を使用しています。

    現在、ある関数のパフォーマンス分析を行っています。

    追記(9/12):『分析』 - 『パフォーマンス分析の開始』を使用して分析を行っています。

    関数は1つ引数を取り、その引数によってボトルネックが違ってくるため、複数種の値を引数として渡してそれぞれのパフォーマンスログを取得したいと考えています。

    手動で1つずつ引数を変えてテストすれば出来ないことも無いのですが、10種、20種となってくると限界が来てしまい、自動化しようと思い立ちました。

    今考えているのが、渡したい引数の候補を配列として持ち、ループを回して1つずつレポートに出力することです。

    このようなことができるのかどうか、ご教示いただけないでしょうか。

    素人考えですが、以下のようにできたら嬉しいです。

    static void Main()
    {
        myclass m = new myclass();
        object[] args = { 1, 2, 3 };
    
        foreach (var arg in args)
        {
            m.mymethod(arg);    // このメソッドのパフォーマンスレポートが呼ぼれた数だけ作成される
        }
    }

    ご回答のほど、よろしくお願いします。


    • 編集済み Mao Zhi 2013年9月12日 2:42 set proper title
    2013年9月6日 10:07

回答

  • みなさん、

    一応、自動化のようなものが出来たので共有します。バッチファイルを併用して実現しています。

    プログラムの流れとしては、

    1. Main関数へ渡す引数に応じて、検証用のパラメータを設定する。
    2. 検証用のパラメータを用いて、メソッドのパフォーマンス分析を実行する。

    2で特定のメソッドのみ分析するため、DataCollectionのメソッドStopProfileとStartProfileを使用して、サンプリングの範囲を絞っています。

    バッチファイルでは、ループを回してプロファイルツールを呼び出しています。

    参考サイト(プロファイラーツールの事前準備など):

    http://msdn.microsoft.com/ja-jp/library/vstudio/z9z62c29.aspx

    サンプル:

    MyNumberクラスのGetDoubledのみをプロファイリングし、結果を C:\pref\YYYYMMDDHHMMSS\ フォルダに出力します。

    (GetDoiubledメソッドが軽量なため、100,000,000 ループしてプロファイリングしています。)

    Program.cs

    using Microsoft.VisualStudio.Profiler;
    using SampleClassLibrary;
    using System;
    
    namespace ProfilerAPISample
    {
        class Program
        {
            /// <summary>
            /// Setting for Sampling
            /// </summary>
            class SamplingSetting
            {
                public int Arg1 { get; set; }
    
                public SamplingSetting() { }
                public SamplingSetting(int arg1)
                {
                    this.Arg1 = arg1;
                }
    
                public override string ToString()
                {
                    return "Arg1:" + this.Arg1.ToString();
                }
            }
    
            /// <summary>
            /// An Array of Sampling Settings
            /// </summary>
            static SamplingSetting[] SamplingSettings = new SamplingSetting[]
            {
                new SamplingSetting(0),
                new SamplingSetting(1),
                new SamplingSetting(2),
                new SamplingSetting(3)
            };
    
            public static void Main(string[] args)
            {
                // Sampling only in Sampling Scope
                DataCollection.StopProfile(
                    ProfileLevel.Global, DataCollection.CurrentId);
    
                // Validate args
                if (args.Length < 1) return;
    
                // Get Sampling Setting
                SamplingSetting setting = GetSetting(args[0]);
                if (setting == null) return;
    
                // Debug mode - only write out sampling setting
                if (args.Length > 1 && args[1].ToLower() == "-debug")
                {
                    Console.WriteLine(setting.ToString());
                    return;
                }
    
                // Constructor, GetSquared will not be sampled
                MyNumber a = new MyNumber(setting.Arg1);
                int x = a.GetSquared();
                Console.WriteLine(setting.Arg1 + " square is {0}", x);
                
                // Start sampling - sample only GetDoubled
                using (SamplingScope.Enter())
                {
                    // 注意:1億回ループしてパフォーマンスを測定しています。
                    for (int i = 0; i < 10000 * 10000; i++)
                        x = a.GetDoubled();
                }
    
                Console.WriteLine(setting.Arg1 + " doubled is {0}", x);
            }
    
            /// <summary>
            /// サンプリングの設定を取得します。
            /// </summary>
            /// <param name="s"></param>
            /// <returns></returns>
            static SamplingSetting GetSetting(string s)
            {
                int n = -1;
                if (int.TryParse(s, out n) == false) n = -1;
    
                if (n < 0 || n >= SamplingSettings.Length)
                    return null;
    
                return SamplingSettings[n];
            }
        }
    
        /// <summary>
        /// Use this class to start sampling
        /// </summary>
        class SamplingScope : IDisposable
        {
            public static SamplingScope Enter()
            {
                return new SamplingScope();
            }
    
            SamplingScope()
            {
                DataCollection.StartProfile(
                    ProfileLevel.Global, DataCollection.CurrentId);
            }
    
            public void Dispose()
            {
                DataCollection.StopProfile(
                    ProfileLevel.Global, DataCollection.CurrentId);
            }
        } // End of SamplingScope class
        
    
    }

    Get-Performance-Reports.bat

    @echo off
    REM Settings ------------------------------------
    set prefdir=C:\perf\
    set nfrom=0
    set nto=3
    REM If profile 32bit application, change path to \Team Tools\Performance Tools\
    set tooldir=C:\Program Files (x86)\Microsoft Visual Studio 11.0\Team Tools\Performance Tools\x64
    REM ---------------------------------------------
    
    set path=%path%;%tooldir%
    
    set time2=%time: =0%
    set ntime=%date:~-10,4%%date:~-5,2%%date:~-2,2%%time2:~0,2%%time2:~3,2%%time2:~6,2%
    
    mkdir %prefdir%%ntime%\
    
    cmd /c "VSPerfCLREnv /sampleon"
    
    set /a N=%nfrom%
    :LOOP
    
    VSPerfCmd /start:sample /output:%prefdir%%ntime%\%N%.vsp /waitstart /shutdown /launch:ProfilerAPISample.exe /args:%N%
    
    if "%N%"=="%nto%" (goto EXIT)
    set /a N=N+1
    goto LOOP
    :EXIT
    
    cmd /c "VSPerfCLREnv /off"


    • 回答としてマーク Mao Zhi 2013年9月12日 2:37
    2013年9月12日 2:36

すべての返信

  • そこまでできてるならあとは mymethod を実装するだけだと思いますが何が問題なのですか?
    2013年9月6日 10:46
  • パラメーターごとに小計してパフォーマンスを見る必要性がわかりません。全て合計し、ボトルネックになっている個所を見ればそれで済むことでは?
    2013年9月6日 13:35
  • galaco さん、

    上記の例はできれば嬉しい、というもので、実際は出来ていません。

    ターゲットを変更したりして、取得しようとはしましたが、全ての合算値のみがレポートに出力されます。

    佐祐理 さん、

    パラメータごとにボトルネックが異なるので、パラメータごとに分析する必要があると考えています。

    これは、パラメータによって実行パスが大幅に異なり、関数の実行回数やデータサイズが変わってくるためです。

    2013年9月9日 1:55
  • mymethod が複数回呼ばれるのにログ出力は合算したのが出るってことはインスタンスに状態を持ってる?
    パラメタごとにインスタンスを作らなくていいんですか?
    どういう作りなのかよくわかんないのですが、出力を分けたいなら出力先を引数で渡せばいいでしょう。

    static void Main()
    {
        int[] args = { 1, 2, 3 };
    
        int i = 0;
        foreach (int arg in args)
        {
            i++;
            string reportFilePath = string.Format("report{0}", i);
    
            MyClass m = new MyClass();
            m.MyMethod(reportFilePath, arg);
        }
    }
    

    2013年9月9日 2:30
  • 説明不足でした。

    MyMethodはパフォーマンス分析用の関数ではなく、VSの機能である『分析』-『パフォーマンス分析の開始』を使用して分析を行っています。

    MyMethodは分析対象で、このメソッド内のどの部分に時間が取られているかを分析したいと考えています。

    現状、『パフォーマンス分析の開始』を都度実行してレポートをとっていますが、自動化出来ないかどうか、検討しています。

    2013年9月9日 2:47
  • 失礼しました。

    でも思い通りの自動化は難しそうですね。。

    2013年9月10日 2:55
  • みなさん、

    一応、自動化のようなものが出来たので共有します。バッチファイルを併用して実現しています。

    プログラムの流れとしては、

    1. Main関数へ渡す引数に応じて、検証用のパラメータを設定する。
    2. 検証用のパラメータを用いて、メソッドのパフォーマンス分析を実行する。

    2で特定のメソッドのみ分析するため、DataCollectionのメソッドStopProfileとStartProfileを使用して、サンプリングの範囲を絞っています。

    バッチファイルでは、ループを回してプロファイルツールを呼び出しています。

    参考サイト(プロファイラーツールの事前準備など):

    http://msdn.microsoft.com/ja-jp/library/vstudio/z9z62c29.aspx

    サンプル:

    MyNumberクラスのGetDoubledのみをプロファイリングし、結果を C:\pref\YYYYMMDDHHMMSS\ フォルダに出力します。

    (GetDoiubledメソッドが軽量なため、100,000,000 ループしてプロファイリングしています。)

    Program.cs

    using Microsoft.VisualStudio.Profiler;
    using SampleClassLibrary;
    using System;
    
    namespace ProfilerAPISample
    {
        class Program
        {
            /// <summary>
            /// Setting for Sampling
            /// </summary>
            class SamplingSetting
            {
                public int Arg1 { get; set; }
    
                public SamplingSetting() { }
                public SamplingSetting(int arg1)
                {
                    this.Arg1 = arg1;
                }
    
                public override string ToString()
                {
                    return "Arg1:" + this.Arg1.ToString();
                }
            }
    
            /// <summary>
            /// An Array of Sampling Settings
            /// </summary>
            static SamplingSetting[] SamplingSettings = new SamplingSetting[]
            {
                new SamplingSetting(0),
                new SamplingSetting(1),
                new SamplingSetting(2),
                new SamplingSetting(3)
            };
    
            public static void Main(string[] args)
            {
                // Sampling only in Sampling Scope
                DataCollection.StopProfile(
                    ProfileLevel.Global, DataCollection.CurrentId);
    
                // Validate args
                if (args.Length < 1) return;
    
                // Get Sampling Setting
                SamplingSetting setting = GetSetting(args[0]);
                if (setting == null) return;
    
                // Debug mode - only write out sampling setting
                if (args.Length > 1 && args[1].ToLower() == "-debug")
                {
                    Console.WriteLine(setting.ToString());
                    return;
                }
    
                // Constructor, GetSquared will not be sampled
                MyNumber a = new MyNumber(setting.Arg1);
                int x = a.GetSquared();
                Console.WriteLine(setting.Arg1 + " square is {0}", x);
                
                // Start sampling - sample only GetDoubled
                using (SamplingScope.Enter())
                {
                    // 注意:1億回ループしてパフォーマンスを測定しています。
                    for (int i = 0; i < 10000 * 10000; i++)
                        x = a.GetDoubled();
                }
    
                Console.WriteLine(setting.Arg1 + " doubled is {0}", x);
            }
    
            /// <summary>
            /// サンプリングの設定を取得します。
            /// </summary>
            /// <param name="s"></param>
            /// <returns></returns>
            static SamplingSetting GetSetting(string s)
            {
                int n = -1;
                if (int.TryParse(s, out n) == false) n = -1;
    
                if (n < 0 || n >= SamplingSettings.Length)
                    return null;
    
                return SamplingSettings[n];
            }
        }
    
        /// <summary>
        /// Use this class to start sampling
        /// </summary>
        class SamplingScope : IDisposable
        {
            public static SamplingScope Enter()
            {
                return new SamplingScope();
            }
    
            SamplingScope()
            {
                DataCollection.StartProfile(
                    ProfileLevel.Global, DataCollection.CurrentId);
            }
    
            public void Dispose()
            {
                DataCollection.StopProfile(
                    ProfileLevel.Global, DataCollection.CurrentId);
            }
        } // End of SamplingScope class
        
    
    }

    Get-Performance-Reports.bat

    @echo off
    REM Settings ------------------------------------
    set prefdir=C:\perf\
    set nfrom=0
    set nto=3
    REM If profile 32bit application, change path to \Team Tools\Performance Tools\
    set tooldir=C:\Program Files (x86)\Microsoft Visual Studio 11.0\Team Tools\Performance Tools\x64
    REM ---------------------------------------------
    
    set path=%path%;%tooldir%
    
    set time2=%time: =0%
    set ntime=%date:~-10,4%%date:~-5,2%%date:~-2,2%%time2:~0,2%%time2:~3,2%%time2:~6,2%
    
    mkdir %prefdir%%ntime%\
    
    cmd /c "VSPerfCLREnv /sampleon"
    
    set /a N=%nfrom%
    :LOOP
    
    VSPerfCmd /start:sample /output:%prefdir%%ntime%\%N%.vsp /waitstart /shutdown /launch:ProfilerAPISample.exe /args:%N%
    
    if "%N%"=="%nto%" (goto EXIT)
    set /a N=N+1
    goto LOOP
    :EXIT
    
    cmd /c "VSPerfCLREnv /off"


    • 回答としてマーク Mao Zhi 2013年9月12日 2:37
    2013年9月12日 2:36