编写高性能的Cache

时间:2011-12-22 14:24:44

标签: java caching optimization memory concurrenthashmap

我写了一个股票市场模拟器,它使用ConcurrentHashMap作为缓存。

缓存可容纳大约75个元素,但它们可以非常快速地更新和检索(每秒约500次)。

这是我做的:

主题1:

连接到外部系统,为我提供给定股票代码的流媒体报价。

线程2(回调线程):

等待外部系统将数据传送给它。一旦获取数据,它就会解析它,创建一个不可变的DataEntry对象,缓存它并向thread3发送一个信号。

主题3(消费者主题): 收到信号后,从缓存中检索DataEntry并使用它。 (不让thread2直接将数据推送到thread3,这是任务的一部分。)

public final class DataEntry{

      private final String field1;
      private final String field2;
      //...
      private final String field25;

      // Corresponding setters and getters

}

public final class Cache{

        private final Map<String, DataEntry> cache;

        public Cache( ){
           this.cache = new ConcurrentHashMap<String, DataEntry> ( 65, 0.75, 32 );
        }

        // Methods to update and retrieve DataEntry from the cache.
}

通过分析器运行后,我注意到我正在创建DataEntry对象的 lot 。因此,伊甸园很快就会填满。

所以,我想通过以下方式调整设计:

a)使DataEntry类可变。

b)使用空DataEntry个对象预先填充缓存。

c)更新到货时,从地图中检索DataEntry对象并填充字段。

这样,DataEntry对象的数量将是常量并且等于元素的数量。

我的问题是:

a)此设计是否存在因使DataEntry变为可变而引入的并发问题。

b)我还能做些什么来优化缓存吗?

感谢。

4 个答案:

答案 0 :(得分:1)

听起来你正在使用ConcurrentHashMap当你真正需要的东西就像一个并发队列 - 比如一个LinkedBlockingQueue

答案 1 :(得分:1)

  • 一个。是的,它确实。可以在没有读者注意的情况下更新可变DataEntry个对象,这将导致状态不一致。
  • 湾是的,您可以:创建一个可变的DataEntryCache,根据请求返回不可变DataEntry。这样,您将在读取时创建新的DataEntry对象,而不是写入。 DataEntryCache可以在内部缓存它构造和返回的不可变DataEntry,并使变异调用上的“缓存”无效。

编辑:我假设您缓存的原因(与在线程2和3之间创建队列相反)是消费者线程可以读取除线程2发送通知之外的其他条目。如果这个假设不正确,您可能根本不需要缓存。

答案 2 :(得分:1)

我不担心ConcurrentHashMap的速度

Map<Integer, Integer> map = new ConcurrentHashMap<>();
long start = System.nanoTime();
int runs = 200*1000*1000;
for (int r = 0; r < runs; r++) {
    map.put(r & 127, r & 127);
    map.get((~r) & 127);
}
long time = System.nanoTime() - start;
System.out.printf("Throughput of %.1f million accesses per second%n",
        2 * runs / 1e6 / (time / 1e9));

打印

Throughput of 72.6 million accesses per second

这远远超出您使用的访问率。

如果你想减少垃圾,你可以使用可变对象和原语。出于这个原因,我会避免使用String(因为你似乎拥有比数据条目多得多的字符串)

答案 3 :(得分:0)

a)在我的代码中,对象创建通常表现为瓶颈,所以我认为你自己重用DataEntry对象的想法也值得实现。但是,正如kdgregory所评论的那样,简单地覆盖当前元素将导致读取不一致的条目。因此,在更新条目时,请写入新的或可用的重用空闲(比如空闲几分钟)条目并将其放入映射中。将新条目放入映射后,将旧条目放在某种空闲列表中。为了完全安全,在读取线程之后,不得允许读取线程访问缓存所传递的DataEntry。 1分钟。如果线程可能阻塞,它们应该复制DataEntry对象,也许重用自己的对象。

b)您当前的设计是模块化的,但涉及许多上下文切换,因为线程反映了模块。我会尝试一种设计,其中单个请求从开始到完成由单个线程提供。请求可以是对新DataEntry对象的完整处理。实现此目标的并发设计模式为Leader/FollowerHalf-Sync/Half-Asynch