庄周梦蝶

生活、程序、未来
   :: 首页 ::  ::  :: 聚合  :: 管理

    sicp的习题3.22,也就是以消息传递的风格重新实现队列,我的解答如下:

(define (make-queue)
  (let ((front
-ptr '())
        (rear-ptr '()))
  (define (set-front-ptr! ptr) (set! front-ptr ptr))
  (define (set
-rear-ptr! ptr) (set! rear-ptr ptr))
  (define (empty
-queue?) (null? front-ptr))
  (define (front
-queue)
    (
if (empty-queue?)
        (error 
"FRONT called with an empty queue")
        (car front
-ptr)))
  (define (insert
-queue! item)
    (let ((new
-pair (cons item '())))
      (cond ((empty-queue?)
              (set
-front-ptr! new-pair)
              (set
-rear-ptr! new-pair))
            (
else
               (set
-cdr! rear-ptr new-pair)
               (set
-rear-ptr! new-pair)))))
  (define (delete
-queue!)
      (cond ((empty
-queue?)
             (error 
"DELETE! called with an empty queue" queue))
            (
else
               (set
-front-ptr! (cdr front-ptr)))))
  (define (dispatch m)
    (cond ((eq? m 
'front-queue) (front-queue))
          ((eq? m 'empty-queue?) (empty-queue?))
          ((eq? m 'insert-queue!) insert-queue!)
          ((eq? m 'delete-queue!) delete-queue!)
          (else
             (error 
"Unknow method" m))))
    dispatch))
(define (front
-queue z) (z 'front-queue))
(define (empty-queue? z) (z 'empty-queue?))
(define (insert-queue! z item) ((z 'insert-queue!) item))
(define (delete-queue! z) ((z 'delete-queue!)))
   
    由此,我才知道自己竟然一直没有想到,scheme完全可以模拟单向循环链表,整整第三章都在讲引入赋值带来的影响,而我却视而不见。在引入了改变函数后,数据对象已经具有OO的性质,模拟链表、队列、table都变的易如反掌。首先,模拟节点对象,节点是一个序对,包括当前节点编号和下一个节点:
(define (make-node n next) (cons n next))
(define (set
-next-node! node next) (set-cdr! node next))
(define (set
-node-number! node n) (set-car! node n))
(define (get
-number node) (car node))
(define (get
-next-node node) (cdr node))

    有了节点,实现了下单向循环链表:
(define (make-cycle-list n)
  (let ((head (make
-node 1 '())))
    (define (make-list current i)
      (let ((next
-node (make-node (+ i 1'())))
        (cond ((= i n) current)
              (
else
                (set
-next-node! current next-node)
                (make
-list next-node (+ i 1))))))
    (set
-next-node! (make-list head 1) head) 
    head))

    make-cycle-list生成一个有N个元素的环形链表,比如(make-cycle-list 8)的结果如下
#0=(1 2 3 4 5 6 7 8 . #0#)
    Drscheme形象地展示了这是一个循环的链表。那么约瑟夫环的问题就简单了:
(define (josephus-cycle n m)
  (let ((head (make
-cycle-list n)))
    (define (josephus
-iter prev current i)
      (let ((next
-node (get-next-node current)))
       (cond ((eq? next
-node current) (get-number current))
             ((
= 1 i)
              (set
-next-node! prev next-node)
              (josephus
-iter prev next-node m))
             (
else
               (josephus
-iter current next-node (- i 1))))))
    (josephus
-iter head head m)))

    从head节点开始计数,每到m,就将当前节点删除(通过将前一个节点的next-node设置为current的下一个节点),最后剩下的节点的编号就是答案。

posted @ 2008-04-16 10:27 dennis 阅读(617) | 评论 (0)编辑 收藏

《王阳明大传》序,作者:周月亮

一生极重践履的阳明,本身就象只鞋。这只鞋上插着高贵的权力意志的权杖。形成心学的倒T字型结构——不是十字架,也不是钻不出地平线的大众的正T字型。他的“致良知”工夫就是要你站在地平线上。然后脚不离地的无限的向上升华,把人拉成顶天立地的大写的人。

拔着头发离地球的是阿Q,当缩头乌龟的是假洋鬼子,只是鞋而无权杖的是读书没有悟道的士子。只耍权杖而不愿当鞋的是政治流氓——那个意志不是高贵的权力意志,只是反人道的独裁欲望。

阳明的心学是这样一种生活方式:既生活在这里,又生活在别处!

《明 史》阳明本传只附了一个学生,既因为别的成了气候的学生都有传,还因为这个学生最能体现阳明学的“鞋”精神,他叫冀元亨,他因去过宁王府而被当成阳明通宁 王的证据给抓起来,在锦衣卫的监狱里受百般折磨,但他对人依然象春风一样,感动得狱吏和狱友一个劲的哭,他把坐大狱当成了上学堂。所有的司法人员都以为 奇,问他夫人:“你丈夫秉持什么学术?”她说:“我丈夫的学问不出阃帏之间”。闻者皆惊愕不已。

但是,人皆在阃帏之间,谁有这种境界、风范?只生活在这里,反而得不到这里;单生活在别处,自然更得不到这里。

先作只鞋,再插上权杖,也不是阳明学的精神。那就是把鞋的大地性当成了手段,断断成不了圣雄,只能成为枭雄。

再高贵的鞋,也是踩在脚下;但路也正在脚下。不能生活在别处的人的所谓脚下之路,只是不得不走的路;有生活在别处之权力意志的人才能“践履”在希望的道路上。

在比做什么事成什么人更哲学的语义上说:穿什么鞋走什么路。

阳明这只鞋,至少有亲在性、超越性、诗性、葆真性、有应必变的践履性.....许多人最大的痛苦就是找不到一只合脚的鞋。阳明这只鞋可以叫真、善、忍;可以叫真、智、乐,叫六通四辟...

致良知,就是要你找到可以上路的合脚的鞋。致者,找也。能否找到呢?就看你肯不肯去找——因为,它就在你自身「心即理」。阳明这样解释孔子说的上智下愚不移——不是不能移,只是不肯移。

说无路可走的人,是没有握住自家的权杖,把生命的舵送给了别人——那人哪怕是上帝也会变成魔鬼——上帝的真诚包含着上帝的欺骗。

心学或曰阳明学并不给世人提供任何现成或统一的鞋,如果有那种鞋就是枷锁和桎铐了,心学只是告诉人们:每个人都能找到自己的那双合脚的上天堂的鞋——找这双鞋的工夫与上天堂的工夫是同一个工夫。

路在脚下,鞋在心中。你的任务是找与走,走着找,找着走,边找边走...

这样边找边走,就凸现出权杖的“权道”来——已发生语义转换,这个权道的“权”是秤砣、以及因此衍生的权衡、权宜的那个权。对于人心来说,权,就是“感应之几”,“几”就是微妙的恰好,象秤砣一样随被秤之物的轻重而变动,找到那个应该的恰好。所谓道, 就是“体乎物之中以生天下之用者也”「王夫之《周易外传》卷一」。权道就是追求“时中”即永远恰当的人间至道。约略等于具体问题具体分析这个马克思主义的活的灵魂。

没有这个权道,权杖只是个摆设,有了这个权道,权杖才能变成如意金箍棒,草鞋才能变成船,驶向理想的港湾。通权达变,是孔子认可的最高境界。不能通权达变就只能刻舟求剑、守株待兔...儒学在近代陷入困境就因为秉政的儒臣们失去了权道。

这个权道就是在践履精神上加上权变智慧——绝对不是无标准的变色龙、流氓。一讲权变就滑向流氓,为杜绝流氓就割断权道,都是找不到权道、反权道的表现。权,这个衡量万物的标准,用阳明的话说就是良知。良知在你心中,不用到别处去找。

所以,阳明这只鞋还带着秤砣,是风铃也是驼铃。

posted @ 2008-04-14 14:00 dennis 阅读(501) | 评论 (0)编辑 收藏

4

    Tablelua的主要——实际上,也是唯一的——数据结构。Table不仅在语言中,同时也在语言的实现中扮演着重要角色。Effort spent on a good implementation of tables is rewarded in the language,because tables are used for several internal tasks, with no qualms about performance。这有助于保持实现的小巧。相反的,任何其他数据结构的机制的缺乏也为table的高效实现带来了很大压力

       Lua中的table是关联数组,也就是可以用任何值做索引(除了nil),也可以持有任何值。另外,table是动态的,也就是说当加进数据的时候它们将增长(给迄今不存在的域赋值)而移除数据的时候将萎缩(给域赋nil值)。

不像大多数其他脚本语言,lua没有数组类型。数组被表示为以整数做键的table。用table作为数组对于语言是有益的。主要的(益处)显而易见:lua并不需要操纵表和数组的两套不同的运算符。另外,程序员不用对两种实现做出艰难选择。在lua中实现稀疏数组是轻而易举的。例如,在Perl里面,如果你尝试去跑$a[1000000000]=1 这样的程序,你能跑出个内存溢出!(you can run out of memory),因为它触发了一个10亿个元素的数组的创建(译注,Perl的哲学是:去除不必要的限制)。而等价的lua程序 a={[1000000000]=1},(只是)创建了有一个单独的项的表(而已)。


直到lua 4.0table都是作为hash表严格地实现:所有的pair都被显式地保存。Lua5.0引入了一个新算法来优化作为数组的table:它将以整数为键的pair不再是存储键而是优化成存储值在真正的数组中。更精确地说,在lua 5.0中,table被实现为一个混合数据结构:它们包括一个hash部分和一个数组部分。图2展示了一个有"x" 9.3, 1 100,2 200, 3 300对子的表的一种可能结构。注意到数组部分在右边,并没有存储整数的key。这个划分仅仅是在一个低的实现层次进行的,访问table仍然是透明的,甚至在虚拟机内部(访问table)也是如此。Table根据内容自动并且动态地对两个部分进行适配:数组部分试图从1n的相应地存储值,关联非整数键或者整数键超过数组范围的值被存储在hash部分。

当一个table需要增长时,lua重新计算hash部分和数组部分的大小。任一部分都可能是空的。重新计算后的数组大小是至少是当前使用的数组部分从1n的一半情况下的最大n值(原文:The computed size of the array part is the largest n such that at least half the slots between 1 and n are in use)(稀疏数组时避免浪费空间),并至少有一个(元素)处在n/2+1n的槽中(当n2整除时,避免n的这样的数组大小)。计算完大小后,lua创建了“新”的部分并将“旧”的部分的元素重新插入到的“新”的部分。作为一个例子,假设a是一个空表;它的数组部分和hash部分的大小都是0。当我们执行a[1]=v时,这个表需要增长到足够容纳新的键。Lua将为新的数组部分的大小选择n=1(带有一个项1v)。hash部分仍然保持为空。

这个混合的方案有两个优点。首先,访问以整数为键的值加快了,因为不再需要任何的散列行为。其次,也是最重要的,数组部分占用的内存大概是将数组部分存储在hash部分时的一半,因为key在数组中是隐式的(译注:也就是数组的下标)而在hash部分却是显式的。因而,当一个table被用作数组的时,它表现的就像一个数组,只要整数键是密集。另外,不用为hash部分付出任何内存和时间上的惩罚,因为它(译注:hash部分)甚至都不存在。相反的控制:如果table被用作关联数组而非一个数组,数组部分可能就是空的。这些内存上的节省是比较重要的,因为lua程序经常创建一些小的table,例如table被用来实现对象(Object)(译注,也就是用table来模仿对象,有点类似javascript中的json)。

Hash部分采用Brent's variation[3]的组合的链状发散表。这些table的主要不变式是如果一个元素没有在它的主要位置上(也就是hash值的原始位置),则冲突的元素在它自己的主要位置上。换句话说,仅当两个元素有相同的主要位置(也就是在当时table大小情况下的hash值)时才有冲突的。没有二级冲突。正因为那样,这些table的加载因子可以是100%而没有性能上的损失。这部分不是很明白,附上原文:

The hash part uses a mix of chained scatter table with Brent's variation [3].

A main invariant of these tables is that if an element is not in its main position

(i.e., the original position given by its hash value), then the colliding element

is in its own main position. In other words, there are collisions only when two

elements have the same main position (i.e., the same hash values for that table

size). There are no secondary collisions. Because of that, the load factor of these

tables can be 100% without performance penalties.

 

 

5、函数和闭包

lua编译一个函数的时候,它产生一个模型(prototype),包括了函数的虚拟机指令、常量值(数字,字符串字面量等)和一些调试信息。运行的时候,无论何时lua执行一个function…end表达式,它都创建一个新的闭包。每个闭包都有一个引用指向相应的模型,一个引用指向环境(environment)(一张查找全局变量的表,译注:指所谓环境就是这样一张表),和一组用来访问外部local变量的指向upvalue的引用。

词法范围以及first-class函数的组合导致一个关于(如何)访问外部local变量的著名难题。考虑图3中的例子。当add2被调用的时候,它的函数体(body)部分引用了外部local变量x (函数参数在lua里是local变量,译注:x就是所谓的自由变量,这里形成了闭包)。尽管如此,当add2被调用时,生成add2的函数add已经返回。如果在栈中生成变量x,(x在栈的)其栈槽将不复存在。(译注,此处的意思应该是说如果在栈保存变量x,那么在调用add2的时候,add函数早已经返回,x也在add2调用前就不在栈里头了,这就是那个著名难题)。


 

 

大多数过程语言通过限制词法范围(例如python),不提供first-class函数(例如Pascal),或者都两者都采用(例如c,译注:也就是说c既不把函数当一等公民,也限制词法范围)来回避这个问题。函数式语言就没有那些限制。围绕着非纯粹函数语言比如SchemeML的研究已经产生了大量的关于闭包的编译技术的知识(参见[19, 1, 21])。尽管如此,这些工作并没有尽力去限制编译器的复杂性。以优化的Scheme编译器Bigloo的控制流分


析阶段为例,(它的实现)比lua实现的10倍还大:Bigloo 2.6fCfa模块源码有106,350 VS. 10,155行的lua5.0核心。正如第2部分已经解释过的(原因),lua需要简单。

Lua使用了一个称为upvalue的结构来实现闭包。任何外部local变量的访问都是通过一个upvalue间接进行的。Upvalue初始指向的是变量存活位置的栈槽(参见图4的左半部分)。当变量(x)已经离开作用域(译注,也就是这里的add函数返回时),它就迁移到upvalue结构本身一个槽中(参见图4的右半部分)。因为(对变量的)访问是间接地通过upvalue结构中的一个指针进行的,因此这个迁移对于任何写或者读该变量的代码都是透明的。不像它的内嵌函数(译注:例子的add2,它是指外部函数add),声明变量的函数访问该变量就像访问它的local变量一样:直接到栈。

通过每个变量最多创建一个upvalue结构并且在必要的时候复用它们,可变状态得以在闭包之间正确地共享。为了保证这一约束,lua维持一个链表,里面是一个栈里(图4中的pending vars列表)的所有open upvalue(所谓open upvalue,是指仍然指向栈的upvalue结构)。当lua创建一个新的闭包的时候,它遍历所有的外部local变量。对于每个(外部local)变量,如果它在列表中找到一个open upvalue,那么它就复用这个upvalue结构。否则,lua就创建一个新的upvalue并将它放入链表。注意到列表搜索只是探测了少数几个节点,因为列表最多包含一个被内嵌函数使用的local变量的项。当一个closed upvalue不再被任何闭包引用的时候,它最后将被当作垃圾并回收。

一个函数允许访问一个不是它的直接外围函数的外部local变量,只要(这个local变量)是外部函数的(outer function)。在那种情况下,甚至在闭包被创建的时候,(外部local)变量可能就不在栈里了。Lua使用扁平闭包(flat closures)解决这种情况[5]。通过扁平闭包,一个函数无论何时去访问一个不属于它的外围函数的外部变量,这个变量都将进入外围函数的闭包。从而当一个函数被实例化的时候,所有进入它闭包的变量要么在外围函数的栈里面,要么在外围函数的闭包里。

posted @ 2008-04-07 17:55 dennis 阅读(3107) | 评论 (0)编辑 收藏

三个多月前翻译的,今天又找出来看看,后面的待整理下继续发,有错误的地方请不吝赐教。

原文:http://www.tecgraf.puc-rio.br/~lhf/ftp/doc/jucs05.pdf

翻译:dennis zhuang (killme2008@gmail.com)  http://www.blogjava.net/killme2008

转载请注明出处,谢谢。

 

摘要:我们讨论了lua 5.0实现的主要新特性:基于寄存器的虚拟机,优化表的新算法以便(将表)用作数组,闭包的实现,以及coroutines(译注:协程)

关键字: compilers, virtual machines, hash tables, closures, coroutines

 

1.    介绍

Lua作为内部使用的开发工具诞生于学术实验室中,现在却已经被世界范围内许多工业级项目所采用,广泛应用于游戏领域。Lua为什么能获得这样广泛的应用呢?我们认为答案就来源于我们的设计和实现目标上:提供一种简单、高效、可移植和轻量级的嵌入式脚本语言。这是Lua1993年诞生以来我们一直追求的目标,并在(语言的)演化过程中遵守。这些特性,以及Lua一开始就被设计成嵌入大型应用的事实,才使它在早期被工业界所接受。

 

广泛的应用产生了对(新的)语言的特性的需求。Lua的许多特性来自于工业需求和用户反馈的推动。重要的例如lua 5.0引入的coroutines和即将到来的Lua 5.1改进的垃圾收集实现,这些特性对于游戏(编程)特别重要。

 

在这篇论文中,我们讨论了lua 5.0相比于lua 4.0的主要新特性:

基于寄存器的的虚拟机:传统上,绝大多数虚拟机的实际执行都是基于栈,这个趋势开始于PascalPmachine,延续到今天的java虚拟机和微软的.net环境。目前,尽管对于基于寄存器的虚拟机的兴趣逐渐增多(比如Perl6计划中的新的虚拟机(Parrot)将是基于寄存器的),但是就我们所知,lua 5.0是第一个被广泛使用的基于寄存器的虚拟机。我们将在第7部分描述这个虚拟机。

 

优化表的新算法以便作为数组: 不像其他脚本语言,Lua并没有提供数组类型。Lua使用整数索引的普通表来实现数组作为替代。Lua 5.0使用了一个新的算法,可以检测表是否被作为数组使用,并且可以自动将关联着数字索引的值存储进一个真实的数组,而不是将它们放进Hash表。在第4部分我们将讨论这个算法。

 

闭包的实现:lua 5.0在词法层次上支持first-class 函数(译注:将函数作为一等公民)。这个机制导致一个著名的语言难题:使用基于数组的栈来存储激活记录。Lua 使用了一个新办法来实现函数闭包,保存局部变量在(基于数组)的栈(stack)上,当它们被内嵌函数引用而从作用域逸出的时候才将它们转移到堆(heap)上。闭包的实现将在第5部分讨论。

添加coroutines lua 5.0语言引入了coroutines。尽管coroutines的实现较为传统,但为了完整性我们将在第6部分做个简短的概况介绍。

 

其他部分是为了讨论的完整性或者提供背景资料。在第2部分我们介绍了lua的设计目标以及这个目标如何驱动实现的概况。在第3部分我们介绍了lua是如何表示值的。尽管就这个过程本身没有什么新意,但是为了(理解)其他部分我们需要这些资料。最后,在第8部分,我们介绍了一个小型的基准测试来得到一些结论。

 

2.    lua设计和实现概况

在介绍部分提到过的,lua实现的主要目标是:

 

简单性:我们探索我们能提供的最简单的语言,以及实现(这样的)语言的最简单的C代码。这就意味着(需要)不会偏离传统很远的拥有很少语言结构的简单语法。

 

效率:我们探索编译和执行lua程序的最快方法,这就意味着(需要)一个高效的、聪明的一遍扫描编译器和一个高效的虚拟机。

 

可移植性:我们希望lua能跑在尽可能多的平台上。我们希望能在任何地方不用修改地编译lua核心,在任何一个带有合适的lua解释器的平台上不用修改地运行lua程序。这就意味着一个对可移植性特别关注的干净的ANSI C的实现,例如避开C和标准库库中的陷阱缺陷,并确保能以c++方式干净地编译。我们追求warning-free的编译(实现)。

 

嵌入性:lua是一门扩展语言,它被设计用来为大型程序提供脚本设施。这个以及其他目标就意味着一个简单并且强大的C API实现,但这样将更多地依赖内建的C类型。

 

嵌入的低成本:我们希望能容易地将Lua添加进一个应用,而不会使应用变的臃肿。这就意味着(需要)紧凑的C代码和一个小的Lua核心,扩展将作为用户库来添加。

 

这些目标是有所权衡的。例如,lua经常被用作数据描述语言,用于保存和加载文件,有时是非常大的数据库(M字节的lua程序不常见)。这就意味着我们需要一个快速的lua编译器。另一方面,我们想让lua程序运行快速,这就意味着(需要)一个可以为虚拟机产生优秀代码的聪明的编译器。因此,LUA编译器的实现必须在这两种需求中寻找平衡。尽管如此,编译器还是不能太大,否则将使整个发行包变的臃肿。目前编译器大约占lua核心大小的30%。在内存受限的应用中,比如嵌入式系统,嵌入不带有编译器的Lua是可能的,Lua程序将被离线预编译,然后被一个小模块(这个小模块也是快速的,因为它加载的是二进制文件)在运行时加载。

 

Lua使用了一个手写的扫描器和一个手写的递归下降解释器。直到3.0版本,lua还在使用一个YACC产生的解释器,这在语言的语法不够稳定的时候很有价值的。然而,手写的解释器更小、更高效、更轻便以及完全可重入,也能提供更好的出错信息(error message)。

Lua编译器没有使用中间代码表示(译注:也就是不生成中间代码)。当解释一个程序的时候,它以“on-the-fly”的方式给虚拟机发出指令。不过,它会进行一些优化。例如,它会推迟像变量和常量这样的基本表达式的代码生成。当它解释这样的表达式的时候,没有产生任何代码,而是使用一种简单的结构来表示它们。所以,判断一个给定指令的操作数是常量还是变量以及将它们的值直接应用在指令都变的非常容易,避免了不必要的和昂贵的移动。

为了轻便地在许许多多不同的C编译器和平台之间移植,Lua不能使用许多解释器通常使用的技巧,例如direct threaded code [8, 16]。作为替代,它(译注:指lua解释器)使用了标准的while-switch分发循环。此处的C代码看起来过于复杂,但是复杂性也是为了确保可移植性。当lua在许多不同的平台上(包括64位平台和一些16位平台)被很多不同的C编译器编译,lua实现的可移植性一直以来变的越来越稳定了。

我们认为我们已经达到我们的设计和实现目标了。Lua是一门非常轻便的语言,它能跑在任何一个带有ANSI C编译器的平台上,从嵌入式系统到大型机。Lua确实是轻量级的:例如,它在linux平台上的独立解释器包括所有的标准库,占用的空间小于150K;核心更是小于100Klua是高效的:独立的基准测试表明lua是脚本语言(解释的、动态类型的语言)领域中最快的语言之一。主观上我们也认为lua是一门简单的语言,语法上类似Pascal,语义上类似Scheme(译注:Lisp的一种方言)。

 

3、值的表示

Lua是动态类型语言:类型依附于值而不是变量。Lua8种基本类型:nil, boolean, number, string, table, function,userdata, threadNil是一个标记类型,它只拥有一个值也叫nilBoolean就是通常的truefalseNumber是双精度浮点数,对应于C语言中的double类型,但用float或者long作为替代来编译lua也很容易(不少游戏consoles或者小机器都缺乏对double的硬件支持)String是有显式大小的字节数组,因此可以存储任意的二进制类型,包括嵌入零。Table类型就是关联的数组,可以用任何值做索引(除了nil),也可以持有任何值。Function是依据与lua虚拟机连接的协议编写的lua函数或者C函数。Userdata本质上是指向用户内存区块(user memory block)的指针,有两种风格:重量级,lua分配块并由GC回收;轻量级,由用户分配和释放(内存)块。最后,thread表示coroutines。所有类型的值都是first-class值:我们可以将它们作为全局变量、局部变量和table的域来存储,作为参数传递给函数,作为函数的返回值等等。

 

typedef struct {                        typedef union {

int t;                                      GCObject *gc;

Value v;                                   void *p;

} TObject;                                 lua_Number n;

int b;

} Value;

Figure 1: 带标签的union表示lua

 

 

Lua将值表示为带标签的union(tagged unions),也就是pairs(t,v),其中t是一个决定了值v类型的整数型标签,而v是一个实现了lua类型的C语言的union结构。Nil拥有一个单独的值(译注:也就是nil)。Booleansnumbers被实现为“拆箱式”的值:vunion中直接表示这些类型的值。这就意味着union(译注:指图中的Value)必须有足够的空间容纳double(类型)。Strings,tables, functions, threads, userdata类型的值通过引用来实现:v拥有指向实现这些类型的结构的指针。这些结构(译注:指实现Strings,tables, functions, threads, userdata这些类型的具体结构)共享一个共同的head,用来保存用于垃圾收集的信息。结构的剩下的部分专属于各自的类型。

 Figure1展示了一个实际的lua值的实现。TObject是这个实现的主要结构体:它表示了上文描述的带标签的联合体(tagged unions (t,v)Value是实现了值的union类型。类型nil的值没有显式表示在这个union类型中是因为标签已经足够标识它们。域n用来表示numbers类型(lua_Number默认是double类型)。同样,域b是给booleans类型用的,域p是给轻量级的userdata类型。而域gc是为会被垃圾回收的其他类型准备的((strings, tables, functions, 重量级userdata, threads)

 

使用带标签的union实现lua值的一个后果就是拷贝值的代价稍微昂贵了一点:在一台支持64double类型的32位机器上,TObject的大小是12字节(或者16字节,如果double8字节对齐的话),因此拷贝一个值将需要拷贝3(或者4)个机器字长。尽管如此,想在ANSI C中实现一个更好的值的表示是困难的。一些动态类型语言(例如Smalltalk80的原始实现)在每个指针中使用多余的位来存储值的类型标签。这个技巧在绝大多数机器上正常工作,这是因为一个指针的最后两个或者三个位由于对齐将总是0,所以可以被用作他途。但是,这项技术既不是可移植的也无法在ANSI C中实现,C 语言标准甚至都不保证指针适合任何整数类型,所以没有在指针上操作位的标准方法。

减小值大小的另一个观点就是持有显式标签,从而避免在union中放置一个double类型。例如,所有的number类型可以表示为堆分配的对象,就像String那样。(python使用了这项技术,除了预先分配了一些小的整数值)。尽管如此,这样的表示方法将使语言变的非常缓慢。作为选择,整数的值可以表示位“拆箱式”的值,直接存储在union中,而浮点值放在堆中。这个办法将极大地增加所有算术运算操作的实现复杂度。

类似早期的解释型语言,例如Snobol [11] Icon [10]lua在一个hash表中“拘留”(internalizes)字符串:(hash表)没有重复地持有每个字符串的单独拷贝。此外,String是不可变的:一个字符串一旦被“拘留”,将不能再被改变。字符串的哈希值依据一个混合了位和算术运算的简单表达式来计算,囊括所有的位。当字符串被“拘留”时,hash值保存(到hash表),以支持更快的字符串比较和表索引。如果字符串太长的话,hash函数并不会用到字符串的所有字节,这有利于快速地散列长字符串。避免处理长字符串带来的性能损失是重要的,因为(这样的操作)在lua中是很普遍的。例如,用lua处理文件的时候经常将整个文件内容作为一个单独的长字符串读入内存。

posted @ 2008-04-07 17:25 dennis 阅读(2694) | 评论 (2)编辑 收藏

    模块化的价值毋庸置疑。
    模块化代码的首要特质就是封装。封装良好的模块不会过多向外部披露自身的细节,不会直接调用其它模块的实现码,也不会胡乱共享全局数据。模块之间通过应用程序编程接口(API)——一组严密、定义良好的程序调用和数据结构来通信。这就是模块化原则的内容。API在模块间扮演双重角色。在实现层面,作为模块之间的滞塞点(choke point),阻止各自的内部细节被相邻模块知晓;在设计层面,正是API(而不是模块间的实现代码)真正定义了整个体系。
    模块的最佳模块大小,逻辑行在200到400之间,物理行在400到800之间为最佳。模块太小,几乎所有的复杂度都集中在接口,同样不利于理解,也就是透明性的欠缺。
   紧凑性就是一个特性能否装进人脑中的特性。理解紧凑性可以从它的“反面”来理解,紧凑性不等于“薄弱”,如果一个设计构建在易于理解且利于组合的抽象概念上,则这个系统能在具有非常强大、灵活的功能的同时保持紧凑。紧凑也不等同于“容易学习”:对于某些紧凑 设计而言,在掌握其精妙的内在基础概念模型之前,要理解这个设计相当困难;但一旦理解了这个概念模型,整个视角就会改变,紧凑的奥妙也就十分简单了。紧凑也不意味着“小巧”。即使一个设计良好的系统,对有经验的用户来说没什么特异之处、“一眼”就能看懂,但仍然可能包含很多部分。
    评测一个API紧凑性的经验法则是:API的入口点通常在7个左右,或者按《代码大全2》的说法,7+2和7-2的范围内。
    如果两个或更多事物中的一个发生变化,不会影响其他事物,这些事物就是正交的。每一个动作只改变一件事,不会影响其他(没有其他副作用)。举个例子,比如读取配置文件,获得系统设置信息这个方法:
module Config
   
def self.load_config
         config_str
=File.open("r","config.xml").read
         
#解析配置文件,可能转化成XML Dom处理等
         
   end
end
这个方法想当然地认为配置文件存储于磁盘文件中,然而配置文件完全是有可能通过网络获取的,也就是说文件句柄未必来源于磁盘文件,这个方法承担了两个职责:获取配置数据和解析配置数据。重构一下,以提高正交性:
module Config
   
def self.load_config(io)
         config_str
=io.read
         parse_config(config_str)         
   end
   private
   
def self.parse_config(config_str)
    
#解析
   end 
end
    重构技术中的很多坏味道,特别是重复代码,是违反正交性的明显例子,“重构的原则性目标就是提高正交性”。
    DRY(Don't Repeat Yourself)原则,意思是说:任何一个知识点在系统内都应当有一个唯一、明确、权威的表述。这个原则的另一种表述就是所谓SPOT原则(Single Point Of Truth)——真理的单点性。
    提高设计的紧凑性,有一个精妙但强大的方法,就是围绕“解决一个定义明确的问题”的强核心算法组织设计,避免臆断和捏造。

posted @ 2008-04-06 13:00 dennis 阅读(1568) | 评论 (0)编辑 收藏

                     无善无恶心之体,
                     有善有恶意之动。
                     知善知恶是良知,
                     为善去恶是格物。

这就是心学四诀。王阳明这样的人物, 还是当年明月评价的妙:
王守仁是一个高尚的人,一个纯粹的人,一个有道德的人,一个脱离了低级趣味的人,一个有益于人民的人。他是真正的圣贤,当之无愧

读《明朝那些事儿》到此,慢慢了解这么多不一般的人:铁铉、于谦、李贤、王守仁......,甚至可叹可笑可气的正德帝,在当年明月的笔下栩栩如生、有声有色。历史不仅仅充斥了阴谋诡计、权利倾轧,原来也有传奇的朱祁镇与钱皇后的感人爱情,有王守仁的龙场悟道,有俗套到不能俗套地不断上演火烧连环船,有正德帝这样的快乐皇帝,有李景隆这样难得一见的“人才”......不一样的读史,不一般的推荐。

posted @ 2008-04-03 18:37 dennis 阅读(864) | 评论 (4)编辑 收藏

    编程珠玑Column 4关于binary search的部分相当精彩,1946年就有人发表binary search,但直到1962第一个正确运行的算法才写出来。尽管算法描述看起来简单明了,但是要写的正确却是有许多地方要仔细考虑。作者着重强调的是对程序正确性的分析方法,仔细分析方法的pre-condition, invariant,和post-condition。g9老大翻译过Joshua Bloch在google blog上的文章《号外,号外,几乎所有的binary search和mergsort都有错》,原文在这里。今天看了下原文,有更新,对于求中值索引的c/c++方法原文仍然是有错的,本来是这样:

int mid = ((unsigned) (low + high)) >> 1

但是在c99标准中,对于两个signed数值之和,如果溢出结果是未定义的(undefined),也就是说与编译器实现相关。上面的代码应该修改为:

mid = ((unsigned int)low + (unsigned int)high)) >> 1;

我查了下jdk6的java.util.Arrays的源码,joshua bloch说的这个问题已经解决,现在的binary search如下:

  // Like public version, but without range checks.
    private static int binarySearch0(int[] a, int fromIndex, int toIndex,
                     
int key) {
    
int low = fromIndex;
    
int high = toIndex - 1;

    
while (low <= high) {
        
int mid = (low + high) >>> 1;
        
int midVal = a[mid];

        
if (midVal < key)
        low 
= mid + 1;
        
else if (midVal > key)
        high 
= mid - 1;
        
else
        
return mid; // key found
    }
    
return -(low + 1);  // key not found.
    }

    如原文所讨论的,采用了>>>操作符替代除以2

posted @ 2008-04-02 10:08 dennis 阅读(2006) | 评论 (2)编辑 收藏

    《代码大全2》刚出来的时候,铺天盖地的宣传,好像《程序员》上也做了整整一期的专题。不能免俗,我也是很早就买了。刚买来没有放进自己的读书计划,因为我的待读书单已经很长了。翻读过前面几章,对使用隐喻去理解软件开发一章印象深刻,还特意写了blog。然后这本书就被我放在了老家,直到春节后被我带到广州,读到昨天晚上,终于算是读完了。
    这本书正如作者所说,是一本关于软件构建的百科全书,小到一条注释语句的编写,大到高层设计和架构,从项目的需求分析到验收测试,从方法论到实践细节,从数据到案例,作者提供了全景式的软件构建视角,并且对每一个主题都做到能有价值地探讨。更可贵的是书中还提供了一张一张的核对表,让你能心中有数地去实践。
    感觉自己的语言是越来越贫乏了,呵呵,除了说好,好像不知道该怎么说了。看看过去在榕树下的写的那些“文章”,真不知道怎么就没有了过去的那种感觉了?难道做技术工作就真的变的越来越枯燥?工作倒没啥,一个游戏搞定了,马上又一个立项,开始慢慢轻车熟路,只是与我来广州的初衷相去甚远咯,俺还是暂时成了Ruby程序员了。
    补充一个关于西藏的资料链接 https://docs.google.com/View?docid=dggh5mp6_73fvdxt4c9,各方观点,精彩纷呈。屁股决定脑袋,至理名言呐。

posted @ 2008-03-31 18:27 dennis 阅读(653) | 评论 (1)编辑 收藏

    数据都是在我的机器上测试所得,我的机器配置:AMD athlon 64 x2 Dual 4000+ 2.11Ghz,1.87G内存。cruby版本是1.8.6,jruby是1.1RC3。操作系统是xp sp2。

1、将繁忙的循环放在内层,比如下面的代码:
a=
for i in 0..1000
  
for j in 0..10
     a
+=(i+j)
  end
end
替换成:
for j in 0..10
   
for i in 0..1000
    a
+=(i+j)
   end
end
cruby提升15%左右,而jruby提升30%以上。

2、乘法运算换成幂运算,cruby降低了200%以上,jruby仅降低30%。也就是说幂运算尽量换算成乘法运算。结论:幂运算换成乘法运算,乘法运算换成加法运算,多数情况下都能对性能有所提高,但请以实际测量为准。

3、100*2 替换成 100<<1,jruby提升8左右%,c ruby提升在0到3%左右。结论:乘以2或者除以2操作可以替换成位移操作,这个调整对性能提高有限,可能以降低代码可读性为代价。

4、字符串累积 a+="abc" 替换成 a<<"abc" c ruby提升接近100%,jruby提升97%

5、将case...when...end语句替换成if...elsif...end语句,cruby没有明显变化(甚至有所降低),而jruby却提高15%左右。同时,jruby的case...when...end语句的效率比cruby快上60%,if...elsif...end语句比cruby快上50%。结论:使用cruby,用case...when语句为好,而jruby则尽量使用if...elsif

6、将case语句中的频率比较高的分支提前,cruby提升15%左右,jruby也是如此。将if...elsif...end语句中的频率比较高的部分提前,jruby提升比较少,大概在5%左右,而cruby可以达到10%。。结论:尽管频率高的语句提前,可以适当提升性能,但可以看到也是有限的,分支语句的顺序不能仅仅考虑频率,更应该兼顾逻辑,维持在同一个抽象层次上。

7、任何一次代码调整,请都要测量一下,这些Tip仅仅是我在我的机器环境下的测试结果。这些调整策略对其他语言也大多有效,我是在读了《代码大全2》代码调整一章后做的测试,并应用到我的代码中了。

posted @ 2008-03-27 09:46 dennis 阅读(1807) | 评论 (2)编辑 收藏

为了我家小白和小红,特意查了下,原来养金鱼也是要讲究的:

1、注意放养方法

刚买回来的金鱼,切忌连鱼带水一起倒入鱼缸。因为刚买回来的鱼,在塑料袋中的水温与家中鱼缸中的水温不等,往往会使鱼“感冒”患病。有时买回来的鱼和水带有病菌,一旦倒入鱼缸中,反而连累其他鱼。所以,刚买回来的鱼应该先连同塑料袋一起在空缸水中放置20分钟,让袋内外水温达到一致时,再把鱼轻轻倒入空面盆中,适当加入少许等温新水,静止半小时,把鱼捞入盛有新水的空鱼缸中饲养。过一周后,未发现鱼儿有病,并已恢复正常活动时,再把鱼捞入饲养缸中合并饲养。

2、注意鱼缸消毒

鱼缸的消毒可用高锰酸钾溶液,以淡紫色高锰酸钾溶液浸泡一段时间即可。消毒以后再用清水清洗两遍。如果是用黏合剂黏结的玻璃鱼缸,最好放置一段时间后再用,因为化学黏结剂对金鱼有损伤。

3、注意光照

金鱼应在光亮、黑暗交替环境下生存。光照既可以增氧,又可以刺激鱼体表色素细胞,有利于加快变色和出现鲜艳光泽。

4、注意投喂方法

金鱼是观赏鱼,养鱼的目的不是要它很快长大,所以也要控制投食量,尤其是新买来以后,投食量过多往往把金鱼撑死。刚买回来的金鱼一时不适应新环境的改变,故开始的1~2天内不要给食,第三天才可给以少量食物,给食量只能逐渐增加,投食量不超过鱼体质量的1%。以后按此比例两天投食一次即可,切不要喂食量过多过勤。喂多了反到以下坏处:一是鱼吃饱了,代谢水平提高,耗氧量增加,容易引起缺氧窒息而死亡;二是饵料剩下,容易腐败发酵,使水质变坏,也会造成缺氧。其实金鱼是比较耐饿的,一两周不喂,也不会发生问题。

5、注意换水量及换水方法;

金鱼属冷血动物,能在0~39℃之间正常生存,最适宜温度是20~28℃。换水时温差不宜超过2℃,温差超过7℃时易得病,甚至死亡。另外,金鱼尤其是幼鱼比较娇嫩,不宜彻底换水。①夏季1~2天部分换水一次,10~15天全部换水一次,冬季一般不换水。②换水宜少不宜多(换水量为总量的1/4~1/3)。在换水时,一定要使用新(优)水,水温差不宜过大,若用自来水,使用前必须凉晒1~2天再用,或每立方米水体中加入2~3克大苏打,以中和水体中的氯离子。若用井水,用浅井水养金鱼效果更好,但使用前也须1~2天再使用。若用江、河、湖水,一定是未污染的,可直接用来养金鱼,但水中腐殖质及寄生虫容易危害金鱼,因而经过滤后使用较安全。

换水的方法:宜在傍晚四、五点钟进行,用乳胶吸管轻轻吸去缸底的粪便和剩余的饲料等污物和部分老水,兑入等温新水,在注入新水时,应按量慢慢地沿缸壁注入,切忌猛倒、猛冲,更不要冲及鱼体,否则容易使金鱼着凉内伤,引发烂鳞病和毛细血管充血等疾病。等鱼适应环境后,再转入正常饲养管理。

posted @ 2008-03-26 15:18 dennis 阅读(7832) | 评论 (5)编辑 收藏

仅列出标题
共56页: First 上一页 27 28 29 30 31 32 33 34 35 下一页 Last