在Guava Cache对象上启用统计信息会有什么性能损失?

时间:2015-08-07 05:17:01

标签: java performance caching guava

显然,正确的答案是“以此为基准并找出答案”,但本着互联网的精神,我希望有人能为我完成这项工作。

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

  

recordStats
  public CacheBuilder<K,V> recordStats()
  在缓存操作期间启用CacheStats的累积。没有这个Cache.stats()将为所有统计信息返回零。请注意,记录统计信息需要对每个操作执行记录,因此会对缓存操作造成性能损失。

     

时间:
  12.0(之前,统计数据收集是自动的)

来自CacheBuilder.recordStats()的JavaDocs。

我很好奇性能惩罚的严重程度是否由任何人记录,基准或停球。我认为它应该是非常小的,每次操作的纳秒数量级。缓存操作本身已经同步 - 读取不会锁定或阻塞,但写入会获取锁定 - 因此不需要额外的锁定或并发来修改统计信息。这应该限制为每个缓存访问的一些额外的增量操作。

Cache.stats()被调用时,它的另一面可能是一些惩罚。我计划通过Codahale MetricsRegistry和Graphite服务器将统计信息暴露给持久记录。实际效果是定期检索统计数据,因此如果检索出现任何阻塞行为,那可能会很糟糕。

1 个答案:

答案 0 :(得分:8)

让我们看看source code

当我们致电CacheBuilder.recordStats()时会发生什么?

CacheBuilder定义了一个无操作StatsCounter实现NULL_STATS_COUNTER,这是默认使用的内容。如果您致电.recordStats(),则会将其替换为SimpleStatsCounter,其中包含六个LongAddable字段(通常为LongAdder但如果可以,则会回退到AtomicLong t对其跟踪的每个统计数据使用LongAdder)。

然后当我们构建Cache

时会发生什么

对于标准LocalCache(这是您从CacheBuilder.build()CacheBuilder.build(CacheLoader)获得的),它构建了所需StatsCounter during construction的实例。 Cache的每个Segment类似地获得相同StatsCounter类型的its own instance。其他Cache实现可以选择使用SimpleStatsCounter,如果他们愿意,或提供他们自己的行为(例如,无操作实现)。

当我们使用Cache

每次调用LocalCache会影响其中一个统计信息会调用相关的StatsCounter.record*()方法,从而导致支持LongAddable上的原子增量或添加。记录的LongAdder明显快于AtomicLong,所以就像你说的那样,这应该是难以察觉的。虽然在无操作StatsRecorder的情况下,JIT可以完全消除record*()次呼叫,但可能随着时间的推移会显而易见。但决定不在此基础上跟踪统计数据肯定是premature optimization

最后我们得到统计数据?

当您致电Cache.stats()时,StatsCounter的{​​{1}}及Cache Segments StatsCounter位于新CacheBuilder.recordStats()内,结果为Cache回到你身边。这意味着将有最小的阻止;每个字段只需要读取一次,并且没有外部同步或锁定。这确实意味着技术上存在竞争条件(可以在聚合的中途访问一个分段),但实际上这是无关紧要的。

总结一下?

您应该对Cache.stats()对您有兴趣监控的Cache.stats()感到满意,并尽可能经常致电Cache.stats()。内存开销大致保持不变,速度开销可以忽略不计(并且比您可能实现的任何类似监控更快),{{1}}的争用开销也是如此。

显然,专用线程除了在循环中调用{{1}}之外什么都不做会引起一些争用,但这很愚蠢。任何形式的定期访问都不会引起注意。