lazy形容词,懒惰的,毫无疑问是一个贬义词。但是,对于计算机领域,lazy却是非常重要的优化思想:把任务推迟到必须的时刻,好处是避免重复计算,甚至不计算。本文的目的是抛砖引玉,总结一些编程中的lazy idea,以期有一些启发。google “lazy”这个单词,在计算机领域高频出现三个词:lazy loading(惰性加载)、lazy initializing(惰性初始化)、lazy evaluation(惰性求值),本文并不刻意区分,因为不管是loading、initializing还是evaluation都需要耗费计算机的运算资源,而且,loading(initializing)也是某种意义上的evaluation。
 

lazy ideas:

  在GOF的设计模式中,并没有一个叫“lazy loading”之类的设计模式,但是其思想贯穿在很多设计模式中。其中比较明显的就是singleton和proxy模式。

singleton

  单例模式的实现一般都有两种方式,要么在调用之前就创建好单例对象(eager way),要么在第一次调用的时候生成单例对象(lazy way),两者对象的代码大致是这样的:
  

 class eager_meta(type):
def __init__(clz, name, bases, dic):
super(eager_meta, clz).__init__(name, bases, dic)
clz._instance = clz() class singleton_eager(object):
__metaclass__ = eager_meta @classmethod
def instance(clz):
return clz._instance class singleton_lazy(object):
__instance = None
@classmethod
def instance(clz):
if clz.__instance is None:
clz.__instance = singleton_lazy()
return clz.__instance

  PS:在python中,这样使用单例模式不是很pythonic,更好的办法可见在stackoverflow上的这篇文章《creating-a-singleton-in-python》。另外在多线程环境下,要实现线程安全的单例还是很复杂的,具体讨论可参见iteye上的分析。

 

proxy:

  代理模式属于责任型模式, 使得一个对象代表另一个对象进行各种操作,常用场景包括

  • remote proxy(远程代理),如RMI, RPC
  • virtual proxy(虚代理),根据需要创建开销很大的对象,如文档中图片的加载
  • (保护代理):控制对原始对象的访问, 如智能指针
  其中 viatual proxy是使用lazy loading很好的例子
 

Short-circuit evaluation:

  短路求值在绝大多数编程语言都有实现,比较常见的语法如下:
    x and y(x && y)
    x or y(x || y)
    x if bool else y(bool? x : y )
  短路求值基本上都是数学逻辑的体现,如果第一个参数已经能推导出这个表达式的意义,那么后面的参数是无需计算的。短路求值非常有用,不仅能避免无用的计算,对于逻辑判断也非常有用。比如在python中,判断一个对象的is_ok属性为True,我们一般这么写
  if(obj.is_ok)
  如果obj被赋值成了None,那么就会报一个异常,所以可以写成
  if(obj is not None and obj.is_ok)
 
  python中,一些函数也有短路求值的特性,比如在这篇文章中提到的any函数:
     ret = any(self.calc_and_ret(e) for e in elements)
def self.calc_and_ret(self, e):
# do a lot of calc here which effect self
return True(or False)
    本意是希望对所有的element都计算,然后返回一个结果,但事实上由于短路求值, 可能后面很多的元素都不会再调用calc_and_ret
 

generator:

  在python和javascript语言中都有generator,generator与普通的函数相比,可以多次(甚至无限次)返回,而且返回值是在需要的时候才生成。在python中,下面两段代码非常相似,但事实上差异非常大:
     for x in [i*i for i in xrange(10000)]
      # do sth with i     for x in (i*i for i in xrange(10000)]
      # do sth with i
 
  generator更广泛的应用可以参见《python yield generator 详解》。javascript中generator的语法和使用与python都非常类似,可以参见这篇文章
 

函数式编程语言中的应用:

  lazy evaluation在函数式编程语言中使用得非常频繁,python也可以当做函数式编程语言来使用,而更为明显的是haskell,在其首页的features介绍里面就有大大的“lazy”

cache:

  cache也是一种lazy思想,如果之前有计算结果,那么直接复用之前的结果就行了,干嘛还要重新计算呢?而且最开始的缓存内容, 也是在需要的时候才计算的,而不是一开始就计算好。wiki上有python实现的简单例子:
   

 class Fruit:
def __init__(self, item):
self.item = item class Fruits:
def __init__(self):
self.items = {} def get_fruit(self, item):
if item not in self.items:
self.items[item] = Fruit(item) return self.items[item] if __name__ == '__main__':
fruits = Fruits()
print(fruits.get_fruit('Apple'))
print(fruits.get_fruit('Lime'))

Dirty Flag:

  在《Dirty Flag模式及其应用》一文中,列举了Dirty Flag模式的诸多应用场景。Dirty Flag显然也是很明显的lazy evaluation。比如《game programming pattern》中的例子:子模型的世界坐标取决于父模型的世界坐标以及子模型在父模型坐标空间的相对坐标,如果父模型的世界坐标变化时就主动去重新计算子模型的坐标,因为两帧之间父模型的坐标可能多次变换,往往会造成冗余的计算。所以Dirty Flag只是在父模型坐标变化的时候标记,绘制的时候再计划所有受影响的模型的世界坐标。
 

CopyOnWrite:

  CopyOnWrite即写时复制,如果大家对一份资源只有读请求时,那么资源是可以共享的,当某个访问者需要修改资源(写操作)时,就将资源拷贝一份给该访问者使用。即资源的拷贝被延迟到了第一次"写"的时候。CopyOnWrite最广为人知的两个应用场景,一个是Unix like系统fork调用产生的子进程共享父进程的地址空间,知道写操作才会拷贝一份。另一个是java中的copyonwrite容器,用于多线程并发情况下的高效访问,cookshell上有对copyonwrite容器的详细介绍。
 

web开发中的惰性加载与惰性预加载:

  在web前端和APP开发中,当提到惰性加载或者动态加载,大家往往会想到分页、轮播图、瀑布流,这些都体现了惰性加载的思想。其中,瀑布流在出诸多图片分享网站中使用非常广泛,比如花瓣网,当滑动到屏幕底部的时候才会去加载新的内容。为什么要使用惰性加载,第一个是用户体验的问题,图片资源流量比较大,一次加载太多对服务器和浏览器压力都很大,对带宽要求也很高;另外,可能用户根本就不会滑动到最下面,多加载的内容就白白浪费了。 
  当然太”Lazy”了也是不好的,总不能让玩家滑动到底部才一边显示loading icon,一边开始加载。为了提高用户体验,这类网站也会做预加载(predictive loading),即多准备一两页的内容,或者在滑屏到一定程度时开始加载新的一页,不过这样的预加载也是惰性预加载(lazy predictive loading)。
 

总结:

  本文列举了惰性计算在编程中的一些具体例子,希望能给自己以及大家有所启发,在以后遇到问题的时候多一种解决思路。由于本人编程领域以及编程语言的局限性,肯定还有诸多遗漏,欢迎大家在评论里补充。
 
references:
 

lazy ideas in programming(编程中的惰性思想)的更多相关文章

  1. lazy ideas in programming

    lazy形容词,懒惰的,毫无疑问是一个贬义词.但是,对于计算机领域,lazy却是非常重要的优化思想:把任务推迟到必须的时刻,好处是避免重复计算,甚至不计算.本文的目的是抛砖引玉,总结一些编程中的laz ...

  2. 编程提取字符串"Java is a programming language"中的各个单词,并打印输出。

    import java.lang.String; import java.util.StringTokenizer; public class StringGetWord{ /* 编程提取字符串&qu ...

  3. 03 Comments in C Programming C编程中的注释

    Comments 注释简介 Let's take a quick break from programming and talk about comments. Comments help progr ...

  4. TCP/IP网络编程中socket的行为

    一. read/write的语义:为什么会阻塞? 先从write说起: #include <unistd.h>ssize_t write(int fd, const void *buf, ...

  5. 浅谈TCP/IP网络编程中socket的行为

    我认为,想要熟练掌握Linux下的TCP/IP网络编程,至少有三个层面的知识需要熟悉: 1. TCP/IP协议(如连接的建立和终止.重传和确认.滑动窗口和拥塞控制等等) 2. Socket I/O系统 ...

  6. (转载)Linux 套接字编程中的 5 个隐患

    在 4.2 BSD UNIX® 操作系统中首次引入,Sockets API 现在是任何操作系统的标准特性.事实上,很难找到一种不支持 Sockets API 的现代语言.该 API 相当简单,但新的开 ...

  7. Linux 编程中的API函数和系统调用的关系【转】

    转自:http://blog.chinaunix.net/uid-25968088-id-3426027.html 原文地址:Linux 编程中的API函数和系统调用的关系 作者:up哥小号 API: ...

  8. 在 JNI 编程中避免内存泄漏

    JAVA 中的内存泄漏 JAVA 编程中的内存泄漏,从泄漏的内存位置角度可以分为两种:JVM 中 Java Heap 的内存泄漏:JVM 内存中 native memory 的内存泄漏. Java H ...

  9. 【转】资源文件在Delphi编程中的应用

    段东宁 计亚南 (郴州职业技术学院, 湖南 郴州  423000) 摘要: 资源文件是一种能有效地组织.管理和使用资源的文件形式,在软件开发中有着广泛的应用.本文详细介绍了在Delphi编程中资源文件 ...

随机推荐

  1. HTML5中表单中新增加元素

    新增input 内属性 1,required:若文本内容为空,阻止表单提交格式,写入单词即可 如:<input type="text" name="username ...

  2. Hadoop(十四)MapReduce原理分析

    前言 上一篇我们分析了一个MapReduce在执行中的一些细节问题,这一篇分享的是MapReduce并行处理的基本过程和原理. Mapreduce是一个分布式运算程序的编程框架,是用户开发“基于had ...

  3. [POJ 1410] Intersection(线段与矩形交)

    题目链接:http://poj.org/problem?id=1410 Intersection Time Limit: 1000MS   Memory Limit: 10000K Total Sub ...

  4. LeetCode 66. Plus One(加1)

    Given a non-negative integer represented as a non-empty array of digits, plus one to the integer. Yo ...

  5. 在Javaava中stringBuilder的用法

    String对象是不可改变的.每次使用 System.String类中的方法之一时,都要在内存中创建一个新的字符串对象,这就需要为该新对象分配新的空间.在需要对字符串执行重复修改的情况下,与创建新的  ...

  6. Paint the Grid Again (隐藏建图+优先队列+拓扑排序)

    Leo has a grid with N × N cells. He wants to paint each cell with a specific color (either black or ...

  7. Pseudoforest(伪最大生成树)

    Pseudoforest Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others) Total ...

  8. 混合app

    cordova run android            把应用发送到手机   ionic serve        电脑浏览器调试命令   创建: cordova create hello co ...

  9. 对象作为 handleEvent

    elem.addEventListener("click", obj, false);    //用对象作为处理函数   var obj = {     handleEvent: ...

  10. Servlet的理解

    一.什么是Servlet? Servlet是用Java编写的web组件,实际上可以简单的理解为是用来处理请求的,为什么这么说,看了它的生命周期就知道了. 二.常见的Servlet容器 4容器顾名思义是 ...