热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

《SmallMemorySoftware:PatternsForSystemWithLimitedMemory》读书笔记

原文地址:http:blog.csdn.netjinzhuojunarticledetails13297447虽然摩尔定律让我们的计算机硬件得以以指数速度升级

原文地址:http://blog.csdn.net/jinzhuojun/article/details/13297447

 

虽然摩尔定律让我们的计算机硬件得以以指数速度升级,但反摩尔定律又不断消减这些升级所带来的好处。其原因之一就是面对硬件的更新换代,程序员似乎不用再对内存精打细处“了。而近年来随着穿戴式设备和大数据平台的兴起(一个是内存本身受限,一个是对内存的需求巨大),让内存的有效利用又成为了值得开发人员关注的热点。

 

Small MemorySoftware: Patterns For System With Limited Memory 》(http://www.smallmemory.com/ , 中文名为《内存受限系统之软件开发:针对内存受限系统而整理的模式》)一书探讨了编程中有效利用内存的方法。全书分为五个部分,分别从架构,次级存储,压缩,数据结构和内存分配进行了阐述。下面是一些读书笔记,留作备忘。

 

Small Architecture(小内存架构)

Memory Limit(Fixed-sized Heap,Memory Partitions):让系统中的每个组件负责自己的内存,为每个组件设置配额,一旦用完即分配失败。这种模式主要是为了防止系统中某一组件耗光系统所有内存的情况发生,而缺点是容易引起内存浪费,因为会出现系统中一些组件还有空闲内存而另外一些内存分配失败的情况。实现上主要有三种方法:截获内存申请释放操作,独立堆和独立进程。

Small Interface:当整个系统被分成若干个组件,而每个组件又独立管理自己的内存使用,那么当内存数据在这些组件间进行传递时,就需要定义接口(或者说协议)。在组件间交换数据有三种方式:Lending(客户传对象给系统组件), Borrowing(系统组件传对象给客户)和Stealing(可双向,同时对象所有权也发生变化)。另外组件间的数据传递也经常会通过Iterator这种增量式的形式来进行。

Partial Failure(Graceful degradation,Feast and Famine):当系统申请内存失败时,让系统在一个“降级模式”下继续运行而不是直接退出。如字体加载失败就用系统字体,图片加载失败就用占位符代替。那些因为内存不足而无法进行的计算可以不执行或是缓存起来以后执行。当内存不那么紧张时,系统要能恢复到先前的模式。值得注意的是,处理Partial Failure本身也需要内存,因此需要预分配这部分内存(称为Rainy day fund)。后面的Multiple representationApplicationSwitching都可作为其实现形式。

Captain Oates(Cache Release):当整个系统内存紧张时,砍掉不必要或是次要的组件,亦或是清空一些缓存得以让整个系统继续运行。它的核心思想是牺牲那些次要的功能来留全那些主要的功能。Captain OatesPartial Failure中很多技术是通用的,前者描述其它组件内存不足时本组件该干什么,后者描述当前组件内存不足时本组件该干什么。

Read-Only Memory(Use the ROM):有些数据是不常更新的,如可执行代码,常量字符串,资源文件等。把这些数据放在只读存储区域中,至少有两个好处:其一是可以通过共享节约内存;另一好处是要换出到次级存储时不用写回操作,从而提高了效率。如果需要更新这些只读数据可以用后面会提到的Copy-on-write或者Hooks

Hooks(Vector table, Jumptable, Patch table, Interrupt table):想要修改只读存储区域上的数据,可以通过间接访问只读内存的方法,中间用位于可写区域的Hook做跳板。Hook的一个例子就是GOT表,代码段被加载在只读段中,而GOT表加载在可写段中,因为它是要在运行时被Dynamic linker修改的。这样当代码段中有外部函数调用时,会通过GOT表来跳转。这样就实现了代码段不变而改变程序逻辑的目的。

 

Secondary Storage (次级存储)

Application Switching(Phases, ProgramChaining, Command Scripts):假设系统提供很多功能而用户不会同时用到,就可以把系统分为很多个独立的可执行程序,等用户需要哪个再起哪个,每次只运行一个。这类似于Unix命令的哲学,把一系列功能专一的独立工具组合连接起来完成强大的功能。程序之间可通过参数,磁盘和环境变量等转递信息。要传输复杂的对象还可能会用到Memento模式。

Data File Pattern(Batch Processing,Filter, Temporary File):如果要处理的数据太多,没法或是没必要一次全部载入,就一批批从次级存储放进内存处理。例如要将一个无法一次放入内存的大数据排序,可以先将之切分使之能够载入内存,然后分别进行归并排序,直到所有数据排序完成。

Resource File Pattern:资源文件一般包括字体,图标,字符串,布局和配置信息等。这些东西一般不会被改写,且在程序运行的任何时候都有可能用到,但用完后即可以被丢到次级存储器上,下次要用可以再去磁盘上取。

Packages(Components, LazyLoading, Dynamic Loading, Code Segmentation):将整个系统分成很多功能包,要什么功能的时候再动态加载。加载可以以单独进程形式也可以动态链接,亦或手工加载进内存,它们各有利弊。如浏览器插件,Java中的classLinux中的driver都是利用了这种动态加载的概念。实际要注意的是binary级的匹配问题。Abstract Factory模式可用来统一客户和包实现的接口。Proxy模式可用于包的自动加载和卸载。

Paging Pattern(Virtual Memory,Persistence, Backing Store, Paging OO DBMS):基于空间局部性,将程序最近常用的数据(Working set)保留在内存中,其余放在次级存储器中,要用时再读进来。它在形式上按粒度从小到大有Demanding page(页级), Object Oriented Databases(对象级)和Swapping(进程数据级)。典型的例子当然就是操作系统中的页式管理了。

 

Compression(压缩)

Table CompressionPattern(Nibble Coding, HuffmanCoding):由于数据中每个元素出现概率不一样(信息量不一样)或者说重要性程度不一样,因此如果对原始数据进行重新编码,可以达到压缩的目的。典型的例子如Huffman编码(基于字符出现频率不一样)和JPEG(基于人眼对色彩的敏感程度不一样)。

Difference CodingPattern(Delta Coding, RunLength Encoding):差分编码,即利用数据(尤其是音、视频等流数据)的前后相关性进行压缩编码,如MPEG格式。我们知道压缩视频中一般有关键帧和非关键帧之分,关键帧可被独立解码,而非关键帧只存有差分信息。差分编码的实现技术有Delta Coding,Run Length Encoding,LossyDifference Compression,此外还需为传错而考虑重同步问题。

Adaptive CompressionPattern:在压缩前或者压缩中分析数据来得到最好的压缩参数或实时调整参数。与前两种方式相比自适应算法处理时间更长,因此不适用于实时任务。如gzip, bzip2压缩方法就使用了该模式。

 

Small Data Structure(小内存数据结构)

Packed Data(Bit Packing) :一方面我们有时会用过大的类型存储数据,另一方面计算机体系一般会将数据进行对齐存储从而优化读写效率,有时这会导致内存利用效率的降低。我们可以通过将数据结构进行pack来减少内存的使用。缺点是它会较大地影响性能。注意将用句柄代替指针也能节省一部分内存,因为句柄一般都比指针小,可以用更小的数据类型存储。

Sharing (Normalisation):内存共享,节约内存。共享的东西在内存中仅需要一份拷贝。有些是明显可以共享的,就像动态库libc.so,有些由于内容重复可以共享的,如Java中的String使用了Flyweight模式,对重复字符串只存储一份。简单的共享可以用静态全局变量,Sigleton模式或者LazyInitialization实现。复杂点的可以用Shared cache,即把所有的共享对象放入数据库(cache),使用时用key查询来得到。

Copy-on-Write:共享的东西需要改动时,就将之拷贝一份,再进行读写。像Linux中对于共享页在页表中是标识成只读的,当对其进行写操作时,就会发生page fault,从而把控制权交还kernel进行Copy-on-write的处理。Copy-on-write常用Proxy模式实现,以实现过程对客户透明。

Embedded Pointer:对于一些用指针组建的数据结构(如链表,树,图等),将前后指针放在要存储的目标对象中,不仅能节省维护数据结构本身的内存,而且能在遍历时节省临时内存(如栈,队列)的使用。实现起来有三种方式:继承,内联和预处理。像Linux中的list_head就是用了内联嵌入指针。另外相关的技术还有Pointer DifferencesPointer reversal

Multiple Representation:Template MethodStrategy模式的作用差不多,只是这儿用在内存利用上了。即准备多套实现,根据内存的使用情况进行切换。该模式一大优点是对客户透明。例子如JavaSTL中的容器类,它们接口统一,但对内存的使用策略却不同。

 

Memory Allocation(内存分配)

原则上只要能满足需求,能用简单的就用简单的。内存分配主要就是和两个问题作斗争:一是碎片问题(解决方法有固定分配,拷贝压缩,池式管理等);二是内存不足问题(解决方法有固定客户内存大小,产生错误信号,降低质量,删除旧对象,延迟申请和忽略问题等)。

Fixed Allocation(Static Allocation,Pre-allocation):初始化时分配固定大小内存,程序执行时不再动态分配,而初始化时可以动态也可以静态分配。这样做的好处是不会出现分配失败的情况,系统可预测性好,且内存分配高效,坏处是不够灵活。

Variable Allocation(Dynamic Allocation):当需要的时候再分配内存,适合预先不知道数据大小的情况(如通用库)。它的缺点也是Fixed Allocation的优点:即分配可能失败(此时可以考虑引入Partial Failure),分配操作会影响性能(将Fixed Allocation与之结合,就有了Pooled Allocation),需要自己释放内存(可用ReferencecountingGarbage Collection),另外动态分配还更容易产生碎片(可用Compaction压缩已有对象,MemoryDiscard删除临时对象)。

Memory Discard(Stack Allocation,Scratchpad):适用于临时用的内存。在操作前从栈、堆或是预分配区域中进行分配,操作完了就一起丢弃。好处是内存的分配和释放都只需要移动下指针即可,简单粗暴高效。函数调用过程中栈帧的处理就是该模式的例子。另一种少见的情况就是要用一个现成的但有内存泄露问题的组件,可以在加载它之前为之分配临时内存,卸载时把它的内存全部统一清空。用Memory Discard的时候要格外小心临时对象所持有的外部资源引用,在释放内存前要先释放这些资源。

Pooled Allocation(Memory Pool):内存池适用于大小类似,但次数较为频繁的内存分配请求。通常做法是先预分配一块内存(通过Fixed Allocation),将其按固定大小切分成为内存池,用空闲队列进行管理。程序执行过程中需要内存了就从里边拿,释放后也放回其中(Variable Allocation)。该模式结合了Fixed AllocationVariableAllocation的优点。Linux中的Slab就是个例子。

Compaction(Manged Table, MemoryHandles, Defragmentation):压缩主要用来处理Variable Allocation产生的碎片问题。当系统跑了很长一段时间后,剩下很多不连续的空闲内存,当用户申请一块大的内存时,申请就容易失败。比较简单的方法就是拷贝数据让它们整一块儿去。注意由于指针的存在,这些指针在压缩过程中需要被及时更新,该问题一般可用间接引用(即用句柄或对象表代替指针)来解决。

Reference Counting:主要用于共享对象的回收,使这些对象在没人使用时被自动回收。大体思路是在对象中记录该对象的引用数来判断其是否是垃圾,当引用数为0时说明无人引用,可以被清除。和GarbageCollection相比,它的优点是本身的overhead被分摊到整个执行过程,因此对用户的实时响应影响不大,且一旦对象变垃圾立即会被清除;缺点是需要程序员来主动维护引用计数,且影响系统性能,另外要处理环形引用比较麻烦。

Garbage Collection(Mark-sweep GarbageCollection, Tracing Garbage Collection):也是主要用来处理共享对象的回收。系统在一定时候(一般是现有内存不足时)触发垃圾回收,然后从一些根结点(通常为栈变量,全局及静态变量,外部库的引用等)出发遍历所有被引用对象。这样剩下的就是可以被清除的垃圾了。清除垃圾有两种做法:Mark-sweep GCCopying GC,实际中它们可结合使用(像mono中的SGen)。Garbage Collection的优点是更加自动,无需用户参于,且总体性能损失相对更小;缺点是回收时会有停顿现象,另外对Finalization(一般用于释放文件句柄或设备等外部资源)的支持会比较麻烦(一般做法是把它们加入到待办队列,然后到单独线程去调用,但这样它们被调用的时间就不好预测,容易使系统变得不稳定)。由于该模式和Reference Counting各有利弊,有些时候系统中它们会同时出现。


推荐阅读
  • 本文介绍了H5游戏性能优化和调试技巧,包括从问题表象出发进行优化、排除外部问题导致的卡顿、帧率设定、减少drawcall的方法、UI优化和图集渲染等八个理念。对于游戏程序员来说,解决游戏性能问题是一个关键的任务,本文提供了一些有用的参考价值。摘要长度为183字。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • 一、Hadoop来历Hadoop的思想来源于Google在做搜索引擎的时候出现一个很大的问题就是这么多网页我如何才能以最快的速度来搜索到,由于这个问题Google发明 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 本文详细介绍了MysqlDump和mysqldump进行全库备份的相关知识,包括备份命令的使用方法、my.cnf配置文件的设置、binlog日志的位置指定、增量恢复的方式以及适用于innodb引擎和myisam引擎的备份方法。对于需要进行数据库备份的用户来说,本文提供了一些有价值的参考内容。 ... [详细]
  • 本文介绍了PhysioNet网站提供的生理信号处理工具箱WFDB Toolbox for Matlab的安装和使用方法。通过下载并添加到Matlab路径中或直接在Matlab中输入相关内容,即可完成安装。该工具箱提供了一系列函数,可以方便地处理生理信号数据。详细的安装和使用方法可以参考本文内容。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 基于事件驱动的并发编程及其消息通信机制的同步与异步、阻塞与非阻塞、IO模型的分类
    本文介绍了基于事件驱动的并发编程中的消息通信机制,包括同步和异步的概念及其区别,阻塞和非阻塞的状态,以及IO模型的分类。同步阻塞IO、同步非阻塞IO、异步阻塞IO和异步非阻塞IO等不同的IO模型被详细解释。这些概念和模型对于理解并发编程中的消息通信和IO操作具有重要意义。 ... [详细]
  • 本文介绍了在Windows环境下如何配置php+apache环境,包括下载php7和apache2.4、安装vc2015运行时环境、启动php7和apache2.4等步骤。希望对需要搭建php7环境的读者有一定的参考价值。摘要长度为169字。 ... [详细]
  • 本文详细说明了在JavaScript中解决alert弹出窗口文本换行问题的方法。通过给alert弹出的文本添加换行符,可以实现在弹窗中显示多行文本的效果。同时,提供了相关代码示例和注意事项,帮助读者更好地理解和应用这一解决方法。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件
    本文旨在全面介绍Windows内存管理机制及C++内存分配实例中的内存映射文件。通过对内存映射文件的使用场合和与虚拟内存的区别进行解析,帮助读者更好地理解操作系统的内存管理机制。同时,本文还提供了相关章节的链接,方便读者深入学习Windows内存管理及C++内存分配实例的其他内容。 ... [详细]
author-avatar
丶希_
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有