web-dev-qa-db-ja.com

DateTime.DayOfWeekマイクロ最適化

まず第一に:

  1. 私はこの質問を楽しみのためだけに、そして学びたいと思っています。私はマイクロ最適化をいじり回すのが大好きだと認めなければなりません(ただし、私の開発のいずれにおいても、速度が大幅に向上することはありませんでした)。

  2. DateTime.DayOfWeekメソッドは、私のアプリケーションのボトルネックを表すものではありません。

  3. そして、他の問題になる可能性は非常に低い。この方法がアプリケーションのパフォーマンスに影響を与えると考えている場合は、 最適化するタイミング について考えてから、プロファイリングを実行する必要があります。

DateTimeクラスをILSpyで逆コンパイルすると、DateTime.DayOfWeekが実装されています:

[__DynamicallyInvokable]
        public DayOfWeek DayOfWeek
        {
            [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
            get
            {
                return (DayOfWeek)((this.InternalTicks / 864000000000L + 1L) % 7L);
            }
        }


public long Ticks
{
    [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
    get
    {
        return this.InternalTicks;
    }
}

このメソッドは以下を実行します。

  1. 現在の日に対応するティックは、1日の既存のティック数で除算されます。

  2. 7の除算の余りが0と6の間になるように、前述の結果に1を加算します。

これが曜日を計算する唯一の方法ですか?

実行速度を上げるためにこれを再実装することは可能でしょうか?

20
rpax

チューニングをしてみましょう。

  1. _TimeSpan.TicksPerDay_ _(864000000000)_の素因数分解: Prime factorization of 864000000000

DayOfWeekは次のように表すことができます。

_public DayOfWeek DayOfWeek
{                   
    get
    {
        return (DayOfWeek)(((Ticks>>14) / 52734375 + 1L) % 7L);
    }
}
_

そして、モジュロ7で作業しています。_52734375 % 7_それは1です。したがって、上記のコードは次のようになります。

_public static DayOfWeek dayOfWeekTurbo(this DateTime date)
{
    return (DayOfWeek)(((date.Ticks >> 14) + 1) % 7);
}
_

直感的には機能します。しかし、それを証明しましょうコード付き

_public static void proof()
{
    DateTime date = DateTime.MinValue;
    DateTime max_date = DateTime.MaxValue.AddDays(-1);
    while (date < max_date)
    {
        if (date.DayOfWeek != date.dayOfWeekTurbo())
        {
            Console.WriteLine("{0}\t{1}", date.DayOfWeek, date.dayOfWeekTurbo());
            Console.ReadLine();
        }
        date = date.AddDays(1);
    }
}
_

必要に応じて実行できますが、正常に動作することを保証します。

さて、残っているのは少しのベンチマークだけです。

これは、コードを明確にするための補助的な方法です。

_public static IEnumerable<DateTime> getAllDates()
{
    DateTime d = DateTime.MinValue;
    DateTime max = DateTime.MaxValue.AddDays(-1);
    while (d < max)
    {
        yield return d;
        d = d.AddDays(1);
    }
}
_

説明は必要ないと思います。

_public static void benchDayOfWeek()
{

    DateTime[] dates = getAllDates().ToArray();
    // for preventing the compiler doing things that we don't want to
    DayOfWeek[] foo = new DayOfWeek[dates.Length];
    for (int max_loop = 0; max_loop < 10000; max_loop+=100)
    {


        Stopwatch st1, st2;
        st1 = Stopwatch.StartNew();
        for (int i = 0; i < max_loop; i++)
            for (int j = 0; j < dates.Length; j++)
                foo[j] = dates[j].DayOfWeek;
        st1.Stop();

        st2 = Stopwatch.StartNew();
        for (int i = 0; i < max_loop; i++)
            for (int j = 0; j < dates.Length; j++)
                foo[j] = dates[j].dayOfWeekTurbo();
        st2.Stop();

        Console.WriteLine("{0},{1}", st1.ElapsedTicks, st2.ElapsedTicks);

    }
    Console.ReadLine();
    Console.WriteLine(foo[0]);

}
_

出力:

_96,28
172923452,50884515
352004290,111919170
521851120,168153321
683972846,215554958
846791857,264187194
1042803747,328459950
Monday
_

データを使用してグラフを作成すると、次のようになります。

Chart

_╔══════════════════════╦════════════════════╦═════════════════════╦═════════════╗
║ Number of iterations ║ Standard DayOfWeek ║ Optimized DayOfWeek ║   Speedup   ║
╠══════════════════════╬════════════════════╬═════════════════════╬═════════════╣
║                    0 ║                 96 ║                  28 ║ 3.428571429 ║
║                  100 ║          172923452 ║            50884515 ║ 3.398351188 ║
║                  200 ║          352004290 ║           111919170 ║ 3.145165301 ║
║                  300 ║          521851120 ║           168153321 ║ 3.103424404 ║
║                  400 ║          683972846 ║           215554958 ║ 3.1730787   ║
║                  500 ║          846791857 ║           264187194 ║ 3.205272156 ║
║                  600 ║         1042803747 ║           328459950 ║ 3.174827698 ║
╚══════════════════════╩════════════════════╩═════════════════════╩═════════════╝
_

3倍高速。

注:コードはVisual Studio 2013のリリースモードでコンパイルされ、アプリケーション以外はすべて閉じた状態で実行されました。 (もちろん、VSを含む)。

テストは Toshiba Satellite C660-2JK 、Intel®Core™i3-2350Mプロセッサー、およびWindows®7HomePremium64ビットで実行しました。

編集:

Jon Skeet が気付いたように、このメソッドは日付の境界にない場合に失敗する可能性があります。

Jon Skeetのコメントにより、この回答は、

dayOfWeekTurboは、日付の境界にない場合に失敗する可能性があります。たとえば、new DateTime(2014, 3, 11, 21, 39, 30)について考えてみます。メソッドは、実際には火曜日であるのに金曜日であると見なします。 「モジュロ7で作業している」というのは、基本的に間違った方法です...その余分な除算を削除することで、曜日が変わります日中

編集することにしました。

proof()メソッドを変更すると、

_public static void proof()
{
    DateTime date = DateTime.MinValue;
    DateTime max_date = DateTime.MaxValue.AddSeconds(-1);
    while (date < max_date)
    {
        if (date.DayOfWeek != date.dayOfWeekTurbo2())
        {
            Console.WriteLine("{0}\t{1}", date.DayOfWeek, date.dayOfWeekTurbo2());
            Console.ReadLine();
        }
        date = date.AddSeconds(1);
    }
}
_

失敗します!

ジョンスキートは正しかった。 Jon Skeetのアドバイスに従い、除算を適用しましょう。

_public static DayOfWeek dayOfWeekTurbo2(this DateTime date)
{
    return (DayOfWeek)((((date.Ticks >> 14) / 52734375L )+ 1) % 7);
}
_

また、メソッドgetAllDates()を変更します。

_public static IEnumerable<DateTime> getAllDates()
{
    DateTime d = DateTime.MinValue;
    DateTime max = DateTime.MaxValue.AddHours(-1);
    while (d < max)
    {
        yield return d;
        d = d.AddHours(1);
    }
}
_

そしてbenchDayOfWeek()

_public static void benchDayOfWeek()
{

    DateTime[] dates = getAllDates().ToArray();
    DayOfWeek[] foo = new DayOfWeek[dates.Length];
    for (int max_loop = 0; max_loop < 10000; max_loop ++)
    {


        Stopwatch st1, st2;
        st1 = Stopwatch.StartNew();
        for (int i = 0; i < max_loop; i++)
            for (int j = 0; j < dates.Length; j++)
                foo[j] = dates[j].DayOfWeek;
        st1.Stop();

        st2 = Stopwatch.StartNew();
        for (int i = 0; i < max_loop; i++)
            for (int j = 0; j < dates.Length; j++)
                foo[j] = dates[j].dayOfWeekTurbo2();
        st2.Stop();

        Console.WriteLine("{0},{1}", st1.ElapsedTicks, st2.ElapsedTicks);

    }
    Console.ReadLine();
    Console.WriteLine(foo[0]);

}
_

それでも速くなりますか?答えははい

出力:

_90,26
43772675,17902739
84299562,37339935
119418847,47236771
166955278,72444714
207441663,89852249
223981096,106062643
275440586,125110111
327353547,145689642
363908633,163442675
407152133,181642026
445141584,197571786
495590201,217373350
520907684,236609850
511052601,217571474
610024381,260208969
637676317,275558318
_

Chart

_╔══════════════════════╦════════════════════╦════════════════════════╦═════════════╗
║ Number of iterations ║ Standard DayOfWeek ║ Optimized DayOfWeek(2) ║  Speedup    ║
╠══════════════════════╬════════════════════╬════════════════════════╬═════════════╣
║                    1 ║           43772675 ║               17902739 ║ 2.445026708 ║
║                    2 ║           84299562 ║               37339935 ║ 2.257624766 ║
║                    3 ║          119418847 ║               47236771 ║ 2.528090817 ║
║                    4 ║          166955278 ║               72444714 ║ 2.304588821 ║
║                    5 ║          207441663 ║               89852249 ║ 2.308697504 ║
║                    6 ║          223981096 ║              106062643 ║ 2.111781205 ║
║                    7 ║          275440586 ║              125110111 ║ 2.201585338 ║
║                    8 ║          327353547 ║              145689642 ║ 2.246923958 ║
║                    9 ║          363908633 ║              163442675 ║ 2.226521519 ║
║                   10 ║          407152133 ║              181642026 ║ 2.241508433 ║
║                   11 ║          445141584 ║              197571786 ║ 2.25306251  ║
║                   12 ║          495590201 ║              217373350 ║ 2.279903222 ║
║                   13 ║          520907684 ║              236609850 ║ 2.201546909 ║
║                   14 ║          511052601 ║              217571474 ║ 2.348895246 ║
║                   15 ║          610024381 ║              260208969 ║ 2.344363391 ║
║                   16 ║          637676317 ║              275558318 ║ 2.314124725 ║
╚══════════════════════╩════════════════════╩════════════════════════╩═════════════╝
_

2倍速くなります。

76
rpax