定时器效率

时间:2010-10-15 18:32:53

标签: .net timer performance theory concept

我计划开发一个包含数万个对象的系统,每个对象最多可以有42个(但更可能是4个或5个左右),它们可能会定期执行单独的操作。我还计划编写代码来停用定时器,直到对象开始使用。空闲时,对象每个只需要1个计时器,但是当激活时,其他计时器将立即启动。起初,对象的数量很少,可能只有几百个,但我预计它会成倍增长,并在几个月内开始达到数万个。

所以,我非常担心我将为定时器和这些对象编写代码的效率。我可以在三个级别编写此应用程序,这将成功执行所需的任务。此外,我计划在四核服务器上运行此系统,因此我希望尽可能使用多线程。

为此,我决定使用System.Timers.Timer类,它为每个过去事件触发一个新线程。

这是我正在考虑的3个级别:

  1. 一个单一的计时器操作整个应用程序,它遍历每个对象,检查是否需要触发任何其他操作,如果是,则运行它们,然后继续下一个。

  2. 多层计时器,其中每个对象都有一个主计时器,用于检查对象可能需要执行的所有功能,运行任何准备好的计时器,然后将下一个计时器间隔设置为下一个所需的操作时间。

  3. 递归层计时器,其中每个对象中的每个操作都有自己的计时器,该计时器将被触发,然后设置为下次可用时运行。

  4. 选项1的问题在于,有这么多的对象和动作,以这种方式经过的一个单一计时器可能运行20秒以上(当它执行几百万行循环代码时),这应该是滴答作响的每1秒钟。如果对象没有保持同步,系统可能无法正常工作。

    选项2的问题在于它比选项3更难写,但不是很多,它也意味着可能在系统上运行10,000+个定时器(每个对象一个),创建和销毁每次经过的线程就像没有人的业务(我不确定这是否是一个问题)。在这种情况下,每个计时器必须每秒至少触发一次,可能运行几百行代码(在极端情况下可能达到一千行)。

    选项3的问题在于可能会引入系统的大量计时器。我说的是平均10,000多个计时器,可能同时运行近100,000个计时器。每个过去的事件可能只需要运行50行或更少的代码行,这使得它们非常短。经过的事件将在一个极端上延迟百分之一秒,在另一个极端上延迟五分钟,平均可能在1秒左右。

    我精通Visual Basic .NET,并计划在其中编写它,但我也可以恢复到高中时代,并尝试用C ++编写这个以提高效率,如果它会产生很大的不同(如果您有任何语言代码效率的来源,请告诉我)。还在玩集群Linux服务器而不是我的四核Windows服务器上运行它的概念,但我不确定我是否可以让我的任何.NET应用程序在这样的Linux集群上运行(会喜欢任何信息)也是如此)。

    本主题的主要问题是:

    我是否使用选项1,2或3,为什么?

    〜考虑评论后编辑〜

    所以第四个选项涉及带有自旋锁的定时轮。这是一个工作班:

    Public Class Job
    Private dFireTime As DateTime
    Private objF As CrossAppDomainDelegate
    Private objParams() As Object
    
    Public Sub New(ByVal Func As CrossAppDomainDelegate, ByVal Params() As Object, ByVal FireTime As DateTime)
        objF = Func
        dFireTime = FireTime
        objParams = Params
    End Sub
    
    Public ReadOnly Property FireTime()
        Get
            Return dFireTime
        End Get
    End Property
    
    Public ReadOnly Property Func() As CrossAppDomainDelegate
        Get
            Return objF
        End Get
    End Property
    
    Public ReadOnly Property Params() As Object()
        Get
            Return objParams
        End Get
    End Property
    End Class
    

    然后是主循环实现:

    Private Tasks As LinkedList(Of Job)
    
    Private Sub RunTasks()
        While True
            Dim CurrentTime as DateTime = Datetime.Now            
    
            If Not Tasks.Count = 0 AndAlso Tasks(0).FireTime > CurrentTime Then
                Dim T As Job = Tasks(0)
                Tasks.RemoveFirst()
                T.Func.Invoke()
            Else
                Dim MillisecondDif As Double
    
                MillisecondDif = Tasks(0).FireTime.Subtract(CurrentTime).Milliseconds
                If MillisecondDif > 30 Then
                    Threading.Thread.Sleep(MillisecondDif)
                End If
            End If
    
        End While
    End Sub
    

    我说得对吗?

    EpicClanWars.com

    〜编辑2~

    将“任务”一词改为“工作”,以便人们可以停止抱怨;)

    〜编辑3~

    添加了跟踪时间和变量的变量。确保在需要时发生spinloops

4 个答案:

答案 0 :(得分:5)

答案 1 :(得分:3)

以上都不是。标准解决方案是保留事件列表,使每个事件指向下一个事件。然后,您可以使用单个计时器,只在下次事件中及时唤醒它。

修改

看起来这称为timer wheel

修改

正如Sentinel所指出的,应该将事件分派给线程池。这些事件的处理程序应尽可能快地完成一些工作,并且不会阻塞。如果它需要进行I / O,它应该触发异步任务并终止。否则,线程池会溢出。

.NET 4.0 Task类在这里可能会有所帮助,特别是对于它的延续方法。

答案 2 :(得分:0)

您的三个选项之间的权衡是在内存和CPU之间。更多的计时器意味着更多的计时器节点(内存),并且在检查需要在运行时提供服务的事件时,将这些计时器聚合到更少的计时器意味着更多的CPU。启动太多定时器(并使它们到期)的CPU开销对于一个体面的定时器实现来说并不是一个很大的问题。

所以,在我看来,如果你有一个好的计时器实现,选择根据需要启动尽可能多的计时器(尽可能精细)。但是,如果每个对象中的任何一个定时器是互斥的,请考虑重新使用定时器节点。

答案 3 :(得分:0)

这让我想起旧航空公司的票务系统,你们有队列。根据他们需要什么样的关注,票务请求被放在不同的队列中。

所以也许你可能需要频繁关注的对象队列,以及需要不经常关注的对象队列。必要时,将它们从一个移动到另一个。

您可以为频繁队列设置计时器,为不频繁的队列设置计时器。对于频繁队列,您可以将其拆分为多个队列,每个队列一个队列。

为了处理频繁的队列, 你不应该有比核心更多的线程。如果你有两个核心,你想要做的就是让它们都发动机。任何比这更多的线程都不会让事情变得更快。实际上,如果处理对象需要磁盘I / O或者为某些其他共享硬件排队,那么它甚至可能无法帮助两个内核运行。