エクスポート (0) 印刷
すべて展開

How To 情報: QueryPerformanceCounter と QueryPerformanceFrequency を使ったマネージ コードの計時方法

Patterns and Practices ホーム

.NET アプリケーションのパフォーマンスとスケーラビリティの向上

J.D. Meier, Srinath Vasireddy, Ashish Babbar, and Alex Mackman
Microsoft Corporation

May 2004
日本語版最終更新日 2005 年 11 月 28 日

要約: この How To 情報では、マネージ ラッパー クラスを作成し、Microsoft(R) Win32(R) 関数の QueryPerformanceCounterQueryPerformanceFrequency をカプセル化する方法を示します。このクラスを使用すれば、マネージ コードの実行時間を計測することができます。また、このクラスを使用し、ボックス化および文字列連結に関連するオーバーヘッドを計測する方法も説明します。

対象製品

  • Microsoft(R) .NET Framework 1.1
目次

概要
QueryPerfCounter ラッパー クラスの作成
ラッパー クラスの使用
QueryPerfCounter クラスの検証
例 A: ボックス化 オーバーヘッド
例 B: 文字列連結
補足資料

概要

Win32 関数の QueryPerformanceCounterQueryPerformanceFrequency を使用すれば、コードのパフォーマンスをナノ秒単位で計測することができます。秒と比較すれば、ナノ秒 (ns または nsec) は 1 秒の 10 億分の 1 (10-9) に相当します。ミリ秒 (ms または msec) は 1 秒の 千分の 1 です。

メモ: 本稿執筆の時点で、.NET Framework 2.0 は、QueryPerformanceCounterQueryPerformanceFrequency の使用を単純化するラッパーを提供しています。

QueryPerfCounter ラッパー クラスの作成

このステップでは、パフォーマンス情の取得に使用される Win32 関数呼び出しをカプセル化するためのラッパー クラスを作成します。

ラッパー クラスを作成するには

  1. Microsoft Visual Studio(R) .NET またはテキスト エディタを使用して新しい C# ファイルを作成し、QueryPerfCounter.cs と名付けます。これに、QueryPerfCounter と名付けた空のクラスを、以下のように加えます。
    public class QueryPerfCounter
    {
    }
    
  2. ネイティブ Win32 関数を呼び出せるように、System.Runtime.InteropServices を参照する using ステートメントを加えます。
    using System.Runtime.InteropServices;
    
  3. QueryPerformanceCounter および QueryPerformanceFrequency という Win32 API を呼び出す宣言を、以下のように作成します。
    [DllImport("KERNEL32")]
    private static extern bool QueryPerformanceCounter(out long
    lpPerformanceCount);
    
    [DllImport("Kernel32.dll")]
    private static extern bool QueryPerformanceFrequency(out long lpFrequency);
    
  4. コンストラクタを追加します。ここで QueryPerformanceFrequency を呼び出し、グローバル変数を渡して、ナノ秒単位の時間計算に使用する値を保持してください。
    private long frequency;
    
    public QueryPerfCounter()
    {
    if (QueryPerformanceFrequency(out frequency) == false)
    {
    // サポートされない頻度
    throw new Win32Exception();
    }
    }
    
  5. QueryPerformanceCounter から現在値を取得する、Start メソッドを作成します。取得値の格納には、グローバル変数を使用してください。
    public void Start()
    {
    QueryPerformanceCounter(out start);
    }
    
  6. QueryPerformanceCounter から現在値を取得する、Stop メソッドを作成します。取得値の格納には、別のグローバル変数を使用してください。
    public void Stop()
    {
    QueryPerformanceCounter(out stop);
    }
    
  7. 反復数を引数として受け、時間値を返す Duration メソッドを作成します。このメソッドを使用し、スタート値とストップ値の間のティック数を計算します。次に、計算結果に頻度係数を掛け、全処理の時間を求めます。さらに、これを反復数で割り、処理値あたりの時間を算出します。
    public double Duration(int iterations)
    {
    return ((((double)(stop - start)*
    (double) multiplier) /
    (double) frequency)/iterations);
    }
    

QueryPerfCounter.cs 内のコードは、以下のようになっているべきです。

QueryPerfCounter.cs

// QueryPerfCounter.cs
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;

public class QueryPerfCounter
{
[DllImport("KERNEL32")]
private static extern bool QueryPerformanceCounter(
out long lpPerformanceCount);

[DllImport("Kernel32.dll")]
private static extern bool QueryPerformanceFrequency(out long lpFrequency);

private long start;
private long stop;
private long frequency;
Decimal multiplier = new Decimal(1.0e9);

public QueryPerfCounter()
{
if (QueryPerformanceFrequency(out frequency) == false)
{
// サポートされない頻度
throw new Win32Exception();
}
}

public void Start()
{
QueryPerformanceCounter(out start);
}

public void Stop()
{
QueryPerformanceCounter(out stop);
}

public double Duration(int iterations)
{
return ((((double)(stop - start)* (double) multiplier) / (double)
frequency)/iterations);
}
}

コードのコンパイルには、以下のコードを使用します。

csc.exe /out:QueryPerfCounter.dll /t:library /r:System.dll QueryPerfCounter.cs

ラッパー クラスの作成

コードで QueryPerfCounter ラッパー クラスを使用するには、QueryPerfCounter.dll を参照し、次に QueryPerfCounter クラスをインスタンス化する必要があります。クライアント コードは、以下のようになっているはずです。

QueryPerfCounter myTimer = new QueryPerfCounter();
// ボックス化せずに計測
myTimer.Start();
for(int i = 0; i < iterations; i++)
{
// 予定の作業を実行
}
myTimer.Stop();
// 反復あたりの時間をナノ秒単位で計算
double result = myTimer.Duration(iterations);

以下では、このラッパーを使用してマネージ コードの実行時間を計測する方法を例示していきます。

QueryPerfCounter クラスの検証

以下の例では、シンプルなコンソール アプリケーションを作成し、QueryPerfCounter クラスを検証します。アプリケーションは、指定時間にわたってスレッドをスリープ状態にし、結果と自身のタイミング結果を比較できるようにします。

以下のサンプル コードでは、スレッドを 1 秒間スリープ状態にし、5 回ループしています。その結果、各反復に 1 秒を要すということで、合計時間は 5 秒となっているはずです。

ValidateQueryPerfCounter.cs

// ValidateQueryPerfCounter.cs
using System;

public class ValidateQueryPerfCounter
{
public static void Main()
{
RunTest();
}

public static void RunTest()
{
int iterations=5;

// テスト実行前にオブジェクトとメソッドを JIT 呼び出し
QueryPerfCounter myTimer = new QueryPerfCounter();
myTimer.Start();
myTimer.Stop();

// 全テスト時間を計測
DateTime dtStartTime = DateTime.Now;

// QueryPerfCounters を使用し、反復あたりの平均時間を出す
myTimer.Start();

for(int i = 0; i < iterations; i++)
{
// 予定のメソッド
System.Threading.Thread.Sleep(1000);
}
myTimer.Stop();

// 反復あたりの時間をナノ秒単位で計算
double result = myTimer.Duration(iterations);

// 反復あたりの平均時間の結果を表示
Console.WriteLine("Iterations: {0}", iterations);
Console.WriteLine("Average time per iteration: ");
Console.WriteLine(result/1000000000 + " seconds");
Console.WriteLine(result/1000000 + " milliseconds");
Console.WriteLine(result + " nanoseconds");

// 全テスト時間の結果を表示
DateTime dtEndTime = DateTime.Now;
Double duration = ((TimeSpan)(dtEndTime-dtStartTime)).TotalMilliseconds;
Console.WriteLine();
Console.WriteLine("Duration of test run: ");
Console.WriteLine(duration/1000 + " seconds");
Console.WriteLine(duration + " milliseconds");
Console.ReadLine();
}
}

上記コードのコンパイルには、以下のコマンドを使用します。

csc.exe /out:ValidateQueryPerfCounter.exe /r:System.dll,QueryPerfCounter.dll
/t:exe ValidateQueryPerfCounter.cs

前に作成した QueryPerfCounter.dll アセンブリを参照していることに注意してください。

結果

ValidateQueryPerfCounter.exe 実行時の出力は、以下のようになっているはずです。

Iterations: 5
Average time per iteration:
0.999648279320416 seconds
999.648279320416 milliseconds
999648279.320416 nanoseconds

Duration of test run:
5.137792 seconds
5137.792 milliseconds

例 A: ボックス化 オーバーヘッド

以下のコンソール アプリケーションの例では、QueryPerfCounter.dll からラッパー クラスの QueryPerfCounter を使用し、整数のボックス化に伴うパフォーマンス コストを計測します。

BoxingTest.cs

// BoxingTest.cs
using System;

public class BoxingTest
{
public static void Main()
{
RunTest();
}

public static void RunTest()
{
int iterations=10000;

// テスト実行前にオブジェクトとメソッドを JIT 呼び出し
QueryPerfCounter myTimer = new QueryPerfCounter();
myTimer.Start();
myTimer.Stop();

// ボックス化 / ボックス化解除に使う変数
object obj = null;
int value1 = 12;
int value2 = 0;

// ボックス化せずに計測
myTimer.Start();

for(int i = 0; i < iterations; i++)
{
// 整数から他の整数への単純な値のコピー
value2 = value1;
}
myTimer.Stop();

// 反復あたりの時間をナノ秒単位で計算
double result = myTimer.Duration(iterations);
Console.WriteLine("int to int (no boxing): " + result + " nanoseconds");

// ボックス化を計測
myTimer.Start();

for(int i = 0; i < iterations; i++)
{
// オブジェクトを整数のコピーへポイント
obj = value1;
}
myTimer.Stop();

// 反復あたりの時間をナノ秒単位で計算
result = myTimer.Duration(iterations);
Console.WriteLine("int to object (boxing): " + result + " nanoseconds");

// ボックス化解除を計測
myTimer.Start();

for(int i = 0; i < iterations; i++)
{
// オブジェクトからの整数値を第 2 整数へコピー
value2 = (int)obj;
}
myTimer.Stop();

// 反復あたりの時間をナノ秒単位で計算
result = myTimer.Duration(iterations);
Console.WriteLine("object to int (unboxing): " + result + " nanoseconds");
Console.ReadLine();
}
}

サンプルのコンパイル

コードのコンパイルには、以下のコマンドを使用します。

csc.exe /out:BoxingTest.exe /r:System.dll,QueryPerfCounter.dll /t:exe
BoxingTest.cs

結果

BoxingTest.exe を実行します。結果は、ボックス化発生時のオーバーヘッドを表しています。

int to int (no boxing): 1.22920650529606 nanoseconds
int to object (boxing): 77.132708207328 nanoseconds
object to int (unboxing): 2.87746068285215 nanoseconds

上記のシナリオでは、ボックス化発生時に追加オブジェクトが作成されます。

例 B: 文字列連結

この例では、QueryPerfCounter クラスを使用し、文字列連結によるパフォーマンスへの影響を計測します。この例により、反復数を増やした場合の影響度の変化も見ることができます。

StringConcatTest.cs

// StringConcatTest.cs
using System;
using System.Text;

public class StringConcatTest
{
public static void Main()
{
RunTest(10);
RunTest(100);
}

public static void RunTest(int iterations)
{
// テスト実行前にオブジェクトとメソッドを JIT 呼び出し
QueryPerfCounter myTimer = new QueryPerfCounter();
myTimer.Start();
myTimer.Stop();

Console.WriteLine("");
Console.WriteLine("Iterations = " + iterations.ToString());
Console.WriteLine("(Time shown is in nanoseconds)");

// StringBuilder パフォーマンスを計測
StringBuilder sb = new StringBuilder("");
myTimer.Start();
for (int i=0; i<iterations; i++)
{
sb.Append(i.ToString());
}

myTimer.Stop();

// 反復数について 1 を渡し、全時間を計算
double result = myTimer.Duration(1);
Console.WriteLine(result + " StringBuilder version");

// 文字列連結を計測
string s = string.Empty;
myTimer.Start();
for (int i=0; i<iterations; i++)
{
s += i.ToString();
}

myTimer.Stop();

//反復数について 1 を渡し、全時間を計算
result = myTimer.Duration(1);
Console.WriteLine(result + " string concatenation version");
Console.ReadLine();
}
}

サンプルのコンパイル

コードのコンパイルには、以下のコマンドを使用します。

csc.exe /out:StringConcat.exe /r:System.dll,QueryPerfCounter.dll /t:exe
StringConcat.cs

結果

連結数が少ないと、StringBuilder 使用のメリットは、はっきりと現れません。しかし、100 連結では、メリットが明確に現れます。以下に、例を示します。

10 反復

Iterations = 10
12292.0650529606 StringBuilder version
20393.6533833211 string concatenation version

100 反復

Iterations = 100
62019.0554944832 StringBuilder version
112304.776165686 string concatenation version

補足資料

詳細は、以下を参照してください。


Patterns and Practices ホーム

表示:
© 2014 Microsoft