队列的性能?期望有大量的 RAM 使用,但我却得到大量的 CPU 使用

Posted

技术标签:

【中文标题】队列的性能?期望有大量的 RAM 使用,但我却得到大量的 CPU 使用【英文标题】:Performance of Queue? Expecting to have heavy RAM usage but instead I get heavy CPU usage 【发布时间】:2016-08-16 22:06:37 【问题描述】:

假设我正在以每秒 50 到 200 次的可变速率接收信号。我想将收到的每个信号的时间戳存储到队列中,以便在超过 1 周前收到信号时将其从队列中删除。

public Queue<long> myQueue = new Queue<long>();

public OnSignalReceived()

    myQueue.Enqueue(DateTime.UtcNow.Ticks);
    PurgeOldSignals();


public void PurgeOldSignals()

    while (myQueue.Count > 0 && myQueue.Peek() < DateTime.UtcNow.AddDays(-7).Ticks)
    
        myQueue.Dequeue();
    

有没有更有效的方法来做到这一点?这是我的实现,我希望利用大量内存(因为假设平均每秒 100 个信号,这意味着队列将在开始清除项目之前保存大约 6000 万个项目(!))作为交换由于 Enqueue()Dequeue() 的 O(1) 时间而具有计算性能。

然而,经过测试,我注意到瓶颈是 CPU 而不是 RAM。事实上,RAM 几乎没有被吃光,但 CPU 使用率从未停止增加。这是运行大约 16 小时后的结果(显然与我的 7 天目标相去甚远)

有什么优化的建议吗?

编辑 1:

事实上,这样做的全部目的只是随时了解我在上周收到了多少信号(精确到实际秒数)。也许有更好的方法来实现这一点?

【问题讨论】:

一目了然,你说每秒会有100个信号(每10毫秒一个)。计算机应该以纳秒级的速度处理内存配置,因此从队列中取出 100 个信号只需要 100 纳秒(甚至不到一微秒)。这意味着处理器正在以极快的速度处理 while 循环,因此内存受到限制,无法疯狂增长。 最简单的做法可能是限制清理。让它每秒只触发一次或类似的效果。 也许ConcurrentQueue&lt;T&gt; 更适合读写器线程? 为什么要在内存中保存 7 天 的数据(无论是几百万)在开始处理之前??跨度> 如果您创建的队列的初始容量是预期的最大容量,会发生什么情况?事实上,你会做很多成长,这会变得越来越昂贵。 【参考方案1】:

对于给定的任务,我将创建 3600*24*7 整数的循环队列。每个整数都表示该秒内的事件数(一周内的每一秒)。它只需要几兆字节。在测量事件上,对应于实际秒数 (=now) 的整数将增加。对数组中的所有项目求和会很方便,只需在更改时更新它即可快速获得它。

public class History

    protected int eventCount = 0;
    protected int[] array;
    protected readonly int _intervalLength_ms;
    long actualTime = 0;
    int actIndex = 0;

    public History(int intervalLength_ms, int numberOfIntervals)
    
        _intervalLength_ms = intervalLength_ms;
        array = new int[numberOfIntervals];
    

    public int EventCount
    
        get
        
            Update();
            return eventCount;
        
    

    public void InsertEvent()
    
        Update();
        array[actIndex]++;
        eventCount++;
    

    protected void Update()
    
        long newTime = DateTime.Now.Ticks / 10000 / _intervalLength_ms;

        while (newTime > actualTime && eventCount > 0)
        
            actualTime++;
            actIndex++;
            if (actIndex >= array.Length)
            
                actIndex = 0;
            
            eventCount -= array[actIndex];
            array[actIndex] = 0;
        

        if (newTime > actualTime)
        
            actualTime = newTime;
            actIndex = (int)(actualTime % array.Length);
        
    

它将使用参数new History(1000, 3600*24*7) 构造。

【讨论】:

感谢您的贡献,会调查的:)【参考方案2】:

我看到两个问题:

    (次要)但 DateTime.Now 比 DateTime.UTCNow 贵得多,因此您可能希望将其排除在循环之外,而不是执行 6000 万次。 (主要)每次收到信号时,您都在循环数百万个项目。如果您只想清除 7 天前的信号,您应该每天只运行一次清除。

【讨论】:

速度并不是 OP 应该使用“UTCNow”的唯一原因。与夏令时更改相关的问题是另一个问题。 re 2:因为他可能随时需要最近 7 天的数据?每天做一次会有不同的行为。 谢谢,这个例子是伪代码,我突然想到了……我确实在使用 DateTime.UtcNow。对于第 2 点,我看不到我每次如何遍历整个队列?从我的角度来看,我只会在超过 1 周前的时间戳之前出队。另外我不能每天只运行一次,因为我需要队列始终是最新的,而不是每天刷新一次 @ibiza 不是整个队列,但仍在检查数百万个,因为以每秒 100 个信号的平均速率,收集一百万个记录需要不到一天的时间。您正在循环查看 7 天 的记录。 @itsme86 ?这是一个队列。先进先出。所以,每次他添加时,他都在看后面的那些......【参考方案3】:

我怀疑这慢的原因是因为这个

如果 Count 已经等于容量,则 Queue 的容量为 通过自动重新分配内部数组来增加,并且 现有元素在新元素之前被复制到新数组中 被添加。

如果 Count 小于内部数组的容量, 此方法是 O(1) 操作。如果内部数组需要 重新分配以容纳新元素,此方法变为 O(n) 运算,其中 n 为 Count。

来自Queue&lt;T&gt;.Enqueue()here 上的 MSDN 文档。 CPU 使用率与 n 成比例增加,因为您正在对 myQueue 执行 O(n) 操作。

然后解决方案是通过调用var myQueue = new Queue&lt;long&gt;(n); 分配您希望该程序立即使用的尽可能多的内存,然后您的代码将进行所需的更改并切换到高内存使用率而不是 CPU 使用率。

【讨论】:

默认容量为32,每次需要增长都会翻倍。 我想可能是……会做更多的测试,谢谢! @hatchet true,虽然从给定的代码来看它是唯一可能的候选者,但我想知道问题是否出在其他地方...... @ibiza 你确定你的 CPU 问题出在队列实现上,而不是你程序的其他地方吗?如果你的程序也死了怎么办?将这一切都保存在内存中并不理想,除非您不关心在程序关闭/崩溃时丢失数据。 如果这是问题所在,您会看到 CPU 使用率达到峰值,而不是持续增加。每次重新分配时,队列的大小都会翻倍。以每秒 100 个信号的速度,在 16 小时内他将产生大约 580 万个信号。 2 的下一个更高的幂是 2^23,即 840 万。因此,如果队列从 32 个项目 (2^5) 开始,那么在 16 小时内最多会有 18 次重新分配。这些将显示为峰值,而不是持续增加 CPU 使用率。

以上是关于队列的性能?期望有大量的 RAM 使用,但我却得到大量的 CPU 使用的主要内容,如果未能解决你的问题,请参考以下文章

虚拟内存介绍

提高 self-JOIN SQL Query 性能

django 性能调优

AsyncRestTemplate 配置队列大小

使用 QlistW 减少 RAM 使用 [关闭]

PHP缓存高使用ram