启用Guava Cache对象的统计信息会带来什么性能损失?

9

显然,正确的答案是“进行基准测试并找出”,但出于互联网精神,我希望有人已经为我完成了这项工作。

我真的很喜欢Guava的缓存库用于Web服务。 然而,他们的文档在这一点上相当模糊。

recordStats
public CacheBuilder<K,V> recordStats()
启用缓存操作期间累积CacheStats。 如果没有此选项,则Cache.stats()将对所有统计信息返回零。 请注意,记录统计数据需要执行每个操作的簿记,因此会对缓存操作产生性能影响。

自从:
12.0(以前,自动收集统计信息)

来自CacheBuilder.recordStats()的JavaDocs。

我很好奇是否有人记录、基准测试或估算了性能惩罚的严重程度。我认为它应该相当小,大约每个操作纳秒级别。缓存操作本身已经同步 - 读取不锁定或阻塞,但写入会获取锁 - 因此修改统计数据不需要额外的锁定或并发控制。这应该将其限制在每次缓存访问时进行了少量的增量操作。
另一方面,当调用 Cache.stats() 时可能会有一些惩罚。我计划通过Codahale MetricsRegistry将统计信息公开到持久记录,并传输到Graphite服务器。最终结果是统计信息会被定期检索,因此如果检索时存在任何阻塞行为,那可能就不太好了。

2
是的,这很小,事实上,它曾经是自动的(没有选择退出的方式)。 - Louis Wasserman
如果没有人回答并且你决定自己进行基准测试,希望你能在这里报告你的发现! - yshavit
没错,@LouisWasserman,我在第一次发布时从文档引用中剪切了它。但是现在你提到它,让我想一想:如果他们费心更改统计数据收集,肯定有人想到禁用它可以获得多少性能提升。 - Patrick M
惩罚是读取吞吐量的约10%,它将命中/未命中计数记录到“LongAdder”中。由于它是值的非阻塞总和,因此统计信息的构建成本较低。 - Ben Manes
保持统计数据并不要考虑它!对于http://cache2k.org,我决定不将统计数据设置为可选项,因为我不喜欢让用户在性能和“洞察力”之间做出选择。另一方面,cache2k中包含的统计数据专门设计为低开销。 - cruftex
1个回答

12

让我们来看一下源代码

当我们调用CacheBuilder.recordStats()时会发生什么?

CacheBuilder定义了一个空操作StatsCounter实现NULL_STATS_COUNTER,这是默认使用的。如果您调用.recordStats(),则会替换为SimpleStatsCounter,它有六个LongAddable字段(通常是LongAdder,但如果不能使用LongAdder,则回退到AtomicLong),每个字段跟踪一项统计信息。

那么,当我们构建一个Cache时会发生什么?

对于一个标准的LocalCache(从CacheBuilder.build()CacheBuilder.build(CacheLoader)获得),它在构建期间构造所需的StatsCounter实例Cache的每个Segment同样会获得同一类型的StatsCounter实例其他Cache实现可以选择使用SimpleStatsCounter,如果需要,也可以提供自己的行为(例如,一个空操作实现)。

当我们使用Cache时需要注意什么?

每次调用LocalCache中会影响统计数据的方法时,都会调用相应的StatsCounter.record*()方法,这些方法会在支持的LongAddable上执行原子增量或加法操作。据文档显示,LongAdderAtomicLong快得多,因此像你所说的,这几乎不会有任何 noticeable 的影响。但是,在无操作StatsRecorder的情况下,JIT 可以完全优化掉record*()调用,这可能随着时间的推移会有一些 noticeable 的影响。但是,基于此而决定不跟踪统计数据肯定是过早优化

最后,我们如何获取统计数据?

当您调用 Cache.stats() 方法时,将会把 Cache 和其所有 SegmentsStatsCounter 合并在一起 并返回一个新的 StatsCounter。这意味着阻塞很少;每个字段只需要读取一次,而且没有外部同步或锁定。虽然这意味着在理论上存在竞争条件(一个段可能在聚合过程中被访问),但实际上这是无关紧要的。

总之?

您应该感到舒适,使用CacheBuilder.recordStats()来监视任何您感兴趣的Cache,并尽可能频繁地调用Cache.stats()。内存开销大约是恒定的,速度开销可以忽略不计(比您可能实现的任何类似监视都要快),Cache.stats()的争用开销也是如此。

显然,专门的线程只在循环中调用Cache.stats()会导致一些争用,但这是愚蠢的。任何形式的定期访问都将被忽略。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接