防止生产者爆发的算法

时间:2013-11-05 19:29:39

标签: c# algorithm

我有以下内容:

  • 一个生成随机整数的生产者(每分钟大约一个)。例如:154609722148751
  • 一个消费这些整数的消费者一个接一个。消耗大约3秒钟。

生产者不时会发疯,只能很快产生一种'那种',然后恢复正常。 例如:6666666666666666666666666666666666666666666666675444696,1秒钟。

我的目标是尽可能降低不同类型的数字。 比如,在上一个示例中 我有:

  • 很多'6'没有消费
  • 一个'7'没有消耗
  • 一个'5'没有消耗
  • 三个'4'没有消耗
  • 一个'9'没有消耗

如果我使用简单的FIFO算法,我会在所有'6'被消耗之前等待很长时间。我宁愿“优先”其他数字,然后消耗'6'。

这样的算法是否已经存在? (C#实施是一个加号)

目前,我正在考虑这个算法:

  • 每个数字都有一个队列(Q0,Q1,Q2 ...,Q9)
  • 为每个队列依次出列一个项目:

    private int _currentQueueId;
    private Queue<T>[] _allQueues;
    
    public T GetNextItemToConsume<T>()
    {
        //We assume that there is at least one item to consume, and no lock needed
        var found = false;
        while(!found)
        {
        var nextQueue = _allQueues[_currentQueueId++ % 10];
        if(nextQueue.Count > 0)
            return nextQueue.DeQueue();
        }
    }
    

你有比这更好的算法吗? (或任何想法)

NB1:我在消费过程中没有领先(也就是说我不能提高消费速度和消费线程的数量......)(实际上无限的消费速度会解决我的问题)< / p>

NB2:确切时间的数字不相关,但我们可以假设消费量比生产快十倍

NB3:我对制作人的'疯狂'行为没有领先,事实上这是正常的(但不是那么频繁)制作行为

2 个答案:

答案 0 :(得分:0)

以上是一个比上述评论更完整的例子:

sealed class Item {
    public Item(int group) {
        ItemGroup = group;
    }

    public int ItemGroup { get; private set; }
}

Item TakeNextItem(IList<Item> items) {
    const int LowerBurstLimit = 1;

    if (items == null)
        throw new ArgumentNullException("items");
    if (items.Count == 0)
        return null;

    var item = items.GroupBy(x => x.ItemGroup)
                    .OrderBy(x => Math.Max(LowerBurstLimit, x.Count()))
                    .First()
                    .First();
    items.Remove(item);

    return item;
}

我的想法是根据频率对项目进行排序,然后从频率最低的项目中选择。它与您的多个队列的想法相同,但它是即时计算的。 如果有多个具有相同频率的组,则将采用最旧的项目。 (假设GroupBy和OrderBy是稳定的。它们在实践中,但我不确定它是否在文档中说明)

如果要按时间顺序处理项目,请增加LowerBurstLimit,但队列中的项目超过LowerBurstLimit的项目除外。

测量我在LinqPad中创建此快速代码的时间 (Eric Lippert:请忽略这一部分: - ))

void Main()
{
    var rand = new Random();
    var items = Enumerable.Range(1, 1000)
                          .Select(x => { // simulate burst
                              int group = rand.Next(100);
                              return new Item(group < 90 ? 1 : (group % 10));
                          })
                          .ToList();

    var first = TakeNextItem(items); // JIT

    var sw = new Stopwatch();
    sw.Start();
    while (TakeNextItem(items) != null) {
    }

    sw.Stop();

    Console.WriteLine("Elapsed: {0} ms", sw.ElapsedMilliseconds);
}

当我使用1000个项目运行此代码时,我的3岁笔记本电脑需要大约80毫秒。即每个“Take”平均80μs。

GroupBy应为O(N * M),OrderBy O(M * LogM)和删除O(N)(N =平均队列长度,M =组数),因此性能应与N线性比例。即a 10000个项目队列每个“Take”需要~800μs(我在测试中得到了700μs,10000个项目)

答案 1 :(得分:0)

我将使用你提到的10个队列,并根据特定队列中存在的元素数量选择从中统计出队的队列。具有最多元素的队列更有可能被选择用于出队。

为了获得更好的性能,您需要跟踪所有队列中元素的总数。对于每个出列操作,在0和总计数-1之间绘制一个随机的int X,这将告诉你从哪个队列出队(循环遍历队列,从X中减去队列中元素的数量,直到你低于零) ,然后选择那个队列。)