为什么DateTime.Now DateTime.UtcNow这么慢/昂贵 [英] Why are DateTime.Now DateTime.UtcNow so slow/expensive

查看:56
本文介绍了为什么DateTime.Now DateTime.UtcNow这么慢/昂贵的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我意识到这对微优化领域来说太遥远了,但是我很好奇为什么对DateTime.Now和DateTime.UtcNow的调用如此昂贵".我有一个示例程序,该程序运行一些完成某些工作"(添加到计数器)的场景,并尝试执行1秒钟.我有几种方法可以使其在有限的时间内完成工作.这些示例显示DateTime.Now和DateTime.UtcNow明显比Environment.TickCount慢,但与仅让一个单独的线程休眠1秒然后设置一个值以指示工作线程停止相比,这仍然很慢.

所以我的问题是这些

  • 我知道UtcNow更快,因为它没有时区信息,为什么它仍然比TickCount慢得多?
  • 为什么读取布尔值要比读取int更快?
  • 处理这类情况的理想方法是什么?您需要允许某些事物在有限的时间内运行,但又不想浪费比实际工作更多的时间?

请原谅示例的冗长:

  class程序{私人静态挥发布尔完成=假;私有静态volatile int doneInt = 0;私有静态UInt64 doneLong = 0;私有静态ManualResetEvent readyEvent = new ManualResetEvent(false);静态void Main(string [] args){MethodA_PrecalcEndTime();MethodB_CalcEndTimeEachTime();MethodC_PrecalcEndTimeUsingUtcNow();MethodD_EnvironmentTickCount();MethodX_SeperateThreadBool();MethodY_SeperateThreadInt();MethodZ_SeperateThreadLong();Console.WriteLine("Done ...");Console.ReadLine();}私有静态无效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(花费的时间:{0,30}总计数:{1,20}",endDT.Subtract(startDT),cnt);}私有静态无效MethodB_CalcEndTimeEachTime(){int cnt = 0;var startDT = DateTime.Now;while(DateTime.Now< = startDT.AddSeconds(1)){cnt ++;}var endDT = DateTime.Now;Console.WriteLine(花费的时间:{0,30}总计数:{1,20}",endDT.Subtract(startDT),cnt);}私有静态无效MethodC_PrecalcEndTimeUsingUtcNow(){int cnt = 0;var doneTime = DateTime.UtcNow.AddSeconds(1);var startDT = DateTime.Now;而(DateTime.UtcNow< = doneTime){cnt ++;}var endDT = DateTime.Now;Console.WriteLine(花费的时间:{0,30}总计数:{1,20}",endDT.Subtract(startDT),cnt);}私有静态无效MethodD_EnvironmentTickCount(){int cnt = 0;int doneTick = Environment.TickCount + 1000;//<-在计数器计时的地方应该保持理智...var startDT = DateTime.Now;而(Environment.TickCount&=; doneTick){cnt ++;}var endDT = DateTime.Now;Console.WriteLine(花费的时间:{0,30}总计数:{1,20}",endDT.Subtract(startDT),cnt);}私有静态无效MethodX_SeperateThreadBool(){readyEvent.Reset();线程计数器=新线程(CountBool);线程服务员=新线程(WaitBool);counter.Start();waiter.Start();waiter.Join();counter.Join();}私有静态无效CountBool(){int cnt = 0;readyEvent.WaitOne();var startDT = DateTime.Now;同时(!完成){cnt ++;}var endDT = DateTime.Now;Console.WriteLine(花费的时间:{0,30}总计数:{1,20}",endDT.Subtract(startDT),cnt);}私有静态void WaitBool(){readyEvent.Set();Thread.Sleep(TimeSpan.FromSeconds(1));完成=真;}私有静态无效MethodY_SeperateThreadInt(){readyEvent.Reset();线程计数器=新线程(CountInt);线程服务员=新线程(WaitInt);counter.Start();waiter.Start();waiter.Join();counter.Join();}私有静态无效CountInt(){int cnt = 0;readyEvent.WaitOne();var startDT = DateTime.Now;而(doneInt< 1){cnt ++;}var endDT = DateTime.Now;Console.WriteLine(花费的时间:{0,30}总计数:{1,20}",endDT.Subtract(startDT),cnt);}私有静态void WaitInt(){readyEvent.Set();Thread.Sleep(TimeSpan.FromSeconds(1));doneInt = 1;}私有静态无效MethodZ_SeperateThreadLong(){readyEvent.Reset();线程计数器=新线程(CountLong);线程服务员=新线程(WaitLong);counter.Start();waiter.Start();waiter.Join();counter.Join();}私有静态无效CountLong(){int cnt = 0;readyEvent.WaitOne();var startDT = DateTime.Now;一会儿(doneLong< 1){cnt ++;}var endDT = DateTime.Now;Console.WriteLine(花费的时间:{0,30}总计数:{1,20}",endDT.Subtract(startDT),cnt);}私有静态无效的WaitLong(){readyEvent.Set();Thread.Sleep(TimeSpan.FromSeconds(1));doneLong = 1;}} 

解决方案

TickCount 只会读取一个不断增加的计数器.这只是您最简单的操作.

DateTime.UtcNow 需要查询系统时间-不要忘记,虽然 TickCount 完全不了解用户更改时钟或NTP之类的事情, UtcNow 必须考虑到这一点.

现在,您已经表达了对性能的关注-但在给出的示例中,您要做的只是增加一个计数器.我希望在您的 real 代码中,您将做的工作更多.如果您要进行大量的工作,则可能会使UtcNow花费的时间相形见

如果要做需要改进,那么:

  • 您可以使用计时器,而不是显式创建新线程.框架中有各种各样的计时器,在不知道您的确切情况的情况下,我不建议使用哪种计时器最明智-但这比起启动线程感觉更好.
  • 您可以衡量任务的几次迭代,然后猜测实际需要多少次迭代.然后,您可能需要执行一半的迭代,盘点所花的时间,然后相应地调整剩余循环数.当然,如果每次迭代所花费的时间差异很大,这是行不通的.

I realize this is way too far into the micro-optimization area, but I am curious to understand why Calls to DateTime.Now and DateTime.UtcNow are so "expensive". I have a sample program that runs a couple of scenarios of doing some "work" (adding to a counter) and attempts to do this for 1 second. I have several approached of making it do the work for a limited quantity of time. The examples show that DateTime.Now and DateTime.UtcNow are significantly slower than Environment.TickCount, but even that is slow compared to just letting a separate thread sleep for 1 second and then setting a value to indicate the worker thread to stop.

So my questions are these:

  • I know that UtcNow is faster because it does not have time zone information, why is it still so much slower than TickCount?
  • Why is reading a boolean faster than an int?
  • What is the ideal way of dealing with these types of scenarios where you need to allow something to run for a limited amount of time, but you do not want to waste more time checking the time than actually doing the work?

Please pardon the verbosity of the example:

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;
    }

}

解决方案

TickCount just reads a constantly increasing counter. It's just about the simplest thing you can do.

DateTime.UtcNow needs to query the system time - and don't forget that while TickCount is blissfully ignorant of things like the user changing the clock, or NTP, UtcNow has to take this into account.

Now you've expressed a performance concern - but in the examples you've given, all you're doing is incrementing a counter. I would expect that in your real code, you'll be doing rather more work than that. If you're doing a significant amount of work, that's likely to dwarf the time taken by UtcNow. Before doing anything else, you should measure that to find out whether you're actually trying to solve a problem which doesn't exist.

If you do need to improve things, then:

  • You can use a timer rather than creating a new thread explicitly. There are various kinds of timers in the framework, and without knowing your exact situation, I can't advise on which would be most sensible to use - but it feels like a better solution than starting a thread.
  • You can measure a few iterations of your task, then guess how many will actually be required. You might want to then execute half that many iterations, take stock of how long that's taken, then adjust the number of remaining cycles accordingly. Of course, this doesn't work if the time take per iteration can vary wildly.

这篇关于为什么DateTime.Now DateTime.UtcNow这么慢/昂贵的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆