如何保留最后 n 个对象的列表?
我想对特定方法进行一些性能测量,但我想平均完成所需的时间。 (这是一个 C# Winforms 应用程序,但这个问题很可能适用于其他框架。)
我有一个秒表,我在方法开始时重置它并在方法结束时停止。 我想将最后 10 个值存储在列表或数组中。添加的每个新值都应将最旧的值移出列表。
我会定期调用另一个方法,对所有存储的值进行平均。
我认为这个构造是一个循环缓冲区是否正确?
如何创建这样一个具有最佳性能的缓冲区?现在我有以下内容:
List<long> PerfTimes = new List<long>(10);
// ...
private void DoStuff()
{
MyStopWatch.Restart();
// ...
MyStopWatch.Stop();
PerfTimes.Add(MyStopWatch.ElapsedMilliseconds);
if (PerfTimes.Count > 10) PerfTimes.RemoveAt(0);
}
这似乎在某种程度上效率低下,但也许事实并非如此。
建议?
I want to do some performance measuring of a particular method, but I'd like to average the time it takes to complete. (This is a C# Winforms application, but this question could well apply to other frameworks.)
I have a Stopwatch which I reset at the start of the method and stop at the end. I'd like to store the last 10 values in a list or array. Each new value added should push the oldest value off the list.
Periodically I will call another method which will average all stored values.
Am I correct in thinking that this construct is a circular buffer?
How can I create such a buffer with optimal performance? Right now I have the following:
List<long> PerfTimes = new List<long>(10);
// ...
private void DoStuff()
{
MyStopWatch.Restart();
// ...
MyStopWatch.Stop();
PerfTimes.Add(MyStopWatch.ElapsedMilliseconds);
if (PerfTimes.Count > 10) PerfTimes.RemoveAt(0);
}
This seems inefficient somehow, but perhaps it's not.
Suggestions?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
您可以创建一个自定义集合:
您当前的解决方案可以工作,但效率低下,因为删除
List
的第一项的成本很高。You could create a custom collection:
Your current solution works, but it's inefficient, because removing the first item of a
List<T>
is expensive.这是一个简单的圆形结构。
这不需要其他解决方案所具有的数组复制或链表节点的垃圾收集。
Here is a simple circular structure.
This requires none of the array copying or garbage collection of linked list nodes that the other solutions have.
为了获得最佳性能,您可能可以只使用长整型数组而不是列表。
我们曾经有过类似的要求来实现下载时间估计器,并且我们使用循环缓冲区来存储最后 N 秒中每一秒的速度。
我们对整个时间的下载速度不感兴趣,只是根据最近的活动预计大约需要多长时间,但不是最近的活动,以至于数字会到处乱跳。 (例如,如果我们只使用最后一秒来计算它)。
我们对整个时间范围不感兴趣的原因是下载速度可能在半小时内达到 1M/s,然后在接下来的 10 分钟内切换到 10M/s。尽管您现在下载速度相当快,但前半小时将严重拖慢平均速度。
我们创建了一个循环缓冲区,每个单元格保存 1 秒内下载的数量。循环缓冲区大小为 300,允许 5 分钟的历史数据,并且每个单元格都初始化为零。就您而言,您只需要十个单元。
我们还维护了总数(缓冲区中所有条目的总和,因此最初也为零)和计数(显然最初为零)。
每一秒,我们都会计算出自上一秒以来下载了多少数据,然后:
基本上,用伪代码:
这应该很容易适应您自己的要求。总和是“缓存”信息的一个很好的功能,它可以使您的代码更快。我的意思是:如果您需要计算总和或平均值,则只能在数据发生变化时并使用最少的必要计算来计算。
另一种方法是在请求时将所有十个数字相加的函数,当将另一个值加载到缓冲区中时,该函数会比单个减法/加法慢。
For optimal performance, you can probably just use an array of longs rather than a list.
We had a similar requirement at one point to implement a download time estimator, and we used a circular buffer to store the speed over each of the last
N
seconds.We weren't interested in how fast the download was over the entire time, just roughly how long it was expected to take based on recent activity but not so recent that the figures would be jumping all over the place (such as if we just used the last second to calculate it).
The reason we weren't interested in the entire time frame was that a download could so 1M/s for half an hour then switch up to 10M/s for the next ten minutes. That first half hour will drag down the average speed quite severely, despite the fact that you're now downloading quite fast.
We created a circular buffer with each cell holding the amount downloaded in a 1-second period. The circular buffer size was 300, allowing for 5 minutes of historical data, and every cell was initialised to zero. In your case, you would only need ten cells.
We also maintained a total (the sum of all entries in the buffer, so also initially zero) and the count (initially zero, obviously).
Every second, we would figure out how much data had been downloaded since the last second and then:
Basically, in pseudo-code:
That should be easily adaptable to your own requirements. The sum is a nice feature to "cache" information which may make your code even faster. By that I mean: if you need to work out the sum or average, you can work it out only when the data changes, and using the minimal necessary calculations.
The alternative would be a function which added up all ten numbers when requested, something that would be slower than the single subtract/add when loading another value into the buffer.
您可能想考虑使用队列数据结构。您可以使用简单的线性列表,但效率很低。可以使用圆形数组,但必须不断调整它的大小。因此,我建议您选择队列。
You may want to look at using the Queue data structure instead. You could use a simple linear List, but it is wholly inefficient. A circular array could be used but then you must resize it constantly. Therefore, I suggest you go with the Queue.
我需要将最后 5 个分数保留在一个数组中,我想出了这个简单的解决方案。
希望它会对某人有所帮助。
I needed to keep 5 last scores in a array and I came up with this simple solution.
Hope it will help some one.
对我来说似乎没问题。改用 LinkedList 怎么样?使用列表时,如果删除第一个项目,则所有其他项目都必须退回到一个项目。使用 LinkedList,您可以以很少的成本在列表中的任何位置添加或删除项目。然而,我不知道这会产生多大的差异,因为我们只讨论十个项目。
链表的缺点是您无法有效地访问列表的随机元素,因为链表本质上必须沿着列表“行走”,传递每个项目,直到到达您需要的项目。但对于顺序访问,链表就可以了。
Seems okay to me. What about using a LinkedList instead? When using a List, if you remove the first item, all of the other items have to be bumped back one item. With a LinkedList, you can add or remove items anywhere in the list at very little cost. However, I don't know how much difference this would make, since we're only talking about ten items.
The trade-off of a linked list is that you can't efficiently access random elements of the list, because the linked list must essentially "walk" along the list, passing each item, until it gets to the one you need. But for sequential access, linked lists are fine.
对于java来说,可能是这样
可以这样启动
结果是
Array5
Array6
Array7
Array8
Array9
For java, it could be that way
It could be started that way
The result is
Array5
Array6
Array7
Array8
Array9
最新答案多年后,我在寻找相同的解决方案时偶然发现了这个问题。我以上述答案的组合结束,尤其是以下答案之一: cycling by agent-j 和 使用 Thomas Levesque 的队列
我不得不放弃 j-agent:
ct = (ct + 1) % times.Length;
因为我需要检测我们何时返回(通过
hasCycled
)以获得一个表现良好的枚举器。请注意,枚举器返回从最新到最旧值的值。Years after the latest answer I stumbled on this questions while looking for the same solution. I ended with a combination of the above answers especially the one of: cycling by agent-j and of using a queue by Thomas Levesque
I had to forego the very elegant one-liner of j-agent:
ct = (ct + 1) % times.Length;
because I needed to detect when we circled back (through
hasCycled
) to have a well behaving enumerator. Note that the enumerator returns values from most-recent to oldest value.