如何解决内存碎片问题


如何解决内存碎片问题?
————NEOHOPE的内存碎片优化手册

其实无论采用哪种分配方式,内存的碎片化都是难以彻底避免的。无论是操作系统、虚拟机还是应用,都要面对这个问题。
业界有多种思路来解决或缓解此问题:

一、操作系统层面
1、把不可移动内存集中管理,内存分区其实在一定程度上解决了这些问题
从操作系统的角度来说,有些内存页面是可以移动的,有些是无法移动的。如果无法移动的页面过于分散,整个系统内存的连续性就很差,碎片化就更严重,而且更加不容易管理。所以在操作系统设计的时候,一般会根据功能用途对内存进行分区,一定程度上避免此类问题。

2、linux采用了buddy system来缓解内存碎片问题
buddy system是一个很巧妙的设计,将内存连续页面按2的n次方进行分组。
申请时会匹配第一个大于等于所需页面的2的n次方连续页面,并将多余页面拆分后挂载到对应的2的x方组。
释放内存页面时,仍按2的n次方进行归还,并尝试将内存进行合并。

3、linux中为了处理特别小的内存请求,引入了slab技术,来辅助buddy system
类似于对象池的概念,系统先申请一部分页面用于对象池。
申请时划分一部分出来给应用使用,如果内存不足会进行主动扩容。
归还时对象还给对象池,如果空闲对象比较多时,会主动释放部分内存。

4、windows有一种LFH(Low Fragmentation Heap)技术,缓解内存碎片问题
在应用程序启动时,操作系统会额外分配一定的连续内存LFH给这个进程备用
如果应用需要使用内存,会优先从LFH中申请,从而降低系统层面的内存管理负担

5、windows在进程退出后,不会立即释放dll文件内存
一方面提速,如果关闭一个应用,再开启,就会感觉很快
另一方面也缓解了操作系统内存管理负担。
其实,看下你手机的APP,切换到后台时,就是这个效果

6、内存整理服务
无论是linux还是windows都有低优先级线程,在后台默默做着内存规整工作,类似于磁盘碎片清理
比如linux内核中的kcompactd线程。

7、类似与LFH,可以考虑在内存分配时额外预留一部分,下次分配在预留的地方继续分配
windows在xp时代,需要应用自行开启LFH功能,但vista之后,操作系统会同一进行管理

8、为了内存规整方便,可以考虑靠近应用已分配内存区域进行分配
其实可操作性不高,还不如上一条可行性好一些

9、还有一种思路,就是将不连续的内存,转换为逻辑上连续的内存,来绕过碎片化问题
但一些情况下性能难以保证

二、虚拟机层面
1、JVM整体内存会被划分为多个部分,类似于做了分区:
一部分是虚拟机共有的【方法区、堆】,一部分是线程私有的【虚拟机栈、本地栈、程序计数器】

2、同时,JVM虚拟机也会根据对象的生命周期,类似进一步做了分区,而且不同分区采用不同GC策略
最常用的就是年代划分法,新生代、老年代、永久代【后来的Metaspace】

3、JVM虚拟机,GC时会通过标记-整理(比如CMS)或复制-清除(比如G1)的方法来解决部分碎片问题

三、应用层面
1、redis在处理内存的时候,申请时会额外申请一部分先备着【记得是jemalloc】

2、redis释放时也不会立即释放,有单独的线程进行处理,在应用层面去降低系统内存管理负担

3、redis在数据结构上也做了很多努力

4、在写程序时,如果需要频繁创建和释放对象,可以尝试使用对象池

5、在写程序的时候,尽量不要零零散散的去申请大量小内存;

6、除了标准库以外,可以试一下 jemalloc或Doug Lea’s malloc

7、感兴趣可以看下redis内存管理的代码

如何写出让CPU跑得更快的代码


如何写出让 CPU 跑得更快的代码?
————NEOHOPE的代码优化手册

一、需求阶段

1、过早优化是魔鬼。业务能运行,比业务响应速度更重要。能用的慢代码,比不能用的快代码好的多

2、搞清楚业务类型,是重IO,还是重CPU,还是重GPU,明确具体需求

3、这个需求,确定是要通过代码优化解决吗?
是不是一个业务问题无法解决,想通过系统去限制业务呢?

4、这个需求,是否可以通过花钱或其他方式解决,比如增加硬件

5、搞清楚为何要优化代码,不优化问题是什么?优化代码的风险是什么?时间周期大概是剁手?代价和受益如何?

6、对比4、5的各种方案,确定用哪个

二、分析阶段

1、一旦明确了要进行优化,不要一头扎入细节。考虑整个系统的架构是否合理,看下全局,看下上下游的影响。

2、遵从Ahmdal定律,确定优化范围;

3、咱们要优化的东西,业界是否已有解决方案,不要重复造轮子

4、如果涉及面比较广,别急着动手,做好分析,制定方案,再动手

三、架构阶段

1、调整不合理的架构

2、调整不合理的数据流

3、进行必要的组件替换

四、算法提升阶段

1、遵从80-20法则,程序80%的时间在运行20%或更少的代码,针对热代码进行优化,才容易产出效果;

2、评估当前算法,是否有高效的算法
比如:用DP或贪婪算法替代暴力算法,将算法的时间复杂度尽量降低

3、使用合理的数据结构
比如哈希表、红黑树

4、使用数学公式,替代低效计算

5、缓存重复出现的中间结果,空间换时间

五、编码提升阶段

1、遵从数据访问的局部性法则,按数据存放顺序访问内存效率远高于乱序访问内存效率,也就是尽量帮助CPU做好数据Cache的预测工作。同样根据Cache大小,做好数据结构的优化工作,进行数据压缩或数据填充,也是提升Cache效率的好方式;

2、遵从指令访问的局部性法则,减少跳转指令,同样是尽量帮助CPU做好数据Cache的预测工作;现代CPU都有一些预测功能【如分支预测】,利用好CPU的这些功能,也会提升Cache命中率;

3、减少内存的重复申请和释放

4、 减少函数调用层数
使用INLINE等关键字,同样是减少调用层数
使用宏展开,也有类似效果

5、减少数据传递个数,合理封装

6、数据传递,以引用为主,减少传值

7、减少数据类型转换

10、调整运算顺序,减少CPU低效运算。
比如除法运算。

11、调整运算顺序,尽早结束循环,避免无效操作

12、 少用高级语言的多重继承等特性

13、使用汇编和C语言,而不是更高级的语言,很多时候可以提速运算效率;

14、直接使用昂贵的寄存器作为变量,可以变相提供加速效果;

15、采用性能高的函数,批量处理数据,如memset等

16、能在编译阶段完成的任务,不要留到运行阶段完成。
比如,CPP的泛型是编译阶段完成的,比运行时再做处理的语言效率更高
比如,不要用反射,不要动态类型绑定
比如,初始化工作尽早完成

六、编译阶段

1、开启编译器优化,速度最大化

2、使用Intel自家的编译器,开启性能优化,很多时候可以提速运算效率;

3、考虑指令级并行性(IPL)

七、运行阶段

1、避免计算线程在多个核心之间漂移,避免缓存重复加载,可以绑定核心【物理核即可,不用到逻辑核】,提高效率;

2、去除伪共享缓存:在多核环境下,减少多个核心对同一区域内存的读写并发操作,减少内存失效的情况的发生;

3、合理提高进程优先级,减少进程间切换,可以变相提供Cache提速的效果;

4、关闭Swap,可以变相提供内存提速、Cache提速的效果;

八、测试阶段

1、合适的手段及工具进行性能评估

2、要符合实际情况,不要片面追求指标

先总结这些吧。