C#での処理時間計測いろいろ

.NETでの速度計測はいろいろあるのでご紹介します。

速度計測するコンポーネントの雛形を決めておきます。

ISpeedCount.cs

namespace SpeedCount
{
    public interface ISpeedCount
    {
        void Start();
        double ElapsedMilliSec();
        string Format();
    }
}

ここでは下記、3つの方法でやってみます。

  1. StopWatchクラスを使う場合
  2. DateTimeクラスを使う場合
  3. Win32APIを使う場合

1.StopWatchクラスを使う場合

.NETのSystem.Diagnostics名前空間にあるStopWatchクラスそのものです。StartNewでオブジェクトを作成し、Stopまでの時間を計測することができます。

TimeCountByStopWatch.cs

using System.Diagnostics;

namespace SpeedCount
{
    public class TimeCountByStopWatch : ISpeedCount
    {
        private Stopwatch stopWatch;
        public void Start()
        {
            stopWatch = Stopwatch.StartNew();
        }

        public double ElapsedMilliSec()
        {
            stopWatch.Stop();
            return (double)stopWatch.ElapsedMilliseconds;
        }

        public string Format()
        {
            return "StopWatch..{0}ms";
        }
    }
}

2.DateTimeクラスを使う場合

DateTimeクラスは日付計算に用いますが、DateTime.Nowというプロパティが”日付”というには精度が良いので使えそうです。

TimeCountByDateTime.cs

using System;

namespace SpeedCount
{
    public class TimeCountByDateTime : ISpeedCount
    {
        private DateTime startDate;
        public void Start()
        {
            startDate = DateTime.Now;
        }

        public double ElapsedMilliSec()
        {
            DateTime endDate = DateTime.Now;
            TimeSpan diff = endDate - startDate;
            return diff.Duration().Milliseconds;
        }

        public string Format()
        {
            return "DateTime..{0}ms";
        }
    }
}

3.Win32APIを使う場合

Windows環境で速度計測といったらWindows Embeddedでも、QueryPerformanceCounter, QueryPerformanceFrequencyです。 QueryPerformanceCounterでは高精度タイマーの値を返します。高精度タイマーがサポートされていない場合はFALSEが 戻るようですが、いまだかつてそのような環境を見たことがありません(私は)。

TimeCountByPerformanceCounter.cs

using System.Runtime.InteropServices;

namespace SpeedCount
{
    public class TimeCountByPerformanceCounter : ISpeedCount
    {
        [DllImport("kernel32.dll")]
        static extern bool QueryPerformanceCounter(ref long lpPerformanceCount);
        [DllImport("kernel32.dll")]
        static extern bool QueryPerformanceFrequency(ref long lpFrequency);

        private long startCounter;

        public void Start()
        {
            QueryPerformanceCounter(ref startCounter);
        }

        public double ElapsedMilliSec()
        {
            long stopCounter = 0;
            QueryPerformanceCounter(ref stopCounter);
            long frequency = 0;
            QueryPerformanceFrequency(ref frequency);
            return (double)(stopCounter - startCounter)*1000.0 / frequency;
        }

        public string Format()
        {
            return "QueryPerformanceCounter/Frequency..{0}ms";
        }
    }
}

これらを使ってみるとこんな感じ。

Program.cs

using System;
using System.Collections.Generic;

namespace SpeedCount
{
    class Program
    {
        static void Main(string[] args)
        {
            var speedCounters = new List{
                new TimeCountByStopWatch(),
                new TimeCountByDateTime(),
                new TimeCountByPerformanceCounter()
            };
            foreach (var speedCounter in speedCounters)
            {
                speedCounter.Start();
                for (int i = 0; i < 2000000; i++)
                {
                    ; // do nothing.
                }
                var val = speedCounter.ElapsedMilliSec();
                Console.WriteLine(speedCounter.Format(), val);
            }
            Console.WriteLine("press enter to exit.");
            Console.ReadLine();
        }
    }
}

出力は以下のような感じになりました。

精度

DateTime.Now < StopWatch < QueryPerformanceCounter の順に精度が良くなるようですね。参考程度に言うなら、DateTime.Nowは15ms単位、StopWatchは1ms単位、QueryPerformanceCounterは1usはでてそうです。

これを使ってThread.Sleep(1)の時間を測ってみると、じぇんじぇん1msではないことがよくわかります(笑

コメントを残す