基准测试日期时间

5

当尝试将分钟/秒四舍五入到最近的小时时(在这种情况下忽略分钟/秒部分),我对以下行为感到好奇。

我尝试了两种方法,并使用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方法时,都会得到一个DateTime对象。

请问有人能解释一下吗?


警告:当时间接近午夜(从23:00开始)时,您的“CeilingUsingNewOperator”将会失败。 - Hans Kesting
感谢@HansKesting...这更多是为了演示目的而不是实际实现。 - Anu Viswan
1个回答

7
更多基准测试可以用来验证这一点,但我强烈怀疑原因是AddHoursAddMinutesAddSeconds方法都可以在不进行任何复杂的日期/时间算术运算的情况下工作。它们只需要:
  • 将单位中的滴答数(小时、分钟、秒)乘以方法调用中指定的单位数
  • 将其添加到原始DateTime中的滴答数中
  • 创建一个新的DateTime值,其中包含这个新的滴答数
你还在使用MinuteSecond属性,但可以在不计算月/日/年值的情况下进行计算。
与“单构造函数”调用相比,它需要执行以下操作:
  • 计算年份(昂贵)
  • 计算月份(昂贵)
  • 计算日期(昂贵)
  • 计算小时(廉价)
  • 使用新的年/月/日/时/分/秒计算滴答数(昂贵)
计算年、月和日的代码需要“了解”格里高利历——需要执行更加复杂的计算来提取那些信息。有些内容可以被缓存(例如每年开始时的滴答数),但这样会在内存访问方面失去一些局部性。
我预计最有效的方法是直接使用简单算术来计算滴答数。
long originalTicks = testData.Ticks;
long hoursSinceEpoch = originalTicks / TimeSpan.TicksPerHour;
long newTicks = hoursSinceEpoch * TimeSpan.TicksPerHour;
return new DateTime(newTicks, testData.Kind);

其中一个显著的缺点是: 我认为当 DateTimeKindLocal 时,在夏令时边界周围会有问题,因为实际上有四种而不是我们通常使用的三种(UtcLocalUnspecified)。Local 被有效地分成两部分,以“知道”表示哪个日期/时间当值在早期选项和较晚选项之间存在歧义时。如果您没有使用 Local 类型,则应该没问题。


根据DateTime的官方源代码,您的解释似乎是正确的。 - Oxald
@daisy 非常感谢您解释得这么清楚。 - Anu Viswan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接