【问题标题】:DateTime.DayOfWeek micro optimizationDateTime.DayOfWeek 微优化
【发布时间】:2014-04-11 01:42:45
【问题描述】:

首先:

  1. 我问这个问题只是为了好玩和渴望学习。我不得不承认我喜欢搞乱微优化(尽管它们从未导致我的任何开发速度有任何显着提高)。

  2. DateTime.DayOfWeek 方法并不代表我的任何应用程序的瓶颈。

  3. 而且极不可能在任何其他方面都存在问题。如果有人认为这种方法会影响他的应用程序的性能, 他应该考虑When to optimize,然后,他应该执行分析。

用ILSpy反编译DateTime类,我们发现DateTime.DayOfWeek是如何实现的:

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. 当天对应的刻度除以一天中现有的刻度数。

  2. 我们将上述结果加 1,以便 7 除法的余数在数字 0 和 6 之间。

这是计算星期几的唯一方法吗?

是否有可能重新实现它以使其运行得更快?

【问题讨论】:

标签: c# performance datetime micro-optimization dayofweek


【解决方案1】:

让我们做一些调整。

  1. TimeSpan.TicksPerDay(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

如果我们用数据做一个图表,它看起来像这样:

╔══════════════════════╦════════════════════╦═════════════════════╦═════════════╗
║ 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)。

我在a toshiba Satellite C660-2JK 中运行了测试, Intel® Core™ i3-2350M 处理器和 Windows® 7 Home Premium 64 位。

编辑:

正如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

╔══════════════════════╦════════════════════╦════════════════════════╦═════════════╗
║ 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 倍。

【讨论】:

  • @AdamSears 我找到了here。它在我的书签栏上:)
  • dayOfWeekTurbo 不在日期边界上时可能会失败。例如,考虑new DateTime(2014, 3, 11, 21, 39, 30) - 您的方法认为是星期五,而实际上是星期二。 “我们正在以 7 为模工作”是错误的方式,基本上......通过删除额外的除法,一周中的日期会在白天发生变化。
  • Skeet 是绝对正确的。原始的long 除法InternalTicks / TimeSpan.TicksPerDay 具有一个重要特征,即它会截断值内的时间部分。您的子表达式 date.Ticks &gt;&gt; 142**14 == 16384 滴答“增量”一次,所以每次经过 1.6384 毫秒,您就会得到一个新的星期几?
猜你喜欢
  • 1970-01-01
  • 2010-12-05
  • 1970-01-01
  • 1970-01-01
  • 2010-12-29
  • 2013-09-17
  • 2017-02-14
  • 2011-02-28
  • 2011-11-08
相关资源
最近更新 更多