基准日期时间
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 方法时,我们都会得到一个日期时间。
有人能解释一下吗?
可以使用进一步的基准来验证这一点,但我强烈怀疑这是因为 AddHours
、AddMinutes
和 AddSeconds
方法都可以在不执行任何复杂的情况下工作 [=64= 】 算术。他们只需要:
- 用方法cal
中指定的单位数乘以单位(小时、分钟、秒)中的刻度数
- 将其添加到原始
DateTime
中的刻度数
- 创建一个新的
DateTime
具有新的刻度数的值
您还使用了 Minute
和 Second
属性,但可以在不计算出 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);
一个明显的缺点:我相信当 DateTimeKind
为 Local
时,它会在夏令时边界附近出现问题,因为实际上有 四个 种类而不是我们通常使用的三种(Utc
、Local
、Unspecified
)。 Local
被有效地分成两部分 "know" 当值在较早的选项和较晚的选项之间不明确时,表示 date/time。如果您使用的不是 Local
类型,应该没问题。
在尝试将 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 方法时,我们都会得到一个日期时间。
有人能解释一下吗?
可以使用进一步的基准来验证这一点,但我强烈怀疑这是因为 AddHours
、AddMinutes
和 AddSeconds
方法都可以在不执行任何复杂的情况下工作 [=64= 】 算术。他们只需要:
- 用方法cal 中指定的单位数乘以单位(小时、分钟、秒)中的刻度数
- 将其添加到原始
DateTime
中的刻度数
- 创建一个新的
DateTime
具有新的刻度数的值
您还使用了 Minute
和 Second
属性,但可以在不计算出 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);
一个明显的缺点:我相信当 DateTimeKind
为 Local
时,它会在夏令时边界附近出现问题,因为实际上有 四个 种类而不是我们通常使用的三种(Utc
、Local
、Unspecified
)。 Local
被有效地分成两部分 "know" 当值在较早的选项和较晚的选项之间不明确时,表示 date/time。如果您使用的不是 Local
类型,应该没问题。