1

最近我遇到了一个奇怪的性能问题。我需要将周期中的时间间隔与大量迭代进行比较。我使用 DateTime.TimeOfDay 属性来比较这些间隔。但是,我发现这些比较与 DateTime 比较相比非常慢。因此,我必须创建具有 1 年 1 个月和 1 天的 DateTime 以加快时间间隔比较。我准备了一个小例子来说明我的意思。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DatesBenchmark
{
    class Program
    {
        static void Main(string[] args)
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            DateTime firstDate = DateTime.Now;
            DateTime secondDate = DateTime.Now.AddSeconds(5);
            for (int i = 0; i < 2000000; i++)
            {
                var a = firstDate.TimeOfDay > secondDate.TimeOfDay;
                //var a = firstDate > secondDate;
            }
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
            Console.ReadKey();
        }
    }
}

在我的笔记本电脑上,我得到了 15 毫秒(如果循环中的第一行被注释)与 176 毫秒(如果循环中的第二行被注释)。

我的问题很简短。为什么?

4

2 回答 2

5

您从不使用a,因此在第二种情况下,编译器可以优化整个语句,因为它不会产生副作用并且永远不会使用该变量。在第一种情况下,它不能确定在日期时间调用的属性不会引起副作用(优化分析不是那么深入),因此需要保留该行。

最重要的是,在确定一天中的时间时至少需要进行一些计算(它需要通过一天中的滴答数来修改日期时间中的滴答数),这意味着它会更慢,它只是一个多少的问题。

于 2013-02-01T19:01:57.877 回答
5

调用 foo.TimeOfDay 是这样做的:

public TimeSpan TimeOfDay
{
    get
    {
        return new TimeSpan(this.InternalTicks % 864000000000L);
    }
}

通过访问超过 200 万次迭代TimeOfDay的 2 个实例上的属性,您将创建 400 万个实例。然而,这还不是最大的开支。DateTimeTimespan

进一步挖掘,你有:

internal long InternalTicks
{
    get
    {
        return (long)(this.dateData & 4611686018427387903uL);
    }
}

所以你有 400 万个实例化、余数计算、强制转换和&操作。这些都是便宜的操作(“便宜”当然是一个相对的术语),但是它们加起来的数量。

实际比较是微不足道的:

public static bool operator >(TimeSpan t1, TimeSpan t2)
{
    return t1._ticks > t2._ticks;
}

在调试模式下编译 OPs 代码,我看到:

  1. 空循环:4ms。
  2. var a = firstDate > secondDate;6ms(暗示它没有被优化掉)
  3. var a = firstDate.TimeOfDay;40毫秒
  4. var a = firstDate.TimeOfDay > secondDate.TimeOfDay;80毫秒
  5. TimeSpan a = new TimeSpan( ticks ), b = new TimeSpan( ticks );7ms

在 VS 2012 中针对该程序运行性能分析,81% 的样本来自DateTime.get_TimeOfDay().

运行 x64,发布模式,启用所有优化:

  1. 空循环:3ms。
  2. var a = firstDate > secondDate;6ms
  3. var a = firstDate.TimeOfDay;20ms
  4. var a = firstDate.TimeOfDay > secondDate.TimeOfDay;40毫秒
  5. TimeSpan a = new TimeSpan( ticks ), b = new TimeSpan( ticks );6ms

所以:

  • 微基准可能会产生误导(尽管并非无用)。
  • 在确定存在问题之前启用优化。
  • 性能似乎随着优化而翻倍。
  • 在任何情况下,所讨论的语句似乎都没有被优化掉。
  • 实例化是费用的有形但很小的一部分。
  • 铸造/算术运算占其余费用。
  • 在循环之前将属性值存储在变量中可以大大提高性能。
于 2013-02-01T19:10:02.680 回答