基准日期时间

Benchmark DateTime

在尝试将 Minutes/Seconds 舍入到最近的小时(在本例中忽略 minute/second 部分)时,我对以下行为有点好奇。

我尝试了两种方法,并使用 BenchMarkDotNet 对两种方法进行了基准测试。

private DateTime testData = DateTime.Now;

[Benchmark]
public DateTime CeilingUsingNewOperator() => new DateTime(testData.Year,testData.Month,testData.Day,testData.Hour + 1,0,0);
[Benchmark]
public DateTime CeilingUsingAddOperator() => testData.AddHours(1).AddMinutes(-testData.Minute).AddSeconds(-testData.Second);

结果如下。

+-------------------------+-----------+----------+----------+
|         Method          |   Mean    |  Error   |  StdDev  |
+-------------------------+-----------+----------+----------+
| CeilingUsingNewOperator | 207.99 ns | 3.465 ns | 3.072 ns |
| CeilingUsingAddOperator | 108.74 ns | 1.429 ns | 1.337 ns |
+-------------------------+-----------+----------+----------+

我很好奇为什么 New Operator 比较慢,如果我假设,每次我们在第二种方法中调用 AddX 方法时,我们都会得到一个日期时间。

有人能解释一下吗?

可以使用进一步的基准来验证这一点,但我强烈怀疑这是因为 AddHoursAddMinutesAddSeconds 方法都可以在不执行任何复杂的情况下工作 [=64= 】 算术。他们只需要:

  • 用方法cal
  • 中指定的单位数乘以单位(小时、分钟、秒)中的刻度数
  • 将其添加到原始 DateTime
  • 中的刻度数
  • 创建一个新的 DateTime 具有新的刻度数的值

您还使用了 MinuteSecond 属性,但可以在不计算出 month/day/year 值的情况下计算它们。

将其与需要的 "single constructor" 调用进行比较:

  • 算出年份(贵)
  • 计算月份(贵)
  • 锻炼一天(昂贵)
  • 计算时间(便宜)
  • 取一个新的 year/month/day/hour/minute/second 并计算出刻度数(昂贵)

计算年月日的代码需要 "understand" 公历 - 要提取该信息,需要执行复杂得多的计算。有些可以被缓存(例如每年年初的滴答数)但是你在内存访问方面失去了一些参考位置。

我希望有效的方法是直接计算报价的方法,只需要简单的算术:

long originalTicks = testData.Ticks;
long hoursSinceEpoch = originalTicks / TimeSpan.TicksPerHour;
long newTicks = hoursSinceEpoch * TimeSpan.TicksPerHour;
return new DateTime(newTicks, testData.Kind);

一个明显的缺点:我相信当 DateTimeKindLocal 时,它会在夏令时边界附近出现问题,因为实际上有 四个 种类而不是我们通常使用的三种(UtcLocalUnspecified)。 Local 被有效地分成两部分 "know" 当值在较早的选项和较晚的选项之间不明确时,表示 date/time。如果您使用的不是 Local 类型,应该没问题。