[Solr源码分析]LRUCache和FastLRUCache实现分析

Keil 5安装教程(C51与MDK共存) 目录前言MDK的安装C51的安装安装完成后的验证新建C51工程添加C51程序文件编译测试总结 前言 本篇文章将介绍嵌入式开发工具Keil 5的安装方法,并使MDK(Microcontroller Development Kit,开发stm32时会用到)和C51(51单片机C语言软件开发系统)共存。今后使用Keil 5可同时开发51单片机程序和ARM单片机程序,便于学习与从51到32的过渡。 注: 为防止商业纠纷,本篇文章不涉及文中软件的注册破解,请支持正版。 同时为了便于今后更深入的学习,本文不会介绍软件的 阅读详情

[Solr源码分析]LRUCache和FastLRUCache实现分析

Posted in cache, solr on 八月 9th, 2010 by kafka0102

在 [Solr 实践]Solr Cache使用介绍及分析 一文我有对Solr的LRUCache和FastLRUCache做了一些介绍,本文在此基础对其实现做些补充。

1、LRUCache的实现分析

在分析LRUCache前先对LinkedHashMap做些介绍。LinkedHashMap继承于HashMap,它使用了一个双向链表来存储Map中的Entry顺序关系,这种顺序有两种,一种是LRU顺序,一种是插入顺序,这可以由其构造函数public LinkedHashMap(int initialCapacity,float loadFactor, boolean accessOrder)指定。所以,对于get、put、remove等操作,LinkedHashMap除了要做HashMap做的事情,还做些调整Entry顺序链表的工作。
以get操作为例,如果是LRU顺序(accessOrder为true),Entry的recordAccess方法就调整get到的Entry到链表的头部去:

   public V get(Object key) {
        Entry<K,V> e = (Entry<K,V>)getEntry(key);
        if (e == null)
            return null;
        e.recordAccess(this);
        return e.value;
    }

对于put来说,LinkedHashMap重写了addEntry方法:

   void addEntry(int hash, K key, V value, int bucketIndex) {
        createEntry(hash, key, value, bucketIndex);
        // Remove eldest entry if instructed, else grow capacity if appropriate
        Entry<K,V> eldest = header.after;
        if (removeEldestEntry(eldest)) {
            removeEntryForKey(eldest.key);
        } else {
            if (size >= threshold)
                resize(2 * table.length);
        }
    }

addEntry中调用了boolean removeEldestEntry(Map.Entry eldest)方法,默认实现一直返回false,也就是默认的Map是没有容量限制的。LinkedHashMap的子类可以复写该方法,当当前的size大于阈值时返回true,这样LinkedHashMap就可以从Entry顺序链表中删除最旧的Entry。这使得LinkedHashMap具有了Cache的功能,可以存储限量的元素,并具有两种可选的元素淘汰策略(LRU和FIFO),其中的LRU是最常用的。
Solr的LRUCache是基于LinkedHashMap实现的,所以LRUCache的实现真的很简单,这里列出其中核心的代码片断:

    public Object init(final Map args, Object persistence, final CacheRegenerator regenerator) {
	//一堆解析参数参数初始化的代码
	//map map    
    map = new LinkedHashMap(initialSize, 0.75f, true) {
      @Override
      protected boolean removeEldestEntry(final Map.Entry eldest) {
        if (size() > limit) {
          // increment evictions regardless of state.
          // this doesn't need to be synchronized because it will
          // only be called in the context of a higher level synchronized block.
          evictions++;
          stats.evictions.incrementAndGet();
          return true;
        }
        return false;
      }
    };
    if (persistence==null) {
      // must be the first time a cache of this type is being created
      persistence = new CumulativeStats();
    }
    stats = (CumulativeStats)persistence;
    return persistence;
  }
 
    public Object put(final Object key, final Object value) {
    synchronized (map) {
      if (state == State.LIVE) {
        stats.inserts.incrementAndGet();
      }
      // increment local inserts regardless of state???
      // it does make it more consistent with the current size...
      inserts++;
      return map.put(key,value);
    }
  }
 
  public Object get(final Object key) {
    synchronized (map) {
      final Object val = map.get(key);
      if (state == State.LIVE) {
        // only increment lookups and hits if we are live.
        lookups++;
        stats.lookups.incrementAndGet();
        if (val!=null) {
          hits++;
          stats.hits.incrementAndGet();
        }
      }
      return val;
    }
  }

可以看到,LRUCache对读写操作直接加的互斥锁,多线程并发读写时会有锁的竞争问题。通常来说,Cache系统的读要远多于写,不能并发读是有些不够友好。不过,相比于Solr中其它耗时的操作来说,LRUCache的串行化读往往不会成为系统的瓶颈。LRUCache的优点是,直接套用LinkedHashMap,实现简单,缺点是,因为LinkedHashMap的get操作需要操作Entry顺序链表,所以必须对整个操作加锁。

2、FastLRUCache的实现分析

Solr1.4引入FastLRUCache作为另一种可选的实现。FastLRUCache放弃了LinkedHashMap,而是使用现在很多Java Cache实现中使用的ConcurrentHashMap。但ConcurrentHashMap只提供了高性能的并发存取支持,并没有提供对淘汰数据的支持,所以FastLRUCache主要需要做的就是这件事情。FastLRUCache的存取操作都在ConcurrentLRUCache中实现,所以我们直接过渡到ConcurrentLRUCache的实现。
ConcurrentLRUCache的存取操作代码如下:

  public V get(final K key) {
    final CacheEntry<K,V> e = map.get(key);
    if (e == null) {
      if (islive) {
        stats.missCounter.incrementAndGet();
      }
      return null;
    }
    if (islive) {
      e.lastAccessed = stats.accessCounter.incrementAndGet();
    }
    return e.value;
  }
 
  public V remove(final K key) {
    final CacheEntry<K,V> cacheEntry = map.remove(key);
    if (cacheEntry != null) {
      stats.size.decrementAndGet();
      return cacheEntry.value;
    }
    return null;
  }
 
  public Object put(final K key, final V val) {
    if (val == null) {
      return null;
    }
    final CacheEntry e = new CacheEntry(key, val, stats.accessCounter.incrementAndGet());
    final CacheEntry oldCacheEntry = map.put(key, e);
    int currentSize;
    if (oldCacheEntry == null) {
      currentSize = stats.size.incrementAndGet();
    } else {
      currentSize = stats.size.get();
    }
    if (islive) {
      stats.putCounter.incrementAndGet();
    } else {
      stats.nonLivePutCounter.incrementAndGet();
    }
 
    // Check if we need to clear out old entries from the cache.
    // isCleaning variable is checked instead of markAndSweepLock.isLocked()
    // for performance because every put invokation will check until
    // the size is back to an acceptable level.
    // There is a race between the check and the call to markAndSweep, but
    // it's unimportant because markAndSweep actually aquires the lock or returns if it can't.
    // Thread safety note: isCleaning read is piggybacked (comes after) other volatile reads
    // in this method.
    if (currentSize > upperWaterMark && !isCleaning) {
      if (newThreadForCleanup) {
        new Thread() {
          @Override
          public void run() {
            markAndSweep();
          }
        }.start();
      } else if (cleanupThread != null){
        cleanupThread.wakeThread();
      } else {
        markAndSweep();
      }
    }
    return oldCacheEntry == null ? null : oldCacheEntry.value;
  }

所有的操作都是直接调用map(ConcurrentHashMap)的。看下put中的代码,当map容量达到上限并且没有其他线程在清理数据(currentSize > upperWaterMark && !isCleaning),就调用markAndSweep方法清理数据,可以有3种方式做清理工作:1)在该线程同步执行,2)即时启动新线程异步执行,3)提供单独的清理线程,即时唤醒它异步执行。

markAndSweep方法那是相当的冗长,这里就不罗列出来。下面叙述下它的思路。

对于ConcurrentLRUCache中的每一个元素CacheEntry,它有个属性lastAccessed,表示最后访问的数值大小。ConcurrentLRUCache中的stats.accessCounter是全局的自增整数,当put或get Entry时,Entry的lastAccessed会被更新成新自增得到的accessCounter。 ConcurrentLRUCache淘汰数据就是淘汰那些lastAccessed较小的Entry。因为ConcurrentLRUCache没有维护以lastAccessed排序的Entry链表(否则就是LRUCache了),所以淘汰数据时就需要遍历整个Map中的元素来淘汰合适的Entry。这是不是要扯上排序呢?其实不用那么大动干戈。

这里定义几个变量,wantToKeep表示Map中需要保留的Entry个数,wantToRemove表示需要删除的个数(wantToRemove=map.size-wantToKeep),newestEntry是最大的lastAccessed值(初始是stats.accessCounter),这三个变量初始都是已知的,oldestEntry表示最小的lastAccessed,这个是未知的,可以在遍历Entry时通过比较递进到最小。Map中的Entry有3种:(a)是可以立刻判断出可以被淘汰的,也就是lastAccessed<(oldestEntry+wantToRemove)的,(b)是可以立刻判断出可以被保留的,也就是lastAccessed>(newestEntry-1000)的,(c)除上述两者之外的就是不能准确判断是否需要被淘汰的。对于遍历一趟Map中的Entry来说,极好的情况是如果淘汰掉满足(a)的Entry后Map大小降到了wantToKeep,这种情况的典型代表是对Cache只有get和put操作,使得lastAccessed在Map中能保持连续;极坏的情况是,可能满足(a)的Entry不够多甚至没有。但遍历一趟Map至少有一个效果是,会把需要处理的Entry范围缩小到满足(c)的。如此反复迭代,一定使得Map容量调到wantToKeep。而对这个淘汰,也要考虑一个现实情况是,wantToKeep往往是接近于map.size(比如等于0.9*map.size)的,如果remove操作不是很多,那么并不需要很多次遍历就可以完成清理工作。

ConcurrentLRUCache淘汰数据的基本思想如上所述。它的执行过程可以分为3个阶段。第一个阶段就是遍历Map中的每个Entry,如果满足(a)就remove,满足(b)则跳过,满足(c)则放到新map中。一遍下来后,如果map.size还大于wantToKeep,第二个阶段就再重复上述过程(实现上,Solr用了个变量numPasses,似乎想做个开关控制遍历几次,当前就固定成一次)。完了如果map.size还大于wantToKeep,第三阶段再遍历一遍Map,但这次使用PriorityQueue来提取出还需要再淘汰的N个最old的Entry,这样一次下来就收工了。需要补充一点,上面提到的wantToKeep在代码中是acceptableWaterMark和lowerWaterMark,也就是如果遍历后达到acceptableWaterMark就算完成,但操作是按lowerWaterMark的要求来。

这个算法的时间复杂度是2n+kln(k)(k值在实际大多数情况下会很小),相比于直接的堆排,通常会更快些。

3、总结

LRUCache和FastLRUCache两种Cache实现是两种很不同的思路。两者的相同点是,都使用了现成的Map来维护数据。不同点是如何来淘汰数据。LRUCache(也就是LinkedHashMap)格外维护了一个结构,在做存取操作时同时更新该结构,优点在于淘汰操作是O(1)的,缺点是需要对存取操作加互斥锁。FastLRUCache正相反,它没有额外维护新的结构,可以由ConcurrentHashMap支持并发读,但put操作中如果需要淘汰数据,淘汰过程是O(n)的,因为整个过程不加锁,这也只会影响该次put的性能,而FastLRUCache也可选成起独立线程异步执行来降低影响。而另一个Cache实现Ehcache,它在淘汰数据就是同步的,不过它限定了每次淘汰数据的大小(通常都少于5个),所以同步情况下性能不会太受影响。


=============================== 华丽的终止符 ================================
9.23华为OD机试真题 新系统 - 小菊的寻宝之旅 (Java/Py/C/C++/Js/Go) 阅读详情

相关推荐

芯片低功耗设计的两种常用EDA流程

如题。

EricWZY2008的专栏 5559

solr 配置多个entity_Solr学习笔记 - 关于cache

原本以为cache多缓存一些,提高命中率,不是美哉。所以看到之前solrconfig里面的filterCache、queryResultCache、documentCache还有ltr feature cache,就想把他调大。缓存的多了,提高命中,看着应该能提高性能才对。下面开始做实验:调整LRUCache为FastLRUCache、增加documentCache和queryResultCach...

weixin_39855634的博客 192

OK3588-C开发板Forlinx Desktop系统烧录与多媒体功能实战

本文详细介绍了OK3588-C开发板Forlinx Desktop系统的烧录步骤与多媒体功能实战。从硬件准备到软件工具配置,逐步解析RKDevTool烧录技巧,并深入测试摄像头、视频编解码及音频功能,帮助开发者快速掌握RK3588芯片的强大性能与Ubuntu系统的优化应用。

weixin_29190169的博客 419

理解Lucene/Solr的缓存

    缓存对于提高搜索引擎的吞吐量,降低CPU占用率极为重要。Lucene/Solr在这块做了很多的工作。Lucene/Solr中默认提供了5种缓存,同时solr还提供扩展缓存接口,允许开发者自定义缓存。1 缓存的基本原理Solr实现了两种策略的缓存:LRU(Leatest Recently Used)和LFU(Least Frequently Used)。这两种策略也用于操作系统的内存管理(页...

weixin_34261415的博客 339

DDR的Controller、Channel、Chip、Rank、Bank、Row、Column、Sided、Dimm

DDR的Controller、Channel、Chip、Bank、Rank、Row、Column、Sided、Dimm

张飞online博客 2万+

告别手动刷金币:零代码配置实现番茄小说自动化任务

本文详细介绍如何利用零代码配置工具实现番茄小说自动化任务,告别手动刷金币的烦恼。通过冰狐智能辅助工具的可视化配置和元数据复用功能,用户无需编程基础即可快速设置自动签到、观看广告等任务流程,大幅提升效率并避免断签风险。

weixin_42522953的博客 297

Solr Cache使用介绍及分析

本文将介绍Solr查询中涉及到的Cache使用及相关的实现。Solr查询的核心类就是SolrIndexSearcher,每个core通常在 同一时刻只由当前的SolrIndexSearcher供上层的handler使用(当切换SolrIndexSearcher时可能会有两个同时提供服 务),而Solr的各种Cache是依附于SolrIndexSearcher的,SolrIndexSear...

204

麒麟V10(arm)配置本地离线yum源

麒麟V10(arm)配置本地离线yum源

myfm2java的博客 7892

LRUCache详解

Cache的容量有限,因此当Cache的容量用完后,而又有新的内容需要添加进来时, 就需要挑选并舍弃原有的部分内容,从而腾出空间来放新内容。其实,LRU译成最久未使用会更形象, 因为该算法每次替换掉的就是一段时间内最久没有使用过的内容。实现LRU Cache的方法和思路很多,但是要保持高效实现O(1)的put和get,那么使用双向链表和哈希表的搭配是最高效和经典的。LRU是Least Recently Used的缩写,意思是最近最少使用,它是一种Cache替换算法。

qq_56444564的博客 8392

C#上位机性能优化全攻略:彻底解决界面卡顿+多线程死锁+PLC数据延迟,产线实测稳定运行2年

一开始我们用的是网上找的零散优化代码,根本满足不了要求,后来花了2个月的时间,把之前27条产线的代码整理、重构、优化,做了一套这套方案上线后,连续稳定运行2年零3个月:界面响应时间稳定在50ms以内,多线程死锁率持续为0,PLC数据延迟稳定在150ms以内,系统可用率≥99.99%,帮客户一年省下了超过480万的停线损失、罚款损失和人工成本,客户第二年就把剩下的22条产线也全部交给我们做了,还介绍了3家同行。

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 294

FISCO BCOS 三、多群组部署以及新节点加入群组

本章主要以星形组网和并行多组组网拓扑为例,指导您了解如下内容:了解如何使用创建多群组区块链安装包;了解创建的多群组区块链安装包目录组织形式;学习如何启动该区块链节点,并通过日志查看各群组共识状态;学习如何向各群组发送交易,并通过日志查看群组出块状态;了解群组内节点管理,包括节点入网、退网等;了解如何新建群组。

qq_63235624的博客 2921

算法-LRU Cache的原理以及实现

146. LRU 缓存 - 力扣(LeetCode)运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1。void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。

tz的博客 2419

还在用 RaiDrive/Rclone 挂载网盘?教您使用Windows 原生 WebDAV 丝滑直连 AList/Openlist,让你的网盘秒变本地硬盘!

网盘文件分散管理?😩 别再依赖第三方工具了!本文教你如何在 Windows 下,利用原生 WebDAV 功能,将 Openlist 网盘直接映射为本地硬盘!💻 操作简单,告别繁琐,让你的云端文件像本地文件一样丝滑!🚀 轻松实现高效管理,快来体验吧!

XiaoqiangClub的博客 2979

Solr4.8.0源码分析(19)之缓存机制(二)

Solr4.8.0源码分析(19)之缓存机制(二) 前文<Solr4.8.0源码分析(18)之缓存机制(一)>介绍了Solr缓存的生命周期,重点介绍了Solr缓存的warn过程。本节将更深入的来介绍下Solr的四种缓存类型,以及两种SolrCache接口实现类。 1、SolrCache接口实现类 前文已经提到SolrCache有两种接口实现类:solr.sea...

weixin_30700099的博客 183

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

Solr4.8.0源码分析(18)之缓存机制(一)

Solr4.8.0源码分析(18)之缓存机制(一) 前文在介绍commit的时候具体介绍了getSearcher()的实现,并提到了Solr的预热warn。那么本文开始将详细来学习下Solr的缓存机制。 1. 简介 Solr目前支持4中cache类型,每种缓存对应一种查询类型。 filterCache documentCache fieldvalueCache ...

weixin_30321709的博客 123

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

一篇搞懂什么是LRU缓存|一篇搞懂LRU缓存的实现|LRUCache详解和实现

一篇搞懂LRU缓存的原理

系统编程、网络编程 1万+

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

美团技术读后感系列——LruCache原理及实现过程

LruCache介绍 LruCache采用的缓存算法为LRU(Least Recently Used),即最近最少使用算法。这一算法的核心思想是当缓存数据达到预设上限后,会优先淘汰近期最少使用的缓存对象。 LruCache内部维护一个双向链表和一个映射表。链表按照使用顺序存储缓存数据,越早使用的数据越靠近链表尾部,越晚使用的数据越靠近链表头部;映射表通过Key-Value结构,提供高效的查找操作,通过键值可以判断某一数据是否缓存,如果缓存直接获取缓存数据所属的链表节点,进一步获取缓存数据。

星空 1722

LRUCache简单实现

来源于LeetCode. 146. LRU 缓存 题目要求实现一个LRUCache,我们使用get时,需要额外把该节点移动到开头;我们使用put时,如果存在该节点,我们需要把该节点删除,并且插入到开头;不存在则插入到开头,此时需要判断长度是否大于给定的capacity。并且实现这两种操作时,时间复杂度都必须为O(1);get我们使用HashMap便可以达到我们的时间复杂度,但是HashMap不是有序的,但它的子类LinkedHashMap是有序的,所以我们使用LinkedHashMap便可以达到本题的效果

small_engineer的博客 1024

solr中Cache综述

目录 一、概述... 2 二、SolrCache接口实现类... 2 2.1、solr.search.LRUCache. 3 2.2、solr.search.FastLRUCache. 3 三、Cache原理及配置... 4 3.1 filterCache. 4 3.2、documentCache. 5 3.3、queryResultCache. 5 3.4 fi

a925907195的专栏 1638

LruCache实现原理

LruCache 是 Android 标准库提供的 LRU 内存缓存框架,基于 Java LinkedHashMap 实现,当缓存容量超过最大缓存容量限制时,会根据 LRU 策略淘汰最久未访问的缓存数据。

Super 含 的博客 674

Solr4.7缓存技术

solr的3个缓存。 Filter cache:这个是被用来缓存过滤器(就是查询参数fq)的结果和基本的枚举类型。 Document cache:这个是被用来缓存lucene documents的,就是存储field的那个东西。 Query result cache:这个一目了然,把查询结果缓存起来。

都是浮云 6897

【深入理解LRU Cache】:缓存算法的经典之作

​LRU Cache(Least Recently Used的缩写,即最近最少使用,它是一种Cache的替换算法。看Cache替换算法这篇文章)是一种常见的缓存淘汰算法。用于在有限的缓存空间中管理数据对象。LRU Cache 的核心思想是基于时间局部性原理,即最近被访问的数据在未来可能会被再次访问。

StuLearn-LJC的博客 1万+

【设计数据结构】实现一个 LRUCache

题目描述这是 LeetCode 上的 「146. LRU 缓存机制」 ,难度为 「中等」。运用你所掌握的数据结构,设计和实现一个  LRU (最近最少使用) 缓存机制 。实现 LRUCache 类:LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。void put(int key, int value) 如果关键字已经存在,

宫水三叶的刷题日记 1081

solr查询优化(实践了一下效果比较明显)

什么是filtercache? solr应用中为了提高查询速度有可以利用几种cache来优化查询速度,分别是fieldValueCache,queryResultCache,documentCache,filtercache,在日常使用中最为立竿见影,最有效的应属filtercache,何谓filtercache?这个需要从一段solr的查询日志开始说起,下面是我截取的solr运行中打印的一...

kuyuyingzi的专栏 865

【常见缓存算法原理及其C++实现】LRU篇(包含过期时间详解)

一、缓存算法简介 (一)缓存算法应用背景 缓存的应用场景和范围十分广泛,下面给出其十分常见的两种应用背景: 首先,在操作系统内部,由于内存资源十分有限,而每个进程又都希望独享一块很大的内存空间。所以诞生了一种“虚拟内存”机制,它将进程的一部分内容暂留在磁盘中,在需要时再进行数据交换将其放入内存,这个过程就需要用到缓存算法机制进行置换。 其次,对于各类应用项目开发而言,在巨大的数据量面前,Catch 是不可或缺的。因为无论是针对本地端的浏览器缓存,还...

saxon_li的博客 1万+

LruCache实现

LRU(Least Recently Used,最近最少使用)算法是一种常用的缓存淘汰策略,当缓存达到其容量上限时,它会移除那些最久没有被访问的数据项。这种策略基于这样一个假设:如果一个数据项在近期没有被访问过,那么在未来一段时间内也不太可能被访问。

dashalen的博客 630
上一篇: Android Bitmap内存限制
下一篇: prezi是个好东西,途客圈就是用这个展示的
code_farmer
博客等级 码龄22年 9粉丝 23原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值