热门标签 | 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各有利弊,有些时候系统中它们会同时出现。


推荐阅读
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 拥抱Android Design Support Library新变化(导航视图、悬浮ActionBar)
    转载请注明明桑AndroidAndroid5.0Loollipop作为Android最重要的版本之一,为我们带来了全新的界面风格和设计语言。看起来很受欢迎࿰ ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • PHP图片截取方法及应用实例
    本文介绍了使用PHP动态切割JPEG图片的方法,并提供了应用实例,包括截取视频图、提取文章内容中的图片地址、裁切图片等问题。详细介绍了相关的PHP函数和参数的使用,以及图片切割的具体步骤。同时,还提供了一些注意事项和优化建议。通过本文的学习,读者可以掌握PHP图片截取的技巧,实现自己的需求。 ... [详细]
  • 生成对抗式网络GAN及其衍生CGAN、DCGAN、WGAN、LSGAN、BEGAN介绍
    一、GAN原理介绍学习GAN的第一篇论文当然由是IanGoodfellow于2014年发表的GenerativeAdversarialNetworks(论文下载链接arxiv:[h ... [详细]
  • 也就是|小窗_卷积的特征提取与参数计算
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了卷积的特征提取与参数计算相关的知识,希望对你有一定的参考价值。Dense和Conv2D根本区别在于,Den ... [详细]
  • Python瓦片图下载、合并、绘图、标记的代码示例
    本文提供了Python瓦片图下载、合并、绘图、标记的代码示例,包括下载代码、多线程下载、图像处理等功能。通过参考geoserver,使用PIL、cv2、numpy、gdal、osr等库实现了瓦片图的下载、合并、绘图和标记功能。代码示例详细介绍了各个功能的实现方法,供读者参考使用。 ... [详细]
  • Html5-Canvas实现简易的抽奖转盘效果
    本文介绍了如何使用Html5和Canvas标签来实现简易的抽奖转盘效果,同时使用了jQueryRotate.js旋转插件。文章中给出了主要的html和css代码,并展示了实现的基本效果。 ... [详细]
  • 本文总结了在开发中使用gulp时的一些技巧,包括如何使用gulp.dest自动创建目录、如何使用gulp.src复制具名路径的文件以及保留文件夹路径的方法等。同时介绍了使用base选项和通配符来保留文件夹路径的技巧,并提到了解决带文件夹的复制问题的方法,即使用gulp-flatten插件。 ... [详细]
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社区 版权所有