OWenT's blog
  • Introduction
  • About Me
  • 2020
    • 近期对libatapp的一些优化调整(增加服务发现和连接管理,支持yaml等)
    • xresloader转表工具链增加了一些新功能(map,oneof支持,输出矩阵,基于模板引擎的加载代码生成等)
    • 在游戏服务器中使用分布式事务
    • libcopp接入C++20 Coroutine和一些过渡期的设计
    • libatbus 的大幅优化
    • nftables初体验
    • 容器配置开发环境小计
  • 2019
    • PALM Tree - 适合多核并发架构的B+树 - 论文阅读小记
    • 跨平台协程库 - libcopp 简介
    • C++20 Coroutine 性能测试 (附带和libcopp/libco/libgo/goroutine/linux ucontext对比)
    • 尝鲜Github Action
    • 一些xresloader(转表工具)的改进
    • protobuf、flatbuffer、msgpack 针对小数据包的简单对比
    • 协程框架(libcopp) 小幅优化
    • Excel转表工具(xresloader) 增加protobuf插件功能和集成 UnrealEngine 支持
    • Anna(支持任意扩展和超高性能的KV数据库系统)阅读笔记
    • C++20 Coroutine
    • libcopp merge boost.context 1.69.0
    • Google去中心化分布式系统论文三件套(Percolator、Spanner、F1)读后感
    • Rust玩具-企业微信机器人通用服务
  • 2018
    • 使用ELK辅助监控开发测试环境服务质量和问题定位
    • Webpack+vue+boostrap+ejs构建Web版GM工具
    • 2018年的新通用伪随机数算法(xoshiro / xoroshiro)的C++(head only)实现
    • Rust的第二次接触-写个小服务器程序
    • 理解和适配AEAD加密套件
    • atsf4g-co的进化:协程框架v2、对象路由系统和一些其他细节优化
    • 协程框架(libcopp)v2优化、自适应栈池和同类库的Benchmark对比
    • 可执行文件压缩
    • 初识Rust
    • 使用restructedtext编写xresloader文档
    • atframework的etcd模块化重构
    • C++的backtrace
  • 2017
    • ECDH椭圆双曲线(比DH快10倍的密钥交换)算法简介和封装
    • protobuf-net的动态Message实现
    • pbc的proto3接入
    • atgateway内置协议流程优化-加密、算法协商和ECDH
    • 整理一波软件源镜像同步工具+DevOps工具
    • Blog切换到Hugo
    • libcopp v2的第一波优化完成
    • libcopp(v2) vs goroutine性能测试
    • libcopp的线程安全、栈池和merge boost.context 1.64.0
    • GCC 7和LLVM+Clang+libc++abi 4.0的构建脚本
    • libatbus的几个藏得很深的bug
    • 用cmake交叉编译到iOS和Android
    • 开源项目得一些小维护
    • atapp的c binding和c#适配
    • 对象路由系统设计
    • 2016年总结
    • 近期的一个协程流程BUG
  • 2016
    • 重写了llvm+clang+libc++和libc++abi的构建脚本
    • atsf4g完整游戏工程示例
    • atframework基本框架已经完成
    • 游戏服务器的不停服更新
    • 对atbus的小数据包的优化
    • Android和IOS的TLS问题
    • pbc的一个陈年老BUG
    • boost.context-1.61版本的设计模型变化
    • 接入letsencrypt+全面启用HTTP/2
    • 理解Raft算法
    • libatbus基本功能及单元测试终于写完啦
    • 博客文章和文档迁移到gitbook
  • 2015
    • 博客文章和文档迁移到gitbook
    • 给客户端写得LRU缓存
    • 近期活动比较零散
    • 关于BUS通信系统的一些思考(三)
    • 针对Java JIT的优化(转表工具:xresloader)
    • libcopp更新 (merge boost 1.59 context)
    • 小记最近踩得两个C++坑
    • Redis全异步(HA)Driver设计稿
    • Vim常用命令
    • 关于firewalld和systemd的一些命令速记
    • Jenkins(hudson)插件记录
    • 我们的Lua类绑定机制
    • LLVM+Clang+Libcxx+Libcxxabi(3.6)工具链编译(完成自举编译)
    • 回顾2014
    • Android NDK undefined reference to ___tls_get_addr 错误
    • gitlab腾讯企业邮箱配置
  • 2014
    • 回顾2013
    • C++11动态模板参数和type_traits
    • C++又一坑:动态链接库中的全局变量
    • tolua++内存释放坑
    • [转]类似github的框架
    • Lua性能分析
    • 集成Qt Webkit 到cocos2d-x
    • Gitlab环境搭建小计
    • 近期研究VPN的一些记录(OpenVPN,pptp,l2tp)
    • LLVM + Clang + Libcxx + Libcxxabi 工具链编译
    • 关于BUS通信系统的一些思考(二)
    • 关于BUS通信系统的一些思考(一)
    • [libiniloader] Project
    • 记录一些在线编辑器
    • [WP Code Highlight.js] Project
    • 再议 C++ 11 Lambda表达式
    • 基于Chrome插件的开发工具链
    • [ACM] HDU 1006 解题报告
    • Linux 编译安装 GCC 4.9
    • 又碰到了这个解谜游戏,顺带记下地址
    • 简单C++单元测试框架(支持一键切到GTest或Boost.Test)
    • 捣鼓一个协程库
  • 2013
    • std和boost的function与bind实现剖析
    • 不知道是哪一年的腾讯马拉松题目 照片评级 解题报告
    • Lua 挺好用的样子
    • VC和GCC成员函数指针实现的研究(三)
    • VC和GCC成员函数指针实现的研究(二)
    • VC和GCC内成员函数指针实现的研究(一)
    • 一个C++关于成员变量偏移地址的小Trick
    • ptmalloc,tcmalloc和jemalloc内存分配策略研究
    • POJ 2192 Zipper HDU 2059 龟兔赛跑
    • 从Javascript到Typescript到Node.js
    • 网络编程小结
    • 试试Boost.Asio
    • Lnmp yum 安装脚本 (for CentOS)
    • ARM 交叉编译环境搭建
    • Linux 编译安装 GCC 4.8
    • [记录]虚拟硬盘的压缩|磁盘写零
  • 2012
    • Boost.Spirit 初体验
    • “C++的90个坑”-阅读笔记
    • AC自动机
    • C++ 标准过渡期
    • 程序员修炼之道 -- 阅读笔记
    • [转载]狼与哈士奇
    • C++ 新特性学习(八) — 原子操作和多线程库[多工内存模型]
    • C++ 新特性学习(七) — 右值引用
    • 理解Protobuf的数据编码规则
    • 忆往昔ECUST的ACM时代
    • Linux编译安装GCC 4.7
    • JSON显示库 -- showJson (Javascript)
    • C++ 新特性学习(六) — 新的字符串编码和伪随机数
    • C++ 新特性学习(五) — 引用包装、元编程的类型属性和计算函数对象返回类型
    • C++ 新特性学习(四) — Bind和Function
  • 2011
    • C++ 新特性学习(三) — Regex库
    • C++ 新特性学习(二) -- Array、Tuple和Hash库
    • C++ 新特性学习(一) -- 概述+智能指针(smart_ptr)
    • Linux 和 Windows PowerShell 常用工具/命令 记录
    • 非常帅气的Linq to sql
    • 2011 Google Code Jam 小记
    • C++总是很神奇
    • 大学生创新项目[国家级]经费使用记录
    • 常用官方文档整理
    • 我们学校的IPV6很不错嘛
  • 2010
    • 线段树相关问题 (引用 PKU POJ题目) 整理
    • 2010 ACM 赛前笔记
    • POJ PKU 2596 Dice Stacking 解题报告
    • POJ PKU 3631 Cuckoo Hashing 解题报告
    • POJ PKU 1065 Wooden Sticks 3636 Nested Dolls 解题报告
    • HDU 3336 Count the string 解题报告
    • Hash模板 个人模板
    • ZOJ 3309 Search New Posts 解题报告
    • POJ PKU Let's Go to the Movies 解题报告
    • 注册表常用键值意义
    • PKU POJ 1724 ROADS 解题报告
    • 《神奇古今秘方集锦》&《民间秘术大全》
    • PKU POJ 1720 SQUARES 解题报告
    • POJ PKU 2155 Matrix 解题报告
    • PKU POJ 1141 Brackets Sequence 解题报告
    • PKU POJ 2728 Desert King 解题报告
    • PKU POJ 2976 Dropping tests 解题报告
    • PKU POJ 3757 Simple Distributed storage system 解题报告
    • GCD Determinant 解题报告
    • Southeastern European 2008 Sky Code 解题报告
    • HDU HDOJ 3400 Line belt 解题报告
    • 线性筛法求质数(素数)表 及其原理
    • HDU HDOJ 3398 String 解题报告
    • 树状数组模块(个人模板)
    • 浙江理工 省赛总结 team62 By OWenT of Coeus
    • POJ PKU 3659 Cell Phone Network 解题报告
    • USACO 2008 March Gold Cow Jogging 解题报告
    • C#格式化输出(记录)
    • 参加有道难题笔记
    • POJ PKU 2446 Chessboard 解题报告
    • POJ PKU 1986 Distance Queries 解题报告
    • 计算几何算法概览[转载]
    • 关于差分约束(转载)
    • POJ PKU 2826 An Easy Problem?! 解题报告
    • 数论模板(个人模板)
    • 简易四则运算(ACM个人模板)
    • Catalan 数
    • The 35th ACM/ICPC Asia Regional Tianjin Site —— Online Contest 1009 Convex 解题报告
    • JQuery扩展插件--提示信息
    • ACM 计算几何 个人模板
    • 解析网站字符串型参数 Javascript QueryString 操作 TQueryString类
    • POJ PKU 1474 Video Surveillance 解题报告
  • 2009
    • 模式匹配(kmp)个人模板
    • 并查集 模板
    • POJ 3267 The Cow Lexicon 解题报告
    • C/C++语言常用排序算法
    • POJ 2606 Rabbit hunt 2780 Linearity 1118 Lining Up 解题报告
    • 打造最快的Hash表(转) [以暴雪的游戏的Hash为例]
    • ECUST 09年 校赛个人赛第六,七场总结
    • ECUST 09年 校赛个人赛第三场部分解题报告(A,D,F,I)
    • 牛顿迭代解方程 ax^3+bX^2+cx+d=0
    • 09年8月9日 ECUST ACM 练习赛总结
    • 连接最多点直线 (OWenT 个人模板)
    • 点到直线距离 和 线段间最短距离 (OWenT 模板)
    • ECUST 09年 校赛个人训练赛第五场总结
    • ECUST 09年 校赛个人赛第八场(最后一场)总结
    • 09年8月14日 ECUST ACM 练习赛总结
    • 矩阵相关 (增强中)
    • Prime最小生成树(个人模板)
    • 最长单调子序列 复杂度nlog(n)
    • POJ PKU 2549 Sumsets 解题报告
    • POJ PKU 3277 City Horizon 解题报告
    • 我的ACM生涯
    • POJ PKU 2528 Mayor's posters 解题报告
    • POJ PKU 2378 Tree Cutting 解题报告
    • POJ PKU 1990 MooFest 解题报告
Powered by GitBook
On this page
  • 前言
  • LRU实现设计
  • 客户端上的应用
  • 服务器上的应用
  • 代码实现

Was this helpful?

  1. 2015

给客户端写得LRU缓存

前言

由于我们的客户端的元素和资源比较多,cocos框架的各种库质量参差不齐,导致了有些地方加载速度实在很慢。并且没有一个统一的内存管理机制导致了整个内存占用不太好控制。

同时手机的硬件环境实在是千差万别,在IOS上,由于CPU和IO比较好,很多东西重算代价倒不大。 但是在Android上,CPU本来就偏弱,然后很多国产性价比机器,零件都缩水在IO设备上,还附加了各种用于节能和降低发热的降频策略,锁CPU策略。导致了很多数据重建的延迟比较高。 然而我们很容易发现,大多数Android的机器的内存都非常高,动辄2-3GB。 所以就希望说我们的应用能够最大化的利用内存作为缓存,在IOS上内存不够时重算,在Android上就拼命地用内存坐缓存,加载loading速度。

于是乎有了个写个LRU算法作为资源管理的想法。并且既然要做,就做得尽量简单、可复用,最好还能到时候服务器上也用。

LRU实现设计

由于最主要还是由客户端的问题引起的,所以最先还是考虑客户端的需求。目标如下:

  1. 首先这个管理器必须能够管理多种不同类型的资源(这是必须得,不然没存在的意义)

  2. 其次需要在不同类型的设备上用,所以一个很重要的功能就是自适应,能够弹性得自动调整在不同设备上的阈值

  3. 低性能消耗的LRU算法

为解决第一个问题,我们把LRU对象池分为两部分,LRU管理器和对象池。其中LRU管理器负责管理所管辖的多种不同类型对象的对象池,然后负责判定LRU算法的缓存和失效淘汰规则。而对象池中则实际负责保存对象缓存。

对第二个问题,考虑到在IOS上有专门的事件通知内存报警,但是在Android没有,所以为了简便起见,统一设置告警走类似IOS的报警作为LRU的主动GC操作。这样的话Android需要自己判定低内村并触发内存回收。 关键在于回收的同时需要动态调整阈值,以适应当前的内存总量。目前的策略是单触发主动回收时,各项阈值减半,并大量回收资源。而push数量过多时触发一次资源回收,但是随之会将阈值的上限+1。总体上有点像TCP的拥塞算法。

最重要的就是LRU算法设计,为了减少LRU管理器的消耗,默认情况下对LRU资源池的操作都可以认为是O(1)的。

  1. 每种对象池内部维护一个队列,记录着所有缓存的空闲对象。

  2. LRU管理器内部维护一个队列,记录着各类对象的进入缓存池的顺序。

  3. 每种对象池对饮一个push操作,一个pull操作。

  4. 每一次push意味着一个对象进入对象池,把该对象推入队列首,同时分配一个push id,并记录到LRU管理器的push序列队列中。

    LRU管理器的push序列队列只记录push id和对象池内部的缓存队列

  5. 每一次pull意味着从对象池里取出一个对象,直接从队列首取出。这个操作和上面的一起组成了后进先出(LIFO)队列,即用来判定LRU的最近使用对象。

  6. push id使用uint64_t基本可以认为单进程内不可能会重复(实际使用中会保留如此之多的push id,所以必定唯一)

  7. LRU管理器统计总对象数量和push序列队列长度,当总对象数量超出上限或push序列队列长度过长时触发被动GC

  8. LRU管理器提供接口触发主动GC

  9. 每次执行GC则是从push序列队列里取最前面的节点,如果该节点的push id和里面记录的缓存队列的队列尾的push id一致,则认为该节点有效,执行该对象的gc。否则就是已失效,直接跳过。

    因为存在一个对象被push,然后被pull出来,再被push进去的情况。这时候前一次的push序列记录已然已经失效,为了减少CPU消耗,pull的时候并不清理失效的push序列记录。 但是无论何时被push进对象池,push id一定是唯一的,所以只要判定push id是否有效就可以了。

    另外,由于是LRU算法,所以缓存队列里的队尾的必定是最早被push进队列的,而push序列也一定是按次序排序的,所以利用它们的这个特性可以实现O(1)复杂度的对象管理。因为每次push一定只伴随着一次pull或者一次gc。

  10. 被动GC的阈值调整是指push过多,pull过少导致的缓存数量过多时调整最大上限。对应着push序列的最大上限和总对象数量上限。每次超出都+1,然后回收一个对象。

  11. 主动GC的阈值是指系统资源不足,需要主动由外部触发的缓存回收,这时候会把push序列的最大上限、总对象数量上限和总对象数量下限(即回收的时候保留的数量)调整为和当前序列数量/对象数量的平均值。然后回收的时候仅保留总对象数量下限

    这一条和上一条配合使用,上一条会使缓存数量缓慢增加,这一条会使缓存数量大幅减少。这样,在高内存机器上就会很少触发主动GC,低内存机器上多触发主动GC。 目前使用这两种算法来实现各个机型上的动态缓存数量。

  12. 在实际使用过程中加入了超时机制,即即便内存很大,超过一定时间的push序列也会触发超时回收。

  13. 在实际使用过程中加入了自动调整各类上下限阈值的边界,因为在客户端场景里,有时候触发主动GC之后,有些资源会过一会再回收资源或在下一个关键点再回收。这会导致短时间内频繁触发主动GC。这会使动态调整的限制很快被调整到0值。以防这种情况再出现,加入了一个边界限制。

客户端上的应用

实际缓存池的实际使用过程中还是碰到了一些问题的。首先是cocos的很多组件本身有缓存机制,比如dragonbones和spine,还有sprite对贴图文件的缓存,对于这种对象实测缓存的影响不是特别大。

影响特别大得就是cocostudio创建的对象,我们里面实施了两层缓存。第一层是把csb文件缓存进来,这样可以减少IO操作,但是后来发现根据csb文件创建cocostudio节点反而花费了更多的时间(2/3的时间耗在这里)。 所以后来不得不对cocostudio创建的节点做缓存。然而如果是一开始就使用这个缓存的话就比较容易发现问题,我们中途开始切入这个缓存的话就发现。我们的很多UI模块代码并没有特别去重置CCNode,而是依赖析构作为资源回收和重置。 这导致了很多地方如果回收作为缓存的话,这次改动的地方,就变成下次读入以后的默认值。包括上一次添加到ListView里的节点都还在。

为了解决这个问题,我们不得不改动了cocos的源代码,对CCNode里一些公用的属性,比如children、position、anchor等属性做了快照。然后下次pull完以后恢复到快照。本来也想直接使用cocos的clone函数,无奈cocos的clone接口实现不全最后没有使用。 而cocostudio创建的一些子类的ui对象比如ListView、ImageView等等里那些不属于CCNode的属性,就要求使用前手动reset,因为如果每个子类都去加缓存的话对cocos的改动有点大,怕以后不好merge。

最后就是实际释放缓存的过程中,有些数据是在切换场景的时候释放的,这时候很多对资源的引用都会清空,不会再次使用。 比如sprite的贴图,清理的时候如果场景里还有引用到的地方,是不会清除的。 再比如dragonbones的骨骼和贴图,dragonbones自己有一层缓存和引用记录,但是它做得不好,在缓存清理的时候不通知被引用的Node,然后会导致被引用的Node在渲染时崩溃。所以清除dragonbones自身的缓存的同时还必须清理所有已有的对象。 而且特别是dragonbones和spine,即便目前没有使用在一场战斗中十有八九马上也会用到。战斗中卡一下的体验是非常不好的。所以对这些资源都会延后释放。

延后释放也会碰到一个问题,就是可能短时间内内存并没有被清理出来,然后会频繁调用主动GC。于是就有了上面第12条提到的限制。但是特别是IOS既然到了内存告警,最好先释放一部分出来。以备后用。 所以我们的缓存回收里加了一些分级,有些对象常规内存回收不做,紧急内存(IOS内存告警)回收会尽量释放一些应该不会再被引用到的资源。

服务器上的应用

暂时还未实施,但是这样的LRU算法设计也考虑了服务器上可能可以使用的场景。比如聊天服务器按活跃度来缓存玩家或者频道的聊天数据,淘汰冷数据。

另外在服务器端虽然不用太多地考虑内存回收问题,但是可以利用这个算法管理器的过期机制来提供定期保存的功能。甚至实现定期脏数据保存的功能。

定期保存: 每个对象只push一次,在gc时保存并重新push进pool

定期脏数据保存: 每个对象在写脏时先pull再push一次,在gc时保存

并且这些功能都可以利用lru管理器的各类上限来实现过载保护

代码实现

Previous博客文章和文档迁移到gitbookNext近期活动比较零散

Last updated 6 years ago

Was this helpful?

以上的代码位于: 单元测试见:

Written with .

https://github.com/owent-utils/c-cpp/blob/master/include/MemPool/lru_object_pool.h
https://github.com/owent-utils/c-cpp/blob/master/test/case/LRUObjectPoolTest.cpp
StackEdit