Is it worth caching the result of .Count (or .Count()) when the operation is O(1)?
如果.count或.count()在实现ICollection的对象上多次使用(因此具有O(1)计算复杂性,除非它被重写),那么在性能方面(而不是内存方面)更好吗?即使存在非常小的差异,在临时变量中缓存值而不是每次访问属性值需要吗?
- 这是一个反问句还是一个哲学问题?
- ICollection对象几乎总是将计数本身缓存在backing字段中,count返回该字段。
- @aresavatar:对于每个实现ICollection并缓存其计数的对象,我可以创建一个实现ICollection且不缓存其计数的对象,并且我可以使检索计数的速度非常缓慢。你关于"几乎总是缓存计数"的说法是错误的。
- 前传:在.NET中,最好调用.count()还是在可用时调用.count?
- @杰森-是的,你可以,这就是为什么我说"几乎总是"。但是这是一个写得很糟糕的类,它的性能非常差,所以您可能不会。
- @阿雷萨瓦塔:不,如果我能为每一个没有邪恶的人制造一个邪恶的人,那么邪恶的人至少和没有邪恶的人一样多。同样,这使得"几乎总是"的说法是错误的。
- @杰森-你完全错了。一个人可以在代码中做很多坏事。这并不意味着有人会这样做,即使有人这样做,他们的代码也会因为工作不好而被丢弃。
- @阿雷萨瓦塔:请指出我说的一个特别的"完全错误"的说法。谢谢!
- @杰森:"如果我能为每一个不邪恶的人制造一个邪恶的人,那么邪恶的人至少和不邪恶的人一样多。"这句话暗示你写坏代码的能力意味着坏集合至少和好集合一样多,这是完全错误的。大多数编码员不会写这样一个邪恶的收集,即使你也不会。至少我希望如此!所以你认为像善的藏品一样多的邪恶藏品的说法是完全错误的。
- @又一次,你不明白。这与写坏代码无关。仅仅因为我给出的示例从来没有人在生产代码中编写或使用过,并不意味着没有任何集合的ICollection不是O(1)。你真的很难理解吗?
- @Jason-一个人不会浪费时间或精力,也不会为所有可能出现的错误代码编写不必要的防御代码。你很难理解吗?
- @杰森,让我们在聊天中继续讨论。
- @阿雷萨瓦塔:我知道,这就是为什么我在回答中说,除非我知道我可能会进入邪恶的收藏,否则我不会担心这个问题,我发现这是一个重要的性能瓶颈。(原版加粗)。我还说:只需编写最简单的有效代码(使用ICollection.Count)。如果并且只有当它是应用程序中的性能瓶颈时,我才会担心对它进行调优。
- 为什么投反对票?
直接回答这个问题,即使是O(1)操作也需要很长时间才能完成;只是,无论收集的数据有多大,它总是需要相同的时间。缓存结果并将其读回将是快速的,总是,但不能保证比任何给定的O(1)操作都快。你首先需要一些时间安排;)
是的,它可能(非常轻微)更快,因为方法调用的开销、相关的错误检查以及(在count()的情况下)iCollection的动态类型检查都有一定的开销。
但是值得吗?这完全取决于您和您的应用程序。如果你处于一个非常紧密的内部循环中,这可能是值得的。然后,在最近的.NET运行时上,在这种情况下,这种琐碎的属性可能会被内联。
和往常一样,让分析器告诉您应用程序中的热点在哪里。
对不起,认为ICollection.Count是O(1)是错误的。这在道义上应该是,但不能保证它是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public EvilCollection <T > : ICollection <T > {
private readonly ICollection <T > collection ;
private readonly Func <int, int> slowGrowingFunction ;
public EvilCollection (
ICollection <T > collection,
Func <int, int> slowGrowingFunction )
{
this.collection = collection ;
this.slowGrowingFunction = slowGrowingFunction ;
}
public int Count {
get {
Thread .Sleep(1000 * this.slowGrowingFunction(this.collection.Count));
return this.collection.Count;
}
}
// other methods wrap methods on collection for ICollection<T>
}
ICollection <T > evilCollection =
new EvilCollection <T >(collection, n => Ackermann (4, n )); |
哦,不,evilCollection.Count是O(Ackermann(4, n))!
这就是说,除非我知道我可能加入了邪恶的集合,否则我不会担心这个问题,而且我发现它是一个重要的性能瓶颈。请记住,代码不太清楚,可能不太正确(缓存结果后,计数更新是什么?)等。
只需编写最简单有效的代码(使用ICollection.Count)。如果并且只有当它是应用程序中的性能瓶颈时,我才会担心对它进行调优。
- 没有人应该写这样的集合。你甚至承认这是邪恶的。每次必须计数的集合都将正确定义为可枚举集合,而不是集合或列表。
- 你错过了要点。假设ICollection.Count是O(1)是错误的。这是错误的,因为我可以举一个不正确的例子。这并不是唯一的例子(太离谱了),甚至可能有现实世界的例子。但是,如果您正在编码接口ICollection,那么您不知道、不知道、不知道会发生什么。你不知道是不是一个EvilCollection,或者是其他什么东西使得ICollection.Count不是O(1)。因此,你不能笼统地假设ICollection.Count是O(1),然后从这个错误的假设开始绞尽脑汁。
- 杰森,是的,你可以假设。所有代码都需要运行和测试。当您的邪恶集合行为不良时(不仅在OP的使用中,而且在许多常见的使用中),它将被抛出或进行防御性的编码,例如通过编写一个适当缓存了count的包装类。
- @阿雷萨瓦塔:你不能这样假设。这是我的方法。我是一个API编写者,或者框架编写者,或者谁知道呢。在我的API中,我提供了这种方法:public void M(ICollection collection),我不知道collection将是什么。一个也没有。在我的方法中,我不能假定M是collection.Count是O(1)。我不能,因为我不知道运行时的collection是什么。你不明白吗?
- 我认为你在这件事上是错的,我认为我们不会同意。我不作进一步评论。
- @阿雷萨瓦塔:对你很好。事实上,你不能指出一个错误的陈述,应该告诉你有什么问题,你的理解。
如果您确定.Count()现在和在该特性中要做什么,那么答案是否定的。如果.Count()是一些可能会改变的实现,那么答案是肯定的。假设您使用某个LinkedList更改了实现,它可能不是O(1)。
不,不需要;使用临时变量没有好处。此外,如果您正在缓存计数,那么如果集合集发生更改,您将面临计数不正确的风险。