控制结构(6): 最近最少使用(LRU)
// 上一篇:必经之地(using)
// 下一篇:程序计数器(PC)
基于语言提供的基本控制结构,更好地组织和表达程序,需要良好的控制结构。
There are only two hard things in computer science: cache invalidation and naming things.
--Phil Karlton
前情回顾
上一次,我们写了资源打开关闭自动化的控制结构。有熟悉ObjectC语言的朋友补充了一个苹果的做法: autorelease pool
比如说Lock,可以这么声明:
@autoreleasepool{ id lock = [Lock lock]; // autorelease 对象, 创建后 lock, dealloc 时释放 }
它的主要作用是,延迟对象的释放。对一个对象调用 autorelease 方法,那么它就被保存到最里层的 autorelease pool。当 autorelease pool 块结束的时候,对里面所有的对象发送一次 release。Objective-C 里有大量的对象都是通过 autorelease pool 管理。
也可以通过autoreleasepool做性能优化:
for(int i=0; i<100; i++){ create_lots_autoreleased_objects(); }
这段代码可以被优化成:
for(int i=0; i<100; i++){ @autoreleasepool{ create_lots_autoreleased_objects(); } }
这个相当于是加速了内存的释放流程,降低了峰值内存占用。那么,其他语言有这种便利么?
典型代码
function send(pkg, onComplete){ let connection = new Connection(name); connection.open(function(err){ connection.send(pkg, function(err){ connection.close(); onComplete(); }); }) }
结构分析
又是open,close。当然可以使用前一节讲到的方法。但这不是本节的主题,前一节实际上做了一个潜在假设,就是存在一个连接池子,下面的open和close里面获取资源和释放资源都是从资源池里取出和放回的:
open(onComplete) { let self = this; /**get from pool*/ self.m_database.getPOOL().getConnection(function(err, conn) { if (err) { return onComplete(err); } self.m_conn = conn; onComplete(0); }); } close() { let self = this; /**release to pool*/ self.m_conn.release(); }
如果你的资源不是个数据库,例如一个TCP连接,那么open和close是实实在在的打开和关闭连接,频繁操作就有性能问题。所以我们需要自己定制一个连接池。例如下面的实现:
class ConnectionPool{ constructor(capacity,ttl){ this.m_pool = {}; this.m_capacity = capacity; this.m_ttl = ttl; } init(){ let self = this; base.setTimer(function(){ self.trimeByTimeout(); }, 30*1000); } trimeByTimeout(){ let self = this; /**剔除超时的连接*/ for(let name in self.m_pool){ let item = self.m_pool[name]; let elapse = base.getNow()-item.updateTime; if(elapse>self.m_ttl){ self.remove(item); } } } getConnection(name,onComplete){ let self = this; let item = self.peek(); self.open(item,(err){ onComplete(err, item.conn); }); } peek(name){ let self = this; let cache = self.getCache(name); if(cache){ return cache; } self.trimeByLRU(); let conn = self.create(name); let item = self.insert(name, conn); return item; } getCache(name){ let self = this; /**获取缓存,也可以加入使用频次的统计,根据使用频次来选取*/ let cache = self.m_pool[name]; if(cache){ cache.updateTime = base.getNow(); return cache; }else{ return null; } } trimeByLRU(){ let self = this; /**剔除最不活跃的连接*/ if(self.overflow()){ let minItem = self.findMinItem(); if(minItem!=null){ self.remove(minItem); } } } overflow(){ let self = this; let length = Object.keys(self.m_pool).length; return length > self.m_capacity; } findMinItem(){ let self = this; let min = Infinity; let minItem = null; for(let name in slef.m_pool){ let item = self.m_pool[name]; if(item.updateTime<min){ min = item.upddateTime; minItem = item; } } return minItem; } create(name){ let conn = new Connection(name); return conn; } open(item, onComplete){ let self = this; if(item.refCount===0){ item.conn.open(onComplete); }else{ item.refCount++; onComplete(0); } } release(item){ let self = this; item.refCount--; if(item.refCount===0){ if(item.markRemove){ delete self.m_pool[item.name]; item.conn.close(); } }else{ // ignore } } insert(name, conn){ let self = this; let item = { updateTime: base.getNow(), conn: conn, refCount: 0, markRemove: false, name: name, } /**给conn添加release方法*/ item.conn.release = function(){ self.release(item); } self.m_pool[name] = item; return item; } remove(item){ let self = this; if(item.refCount>0){ item.markRemove = true; }else{ delete self.m_pool[item.name]; item.conn.close(); } } }
有了连接池,就可以像常规一样使用Connection:
function test(){ let pool = new ConnectionPool(32,30*1000); pool.init(); pool.getConnection(function(err, conn)=>{ conn.connect(function(err){ //... conn.release(); }) }); }
语义分析
Least Recently Used, 简称LRU,是一种常用的缓存思路。基本思想是从资源池里剔除最近最少使用
的资源条目。
编程中经常有优化的需求,例如缓存。但是很容易出现所谓的碎片优化
,例如在需要使用资源的地方,每个地方都去手工持有资源的缓存,就不是一种好的方法,如果有多个协作者,还会导致每个人都自己做了一份缓存,或者在源代码的多个地方对同一类资源重复做缓存。
正确的做法应该是:
- 使用资源的地方保持资源使用的最简单形式:打开,使用,关闭(当然你可以使用自动化手法)
- 提供统一并且单点的资源池实现,在一个地方把资源的缓存做好。
【推荐】100%开源!大型工业跨平台软件C++源码提供,建模,组态!
【推荐】2025 HarmonyOS 鸿蒙创新赛正式启动,百万大奖等你挑战
【推荐】博客园的心动:当一群程序员决定开源共建一个真诚相亲平台
【推荐】开源 Linux 服务器运维管理面板 1Panel V2 版本正式发布
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有调度器的协程不是好协程,零基础深入浅出 C++20 协程
· 别做抢活的导演:代码中的抽象层次原则
· 从 Redis 客户端超时到 .NET 线程池挑战
· C23和C++26的#embed嵌入资源指南
· 「EF Core」框架是如何识别实体类的属性和主键的
· 阿里巴巴为什么禁止超过3张表join?
· 博客园众包线下沙龙第1期:云栖开发者基地,共建技术新天地
· 让 AI 帮我部署网站,太方便了!
· 别做抢活的导演:代码中的抽象层次原则
· .NET周刊【7月第1期 2025-07-06】