【问题标题】:Performance of Queue? Expecting to have heavy RAM usage but instead I get heavy CPU usage队列的性能?期望有大量的 RAM 使用,但我却得到大量的 CPU 使用
【发布时间】: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 天 的数据(无论是几百万)在开始处理之前??跨度>
  • 如果您创建的队列的初始容量是预期的最大容量,会发生什么情况?事实上,你会做很多成长,这会变得越来越昂贵。

标签: c# performance data-structures


【解决方案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】:

我看到两个问题:

  1. (次要)但 DateTime.Now 比 DateTime.UTCNow 贵得多,因此您可能希望将其排除在循环之外,而不是执行 6000 万次。
  2. (主要)每次收到信号时,您都在循环数百万个项目。如果您只想清除 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 使用率。
猜你喜欢
  • 1970-01-01
  • 2023-04-01
  • 2016-12-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-01
  • 1970-01-01
相关资源
最近更新 更多