——————————————————————————————————
本来只是做个简短的评述,无意中发现竟然默认作为一篇新的日志,为了保证日志的质量,同时也是在看了一部分后,记录心得体会。
事实上,我是先知道作者-云风大侠后,才在他的日志上发现还有专著面世。于是立即奔往图书馆借了一本,尽管此书由于多人翻阅,上面笔记画线很多,由于书页较宽,空白较多,于是读者也不吝笔墨,评语良莠不齐,有的顶礼膜拜,大呼“NB,爽!”,有的直接是“呵呵”,“太强了”等语,如云风提到(遗传算法中)杂交子代的先进性如杂交水稻,反之,如狗便是纯种的好,立刻有人呼应:“马也是啊!纯血赛马”,实在是让人哭笑不得。不过能在一本书上见到这么多的评语确实少见,看来云风前辈确实有人缘。
云风是工大毕业的,没错,是我们的校友。想来有一位这么优秀的校友,难得这本书在本校的图书馆这么受青睐。不过说起来,似乎云大侠对母校的感情很复杂,一方面,在学校一直在做自己喜欢的事情,尽管中间有些波折,倒是最终皆大欢喜。另一方面,进入不喜欢的专业学习,而在另一学科取得成就实在是一种讽刺。
那么回归这本书,《游戏之旅-我的编程感悟》:从书名就可以看出作者的侧重点,作者试图通过对自幼开发游戏体验游戏的历程,来向 读者 畅谈自己的编程感悟,感悟是多方面的,有编程技巧,有对计算机学科的感悟,有对游戏的认识,。。等等。
尽管作为一名计算机专业的研究生,学了这么多的基础课程,似乎也读了一些的“名著”,但是自己在读这本书的时候,仍有诸多的收获和新的认识。尽管云风不是计算机科班出身,但是执着的思考,出众的领悟能力,以及锲而不舍的实践,显然让诸多科班惭愧。更何况,计算机科学重实践呢。
总结一下,读到至今比较深刻的认识:
YF. 编程的基本功,光靠理解是无法随手写出正确、清晰的代码的。
ME:按着书中的脉络,对此作出解释是:所谓“理解”其实是建立在 人脑(模型)上的,编程是建立在 计算机模型上的。造成理解写不出好代码的本质原因就在于,人和 计算机模型(有限状态机,下推自动机,图灵机) 存在鸿沟,程序员的任务就是尽量填补这道鸿沟,或者更好的联系两方。
ps: 其实这个 计算模型 包含的意义并不只是上述的计算机的数学模型,在程序语言的设计中,也有自己的不同计算模型,例如过程式计算机模型,函数式计算模型,环境模型,同时,在算法设计中也可以存在多种计算机模型,如量子计算机模型,非确定的图灵机模型,这些模型都将直接的决定算法的时间复杂度和设计,参见SICP和计算理论方面的教材。
2011-4-23
YF. 用空间换时间的做法在游戏中用得非常广泛,也就是预处理的方法;而用时间换空间的做法也不能忽视,它多采用重复计算的方式实现。
ME:云风的书其实反复的强调 空间与时间的对立,类似的话可以在很多章节找到同义转述。可见 空间与时间 在程序实践上的主题。以前我可以轻松的举例说明:怎样用空间换时间,如快速傅里叶变换,动态规划;但是如何以时间换空间我似乎只能举出一个似乎牵强的例子,就是相较于宽度优先不断的记录状态,其空间复杂度约为搜索树的节点数,深度优先是一种时间换空间的做法,因为空间复杂度与搜索树的深度成正比 [2011-3-21 修改]。
关于上述的说明,可以参见《人工智能-一种现代方法》一书关于状态空间搜索的说明。对深度优先搜索和广度优先搜索的折中是迭代加深搜索方法,使之兼有两种搜索算法在时间和空间上的优势。这里简单的说一下,深度优先搜索和回溯法是由区别的,一方面有人认为,回溯是一种算法设计思想和技巧,而深度优先搜索只是回溯的一种实现方式。另则,有人认为回溯在搜索中不会保存完整搜索树,而只会标记一些搜索过的状态(其实多是隐式的回避搜索到重复状态的情况),而深度优先搜索是要记录搜索树的完整形态,因此,为了降低空间开销,很多时候我们也采用类似于回溯的思路,对树节点进行判重,而不是记录全部搜索树形态。
2011-4-23
YF. 通常的代码中,树结构一般都存在于大的程序框架上,是整个软件的支柱。
ME:这句话被某读者标记,但并没有写评语,说实话,缺乏大软件实践的我,对此却是没有见解,这里只作为一项记录,方便以后有认识再来补充。在文件系统中,往往是树结构[2011-3-21 补充]。
其实云风的话有些问题,树结构不仅用于大的软件系统,而是在很小的程序设计中,我们都隐式的使用树结构,如搜索算法的设计,递归。另则在查找和集合、字典等抽象数据结构的设计中,树结构也占据着重要的地位[参见TAOCP-6]。当然在大的软件中,如操作系统的设计中,树结构都有着很多的显式抑或隐式的应用,如进程树结构、文件系统的设计。其中B树就是实现数据库和外部存储上查找和检索的重要技术[同样参见TAOCP-6],在计算机世界的另一复杂软件系统:编译器中,树结构的应用就更为广泛了[参见龙书或任何一本编译原理和技术的教材]。
简单的说,树之所以优美,很大程度上因为其是一种无环的递归结构[待验证]。
2011-4-23
YF. 我曾经天真地认为,计算机需要解决的一切问题都可以用穷举搜索的方法来解决。搜索依然是计算机解决问题的最有利的利器。 因为任何问题都可以转化为若干个状态的连续集。我们将问题在某一时刻的情况做一个数学描述后,再归纳出问题从一个状态到另一个状态的转移操作。由计算机模拟这些状态转移过程,遍历问题的所有状态,找到从起始状态到目标状态的可行路径,就找到了问题的解。
ME:aha!我以前也执着的以为穷举是万能药,知道明白NP完全理论后,才发现当初的幼稚。不过话说,云风的书是类似一种个人笔记形式的著作,不是那些基础理论的教科书。我们也就没有必要对不严谨的地方吹毛求疵了。说实话,这里的问题的范畴是一种离散有穷状态问题,也就是计算机可解的问题。构造的状态描述和状态转移方程是有限状态自动机模型。不过,个人觉得 模拟 一词用的特别的好!搜索和计算机模拟统一起来了!很有意思。
不再赘述,请参考《人工智能-一种现代方法》一书,其关于搜索的讲解非常的细致。云风阐述的概念其实是一个未形式化的状态空间搜索概念。更为先进的搜索技术如A*、IDA*等等,可以在上述的书中找到。不过现在云风在游戏中的寻径中应经用过这些技术了,参见云风的个人主页:云风
2011-4-23
YF. 把算法的使用和特定的问题等同起来,对于游戏程序员,将是很危险的。
ME:这句话让我想起,在上计算理论课程有限自动机模型一章时,我傻乎乎的问老师,这课程怎么感觉和编译原理有点像。可想而知,授课老师极为不爽,大呼现在的学生一代不如一代。显然把理论和一项应用混为一谈是对理论的一种侮辱,呵呵。
YF. 稍有经验的程序员都能意识到算法优化对程序性能的重要性。经过近二十年的编程经历,我总结下来,无非是以下四点:
(1)数学方法的改进
(2)预运算来节省时间(空间换时间来避免重复运算)或是重复运算来节省空间
(3)简化算法求得近似解来取代精确解(或最优解)
(4)改进数据组织的方式,用更少的操作处理更多的数据,甚至避免冗余数据的处理。
逐点举例说明是很难得,因为每次成功的优化都会是各方面的。
ME:呵呵!这是我看到极有价值的总结和归纳,不过后面的一句话让云风省了很多赘述,倒是我大呼不过瘾。尽管在后文补充了一些例子,但是我在此试着举例:
对于第一条,我实在是觉得“所谓数学方法”有点过于笼统,我倾向于将第一条改为(1)分析问题的性质,避免冗余的运算(如上面说到的快速傅里叶变换、动态规划,但是显然这和(2)条有些重复,因为它们也是一种 空间换时间的方法)。于是,纠结之下,突然灵机一动:云风所言非虚。简单一例“数学优化”就是行列式求法,如果采用计算机利用拉普拉斯展式递归计算n维矩阵时,时间复杂度为O(n!),但是如采用高斯消元法,可将复杂度降至O(n^3),这就是经典的一个数学方法优化程序的例子。对于(2),我想我就不用举例了:时间与空间的矛盾。考虑(3)求取近似解的方法显然不一而足,如进化算法就是一例,当然似乎牵强。其实对于很多NP难问题,如TSP,如果考虑加入一些特殊的性质,是可以设计高效的解法的。另外,参数计算(引入特定的参数简化问题)和概率算法(加入概率,如经典的多次随机排序,保证概率意义上的求解出最优解,当然复杂度随之大大降低)也是可以归入这个范畴内的。第(4)条,一个简单的例子就是在MATLAB环境下矢量化代码,即尽量一次性处理须相同操作的数据(如大规模的向量和矩阵),这一点其实与云风的本职:游戏引擎开发总对图像的处理是一致的,而且我发现尽管MATLAB是一个更高级语言范畴,仍然在优化处理上和C和汇编有很多相通的地方,毕竟图片本身就是矩阵。
YF. Pacal想传达一种规则的结构化观念,让使用者潜移默化地接受这种观念;而C语言则想尽可能地贴近机器的固有模型,提供一种程序员更容易接受的描述工具。
ME:看到此句,茅厕顿开!我接触的语言似乎也不少,但是让我说出来这些语言的设计哲学和特点确实说不上来。如今被云风点拨,我便清醒的意识到,以前在使用语言上是多么的单纯,并没有注意到语言的设计指导思想。例如MATLAB的运算出发点就是便于矩阵运算,所以在编程熟练之后,我的编程思维也跟着变化起来,总想一次性处理很多规整的数据,而且善用MATLAB灵活的矩阵处理。导致在写C语言时总是感到处处受制,写个像样的字符串处理程序都成问题。说来,汇编其实是最贴近 图灵机模型(其实是一种基于栈的寄存器模型[2011-3-21 补充])的了(如此我对汇编又有点向往起来!当然机器语言就另当别论了),“了解汇编,也就能了解你的代码最终会以怎样的形态运行于CPU”
YF. 为了解决计算机固有模型对描述问题方式方面的一些缺陷,新的语言如雨后春笋般地诞生。
ME:在此映证上面的话,显然图灵机模型与人类思维是存在隔阂的。更不用说特定的人,如线性代数学家显然青睐于MATLAB。很多程序员的精力花在如何让计算机理解我们的初衷上,当然这也要求程序语言的表现能力要足够的强(比方说中文的表现能力就很强,可惜计算机不懂,而且中文的自然语言理解较英文显然是更难的),如后文中template就是一例。
YF. 在许多人的观念里,游戏中要用到的数据只管加载进来,反正物理内存不够了,OS自然会负责和外存交换,只需要保证程序的逻辑运行正常就行了。
ME:显然,这又是一例佐证,逻辑对了和成功有效的实现还很远。除了让计算机模型理解外,内存管理也是一个问题,内存无限只存在理想的图灵机模型中。所谓:程序设计并不单纯是算法设计,还有内存管理,垃圾回收等内容[2011-3-21 补充]。
事实上,内存管理并不只是一项技术活,TAOCP中有一独立的一节讲述动态内存管理[TAOCP-2.5]
YF. 为什么同样功能的代码会拥有不同的性能?这是因为在代码的不同层面,我们都可以有不同的模块组合方式,大到设计模式,小到一个局部函数的语句实现。在了解了完成事情的方法后,程序员做的就是怎样组合这一些东西,让他们联合工作的效率最高。这就好像搭积木一样,各种不同形状的积木可以搭配得紧凑,也可以松松散散。这个过程随着模块的个数的增加成天文数字的增长,不仅仅让最优化变得困难,连一大堆积木堆起来保证不塌掉都成了问题。这显然说明了,模块分得过细不利于软件开发,设计师往往寻求简单的途径将更小的积分先组合起来,做成更大的块再堆起来,这样条理清晰,不容易出错。
ME:一个很贴切的比喻,说到点子上了。正像网上的比喻 宏观上的组合以及微观的搭配 都是这个道理。也给我们的设计一个很好的建议。
不再赘述,以前已经提到过,这是一个关于自顶向下和自底向上的设计方法学的通俗论述,详细参见 代码大全2 以及 UNIX程序设计艺术
2011-4-23
YF. 如果理论上,让编译器从代码中了解到足够多的信息,使它能从足够多的信息中明白编程者想达到的目标,那么就一定能得到最优的结果(C++就是从这方面,利用template等特性实现了比C更高的编译质量)
ME:显然让计算机知道更多的信息是代码优化的前提。如果一门语言能让用户更直接更贴近他的意图编写代码,显然优化才更有可能,当然这就要求语言有足够的表现力。(template 是 图灵完备跟这个有关系么?待考 参考一下wiki吧)
其实,业已证明lambda演算的能力是等同于图灵机的能力的,template其实不是一个新鲜玩意,函数式程序设计(如Lisp,Haskell)已经发展了几十年了。[2011-3-21 补充],云风的潜在意味着,C++是传统的过程式语言C的进化版,代表的是一种局部变量副作用的过程式计算模型,而template代表的是一种函数式程序设计模型,自然造成程序设计的不同。详细参见SICP。[补充于 2011-4-23]
YF. C语言是对汇编语言的一种抽象,正如汇编语言是对机器底层指令是对机器底层指令的一个最小幅度的抽象一样,是为了人类更方便地控制机器。但是人在使用C语言控制机器时,并没有比汇编语言有观念上的不同,也就是说,程序员依旧是按机器模型来思考问题。在面对问题时,问题的描述往往并不符合机器思考的模型,程序员的工作也就是在两者之间搭上一座桥梁,让问题的描述可以使用机器思维来解决。 C语言程序员干的就是这种事情。
相异于机器模型的语言,通常对于特定的问题都有很好的解决方案,但是离开相应问题的领域,就会变得很难用。如lisp绝对不适合写游戏的图像引擎,即使对于lisp的爱好者,这也是不能否认的事实。
ME:说实话,一下子把程序员的本质给讲出来了。显然程序员似乎干的事情比算法设计师要 “低贱” 些,算法设计师保证设计出一个方案(当然这种方案一定建立在计算机可以干的事情范畴内,操作都是机器可以实现的,反之,“突然来了个灵感”“直觉怎样做”等等操作这个是机器无法做到的),这个方案往往是基于人类思维规律以及语言表述出来的。然后程序员的事就是把这个方案 翻译成机器的语言(或者介于人类语言和机器语言间的一个折中。啊,程序员原来就是一个翻译啊)。其实《编码的奥秘》一书讲的就很透彻,人类的自然语言以及程序语言都是一种编码。只不过机器能够接受的语言要有更多的限制和规则。而人类接受的语言似乎就更多而灵活,每个词的解释是依赖于环境的,换句话说,是可以是根据context变化语义的(即不是context-free的,称为context-sensitive),可以是很多的规则,甚至无规则的(诸多网络火星文,如果仔细看,似乎也是能理解的,但对于机器根本是匪夷所思的),呵呵,说的太抽象,太空了。不过仔细想一想,哥德尔的证明确实依据的就是编码,在当年一定是一件让很多人惊为天人的创举。
后面的一句话见《解决问题的原则》的问题特性原则,在此不多述。