C# 为什么DateTime.Now DateTime.utc这么慢/贵
我意识到这对于微优化领域来说太远了,但我很好奇为什么调用DateTime.Now和DateTime.UtcNow会如此“昂贵”。我有一个示例程序,它运行两个场景来做一些“工作”(添加到计数器),并尝试做1秒。我有好几种方法让它在有限的时间内完成这项工作。示例显示DateTime.Now和DateTime.UtcNow明显比Environment.TickCount慢,但即使这样,也比让单独的线程睡眠1秒,然后设置一个值来指示工作线程停止要慢 所以我的问题是:C# 为什么DateTime.Now DateTime.utc这么慢/贵,c#,performance,micro-optimization,C#,Performance,Micro Optimization,我意识到这对于微优化领域来说太远了,但我很好奇为什么调用DateTime.Now和DateTime.UtcNow会如此“昂贵”。我有一个示例程序,它运行两个场景来做一些“工作”(添加到计数器),并尝试做1秒。我有好几种方法让它在有限的时间内完成这项工作。示例显示DateTime.Now和DateTime.UtcNow明显比Environment.TickCount慢,但即使这样,也比让单独的线程睡眠1秒,然后设置一个值来指示工作线程停止要慢 所以我的问题是: 我知道UtcNow速度更快,因为它
- 我知道UtcNow速度更快,因为它没有时区信息,为什么它仍然比TickCount慢那么多
- 为什么读取布尔值比读取整数快
- 处理这些场景的理想方法是什么?在这些场景中,您需要允许某些东西在有限的时间内运行,但您不想浪费更多的时间检查时间而不是实际执行工作
class Program
{
private static volatile bool done = false;
private static volatile int doneInt = 0;
private static UInt64 doneLong = 0;
private static ManualResetEvent readyEvent = new ManualResetEvent(false);
static void Main(string[] args)
{
MethodA_PrecalcEndTime();
MethodB_CalcEndTimeEachTime();
MethodC_PrecalcEndTimeUsingUtcNow();
MethodD_EnvironmentTickCount();
MethodX_SeperateThreadBool();
MethodY_SeperateThreadInt();
MethodZ_SeperateThreadLong();
Console.WriteLine("Done...");
Console.ReadLine();
}
private static void MethodA_PrecalcEndTime()
{
int cnt = 0;
var doneTime = DateTime.Now.AddSeconds(1);
var startDT = DateTime.Now;
while (DateTime.Now <= doneTime)
{
cnt++;
}
var endDT = DateTime.Now;
Console.WriteLine("Time Taken: {0,30} Total Counted: {1,20}", endDT.Subtract(startDT), cnt);
}
private static void MethodB_CalcEndTimeEachTime()
{
int cnt = 0;
var startDT = DateTime.Now;
while (DateTime.Now <= startDT.AddSeconds(1))
{
cnt++;
}
var endDT = DateTime.Now;
Console.WriteLine("Time Taken: {0,30} Total Counted: {1,20}", endDT.Subtract(startDT), cnt);
}
private static void MethodC_PrecalcEndTimeUsingUtcNow()
{
int cnt = 0;
var doneTime = DateTime.UtcNow.AddSeconds(1);
var startDT = DateTime.Now;
while (DateTime.UtcNow <= doneTime)
{
cnt++;
}
var endDT = DateTime.Now;
Console.WriteLine("Time Taken: {0,30} Total Counted: {1,20}", endDT.Subtract(startDT), cnt);
}
private static void MethodD_EnvironmentTickCount()
{
int cnt = 0;
int doneTick = Environment.TickCount + 1000; // <-- should be sane near where the counter clocks...
var startDT = DateTime.Now;
while (Environment.TickCount <= doneTick)
{
cnt++;
}
var endDT = DateTime.Now;
Console.WriteLine("Time Taken: {0,30} Total Counted: {1,20}", endDT.Subtract(startDT), cnt);
}
private static void MethodX_SeperateThreadBool()
{
readyEvent.Reset();
Thread counter = new Thread(CountBool);
Thread waiter = new Thread(WaitBool);
counter.Start();
waiter.Start();
waiter.Join();
counter.Join();
}
private static void CountBool()
{
int cnt = 0;
readyEvent.WaitOne();
var startDT = DateTime.Now;
while (!done)
{
cnt++;
}
var endDT = DateTime.Now;
Console.WriteLine("Time Taken: {0,30} Total Counted: {1,20}", endDT.Subtract(startDT), cnt);
}
private static void WaitBool()
{
readyEvent.Set();
Thread.Sleep(TimeSpan.FromSeconds(1));
done = true;
}
private static void MethodY_SeperateThreadInt()
{
readyEvent.Reset();
Thread counter = new Thread(CountInt);
Thread waiter = new Thread(WaitInt);
counter.Start();
waiter.Start();
waiter.Join();
counter.Join();
}
private static void CountInt()
{
int cnt = 0;
readyEvent.WaitOne();
var startDT = DateTime.Now;
while (doneInt<1)
{
cnt++;
}
var endDT = DateTime.Now;
Console.WriteLine("Time Taken: {0,30} Total Counted: {1,20}", endDT.Subtract(startDT), cnt);
}
private static void WaitInt()
{
readyEvent.Set();
Thread.Sleep(TimeSpan.FromSeconds(1));
doneInt = 1;
}
private static void MethodZ_SeperateThreadLong()
{
readyEvent.Reset();
Thread counter = new Thread(CountLong);
Thread waiter = new Thread(WaitLong);
counter.Start();
waiter.Start();
waiter.Join();
counter.Join();
}
private static void CountLong()
{
int cnt = 0;
readyEvent.WaitOne();
var startDT = DateTime.Now;
while (doneLong < 1)
{
cnt++;
}
var endDT = DateTime.Now;
Console.WriteLine("Time Taken: {0,30} Total Counted: {1,20}", endDT.Subtract(startDT), cnt);
}
private static void WaitLong()
{
readyEvent.Set();
Thread.Sleep(TimeSpan.FromSeconds(1));
doneLong = 1;
}
}
类程序
{
私有静态易失性bool done=false;
私有静态volatile int doneInt=0;
专用静态UInt64 doneLong=0;
专用静态手动复位事件readyEvent=新手动复位事件(假);
静态void Main(字符串[]参数)
{
方法a_precalcenttime();
方法b_CalcEndTimeEachTime();
方法c_PrecalcEndTimeUsingUtcNow();
MethodD_环境TickCount();
MethodX_分离ThreadBool();
MethodY_separateThreadInt();
MethodZ_separateThreadLong();
控制台。WriteLine(“完成…”);
Console.ReadLine();
}
私有静态无效方法a_PrecalcEndTime()
{
int-cnt=0;
var doneTime=DateTime.Now.AddSeconds(1);
var startDT=DateTime.Now;
while(DateTime.NowTickCount
只是读取一个不断增加的计数器。这几乎是你能做的最简单的事情
DateTime.UtcNow
需要查询系统时间-不要忘记,虽然TickCount
对用户更改时钟或NTP之类的事情一无所知,但UtcNow
必须考虑到这一点
现在,您已经表达了一个性能问题-但是在您给出的示例中,您所做的只是增加一个计数器。我希望在实际代码中,您所做的工作会比这多。如果您正在做大量工作,这可能会缩短UtcNow
所花费的时间。在做任何其他事情之前,您需要应该衡量一下,看看你是否真的在试图解决一个不存在的问题
如果您确实需要改进,那么:
- 你可以使用计时器,而不是显式地创建一个新线程。框架中有各种各样的计时器,在不知道具体情况的情况下,我无法建议使用哪种计时器最明智——但这感觉比启动线程更好
- 您可以测量任务的几个迭代,然后猜测实际需要多少次。然后您可能希望执行一半的迭代,评估所需的时间,然后相应地调整剩余周期的数量。当然,如果每次迭代所需的时间变化很大,这是行不通的
FWIW这里是NLog用来获取每个日志消息的时间戳的一些代码。在这种情况下,“工作”是当前时间的实际检索(当然,它发生在可能更昂贵的“工作”的上下文中,即消息的日志记录)。NLog通过只获取“真实”来最小化获取当前时间的成本时间(通过DateTime.Now
)如果当前的勾号计数与以前的勾号计数不同。这实际上并不直接适用于您的问题,但这是一种“加速”当前时间检索的有趣方式
internal class CurrentTimeGetter
{
private static int lastTicks = -1;
private static DateTime lastDateTime = DateTime.MinValue;
/// <summary>
/// Gets the current time in an optimized fashion.
/// </summary>
/// <value>Current time.</value>
public static DateTime Now
{
get
{
int tickCount = Environment.TickCount;
if (tickCount == lastTicks)
{
return lastDateTime;
}
DateTime dt = DateTime.Now;
lastTicks = tickCount;
lastDateTime = dt;
return dt;
}
}
}
// It would be used like this:
DateTime timeToLog = CurrentTimeGetter.Now;
内部类CurrentTimeGetter
{
私有静态int lastTicks=-1;
私有静态DateTime lastDateTime=DateTime.MinValue;
///
///以优化方式获取当前时间。
///
///当前时间。
现在是公共静态日期时间
{
得到
{
int tickCount=Environment.tickCount;
如果(滴答声=最后滴答声)
{
返回lastDateTime;
}
DateTime dt=DateTime.Now;
lastTicks=滴答声计数;
lastDateTime=dt;
返回dt;
}
}
}
//它的用法如下:
DateTime timeToLog=CurrentTimeGetter.Now;
在您的问题的上下文中,您可能会“改进”时间循环代码的性能,如下所示:
private static void MethodA_PrecalcEndTime()
{
int cnt = 0;
var doneTime = DateTime.Now.AddSeconds(1);
var startDT = CurrentTimeGetter.Now;
while (CurrentTimeGetter.Now <= doneTime)
{
cnt++;
}
var endDT = DateTime.Now;
Console.WriteLine("Time Taken: {0,30} Total Counted: {1,20}", endDT.Subtract(startDT), cnt); }
}
[__DynamicallyInvokable]
public static DateTime UtcNow
{
[__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries"), SecuritySafeCritical]
get
{
long systemTimeAsFileTime = DateTime.GetSystemTimeAsFileTime();
return new DateTime((ulong)(systemTimeAsFileTime + 504911232000000000L | 4611686018427387904L));
}
}
private static void MethodA_precalcenttime()
{
int-cnt=0;
var doneTime=DateTime.Now.AddSeconds(1);
var startDT=CurrentTimeGetter.Now;
而(CurrentTimeGetter.Now据我所知,DateTime.UtcNow
(不要与DateTime.Now混为一谈,后者要慢得多)是获得时间的最快方式。
事实上,以@wageoghe建议的方式缓存它会显著降低性能(在我的测试中,这是3.5倍)
在ILSpy中,UtcNow如下所示:
private static void MethodA_PrecalcEndTime()
{
int cnt = 0;
var doneTime = DateTime.Now.AddSeconds(1);
var startDT = CurrentTimeGetter.Now;
while (CurrentTimeGetter.Now <= doneTime)
{
cnt++;
}
var endDT = DateTime.Now;
Console.WriteLine("Time Taken: {0,30} Total Counted: {1,20}", endDT.Subtract(startDT), cnt); }
}
[__DynamicallyInvokable]
public static DateTime UtcNow
{
[__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries"), SecuritySafeCritical]
get
{
long systemTimeAsFileTime = DateTime.GetSystemTimeAsFileTime();
return new DateTime((ulong)(systemTimeAsFileTime + 504911232000000000L | 4611686018427387904L));
}
}
我认为,这表明该函数是由编译器内联的,以实现最大速度。可能有更快的方法来获取时间,但到目前为止,我还没有看到一个来更新DateTime.UtcNow
/DateTimeOffset.UtcNow
,请参见此处,其中BenchmarkDotNet
用于奥菲尔
不幸的是,出现了性能倒退