日志分类:计算机科学与编程

一道 C 语言指针访存题目的引申

2009/11/08 | 13:41 | 分类:计算机科学与编程 | 标签: | 804次阅读

  毕业生求职的时节,非毕业生接触到各种面试、笔试题目的几率也会相应地增加。下面请看一道经典的 C 语言指针访存题目,稍有些经验的朋友应该很快可以看出这个题目考查的是字节序、内存布局等知识点。然后在大脑中略排列一下,就能够给出答案(2000000)。

  1. #include <stdio.h>
  2.  
  3. int main()
  4. {
  5.     int a[5] = {1, 2, 3, 4, 5};
  6.     int *pa = (int)(&a) + 1;
  7.     printf("%x\n", *pa);
  8.     return 0;
  9. }

  不过,这个答案是否绝对正确,还要看题目所处的上下文了。如果题目明确说是在常见的 32 位 x86 平台上运行,那就无可厚非;但如果没有指明机器架构,那就要小心一点了,也许命题者真想考查一下求职者对非 x86 平台的了解程度呢。如果考虑机器架构,这个题目应当如何作答呢?粗想一下,我们需要考虑的是字长、字节序和对齐(alignment)访问规则。不过真要做实验看看,会发现这里面还是有一些花样的。如果没有实际经验,只凭教条加推测,很可能想不到其它平台上的一些细节之处。
  我们换用一段信息量更丰富的程序来进行后续的实验。在不同的平台上,均使用未加特殊参数的 gcc 来编译这段程序——

  1. #include <stdio.h>
  2.  
  3. int main()
  4. {
  5.     int x;
  6.     int a[5] = {0x11121314, 0x21222324, 0x31323334, 0x41424344, 0x51525354};
  7.     for (x = 0; x < 20; x++) {
  8.         printf("%02x ", *(char *)((int)(&a) + x));
  9.     }
  10.     printf("\n");
  11.     for (x = 0; x < 8; x++) {
  12.         printf("%08x ", *(int *)((int)(&a) + x));
  13.     }
  14.     printf("\n");
  15.     return 0;
  16. }

  在 32 位 x86 下的结果不需要多解释。

  1. uname -a
  2. Linux ubuntu 2.6.31-14-generic #48-Ubuntu SMP Fri Oct 16 14:04:26 UTC 2009 i686 GNU/Linux
  3. ./a.out
  4. 14 13 12 11 24 23 22 21 34 33 32 31 44 43 42 41 54 53 52 51
  5. 11121314 24111213 23241112 22232411 21222324 34212223 33342122 32333421

  而在 64 位的 x86_64 下,由于 8 字节的指针被截断到了 4 字节的整型长度,故会引发段错误。同样的情况出现在 64 位的 Alpha 机器下。解决办法自然是把运算地址时的 int 修改成 long 或某种显式的 64 位类型。修改后的结果应该与 32 位 x86 一致。

  1. uname -a
  2. Linux ubuntu 2.6.24-22-generic #1 SMP Mon Nov 24 19:35:06 UTC 2008 x86_64 GNU/Linux
  3. ./a.out
  4. Segmentation fault
  1. uname -a
  2. NetBSD sdf 2.1.0_STABLE NetBSD 2.1.0_STABLE (sdf) #0: Fri Mar 30 02:24:32 UTC 2007  root@ol:/var/sys/arch/alpha/compile/sdf alpha
  3. ./a.out
  4. Memory fault (core dumped)

  有趣的是在 XScale(Intel 实现的 ARMv5)下,虽然同属 little-endian,但非对齐取数时出现了在字内按字节循环的移位的结果。查查 ARM 的官方文档,这确实是 ARMv5 的特性;而在 ARMv6 以后,非对齐访问则是完全支持的。

  1. uname -a
  2. Linux zaurus 2.4.18-rmk7-pxa3-embedix #1 Sat, 06 Aug 2005 12:22:55 +0000 armv5tel unknown
  3. ./a.out
  4. 14 13 12 11 24 23 22 21 34 33 32 31 44 43 42 41 54 53 52 51
  5. 11121314 14111213 13141112 12131411 21222324 24212223 23242122 22232421

  接下来看看 PowerPC,它是 big-endian 的代表,允许 32 位以内的非对齐访问,结果是容易理解的。有关 PowerPC 非对齐访问的一些细节可以参考这篇文章

  1. uname -a
  2. AIX aix 3 5 00C97AC04C00 powerpc unknown AIX
  3. ./a.out
  4. 11 12 13 14 21 22 23 24 31 32 33 34 41 42 43 44 51 52 53 54
  5. 11121314 12131421 13142122 14212223 21222324 22232431 23243132 24313233

  同样是 big-endium 的 SPARC 则不允许非对齐访问。它会对非对齐访问抛出 SIGBUS。

  1. uname -a
  2. SunOS t1000 5.10 Generic_118833-33 sun4v sparc SUNW,Sun-Fire-T1000 Solaris
  3. ./a.out
  4. 11 12 13 14 21 22 23 24 31 32 33 34 41 42 43 44 51 52 53 54
  5. Bus Error (core dumped)

  最后看看我们中科院计算所的龙芯(Loongson)2E,它是兼容 MIPS 架构的处理器。很多教科书告诉我们说通常的 MIPS 是不允许非对齐访问的(部分 MIPS 实现提供了非对齐访问指令,并申请了专利),但我们在龙芯下却得到了和 x86 相同的、允许非对齐访问的结果,这又是为什么呢?初步查到的原因是“(针对龙芯修改过的 Linux)内核里确实有一个异常处理函数负责处理 lw 访问非对齐地址引起的异常”。这也许是龙芯绕开 MIPS 专利的一种办法?我会向龙芯团队的同学求证一下,也希望熟悉 MIPS 或龙芯的朋友给我一个确切的答案。

  1. uname -a
  2. Linux Loongson-1 2.6.18.1lemote #1 Sat Jan 13 16:02:26 CST 2007 mips GNU/Linux
  3. ./a.out
  4. 14 13 12 11 24 23 22 21 34 33 32 31 44 43 42 41 54 53 52 51
  5. 11121314 24111213 23241112 22232411 21222324 34212223 33342122 32333421

  不过用心思考的朋友也许会发现上面一系列实验存在的一个疏漏:没有考虑编译器的影响。一方面,编译器可能对整型的字长有不同的规定(例如 Windows 下的某些编译器即使在 32 位 x86 上也会把 int 定义为 16 位);另一方面,编译器可以对不支持非对齐访问的处理器生成一定的指令序列、通过多次访存来模拟非对齐访问。我们看下面的例子:还是在 SPARC 平台上,改用 Solaris 自带的 Sun CC 来编译实验程序,这时就不会出现“Bus Error”,而会输出和 PowerPC 一样的结果。因为 SunCC 默认会使用“-xmemalign”参数来生成适当的访存指令序列。

  1. uname -a
  2. SunOS t1000 5.10 Generic_118833-33 sun4v sparc SUNW,Sun-Fire-T1000 Solaris
  3. cc data.c
  4. ./a.out
  5. 11 12 13 14 21 22 23 24 31 32 33 34 41 42 43 44 51 52 53 54
  6. 11121314 12131421 13142122 14212223 21222324 22232431 23243132 24313233

  这样看来,在不指定机器架构和编译器等上下文的情况下,要正确且完美地回答一开始的那道题目还是需要一定知识积累的。答案省略,留给大家自己求解。在面试、笔试诸如 Sun SPARC、IBM PowerPC、中科院计算所微处理器中心等部门或者做 ARM 等嵌入式开发的公司时,最好先了解清楚它们的产品常识。
  (部分实验环境来源于 Unix-Center.Net,在此致谢)

对“为人民计算”的几点思考

2009/06/30 | 23:30 | 分类:计算机科学与编程 | 标签: | 745次阅读

对“为人民计算”的几点思考

(本文为本人“先进计算机和软件技术系列讲座”课程作业)

 
  “为人民计算”(Computing for the Masses)是近年来计算机科学技术和产业发展的目标之一。而究竟什么是“为人民计算”,出于国情、技术水平和个人认识的差异,不少学者提出过不同的理解。我国人口众多,农村人口占56%(国家统计局2007年10月数据),城乡经济和区域发展尚不协调,正处于信息化带动工业化的发展时期。在这种历史条件下,如何使得信息化的成果真正惠及人民,实现计算机行业所谓“普惠计算”的目标,是业界出于其社会责任感有必要思考的一个问题。针对我的导师徐志伟研究员在“为人民计算”方面的提出的一些观点,我浅谈一下我对几个相关问题的思考。
对“为人民计算”的几点思考
 
  消费动机不在信息化本身
  在受益于信息化且面向大众服务的行业当中,近几年来增长较为显著的是移动通信产业。中国的移动电话用户数量由1999年底的4330万户发展到2009年中期的6.87亿户。城市手机用户已经趋于饱和,但运营商对农村移动通信市场仍有信心。移动通信之所以得到了市场的认可,与其明确的消费目的息息相关。绝大多数消费者购买手机,其基本动机就是为了享受通信的便利;对于相当一些职业的人士,实时的通信可能已经不是“方便”而是“必须”。对于农村新市场的开拓,其难点可能在经济方面,而不在于人民对手机的基本功能和作用的认知。类似的案例还有银行卡。截至2009年6月,全国累计发行银行卡18.88亿张,银行卡的主要用户也分布在城市,农村和欠发达地区受制于经济条件和消费意识,对银行卡产品也许有所误解,但这不能说明银行卡本身的定位有问题,至少它们的功能是相对单一和明确的,满足了人民群众的特定需求。
  然而,计算机与网络技术对人民大众来说,其直接的消费目的性并不如移动通信、银行卡那样显而易见。在目前阶段,计算机与网络更多地扮演着工具和桥梁的角色。购买计算机、接入互联网,并不能直接解决什么具体问题,计算机和网络解决的问题是通过运行在其上的应用体现出来的。“上网”、“获得信息”、“学习”都是一些相对抽象的动机,不能直接满足低端用户具体一项生活需求。因此在商业化推广中,这些空虚的目标可能不会对低端消费者产生太大的吸引力。即便通过财政支持来发展信息化,这些成果也很可能沦落为地方政绩的象征或闲人娱乐的工具,对生产生活不会有实质的影响。要在2040年建成惠及至少12亿人的信息化社会,我认为不能把民用计算机、网络定位为移动通信那种具有明显消费目的性的产品,而必须改变现有中低端计算机和网络相关产品或服务的社会定位,在应用层面寻找“为人民计算”的突破口。
  信息化被作为工具的现状令很多从业者,特别是计算机科学研究人员不满:业界不愿意让信息技术变成像楼宇保洁一样必不可少但几乎不能创造价值的服务业,学界不也甘心计算机科学停滞不前,最终被其它学科以工具和技术的名义吞并。上世纪末本世纪初,网格计算的理念曾十分风行,研究人员力图建立一种像电网一样即插即得的信息基础设施,但至今网格技术只在部分专业研究领域发挥了功效,而那种惠及全民的“Great Glocal Grid”还未见雏形。其中主要的原因,我认为是信息资源缺乏像电力资源那样的同质性,抛开技术层面的因素,难以同质化的信息本身就使得潜在的用户无法建立明确的消费动机。但信息的同质化正是一个值得研究的科学问题,这个问题的解决,将使得计算机、网络由独立的工具进化为信息基础设施的地位。我们需要这样的基础设施在社会的后台像电网一样默默无闻地“为人民计算”,而用来明确消费动机、创造商业价值、进而构建信息社会的,不是计算机和网络的实体,而是这一基础设施之上的、像家用电器一样的、但目前尚未被发明的具体应用。
 
  比“脱贫”更难的是“第二跳”
  目前我国大多数人口仍处于“信息贫困线”以下,“脱贫”是产业界、学术界和决策层正在着力解决的问题。“脱贫”的方向相对明确。对于产业界,主要需要考虑机器生产和网络运营的成本,让计算机和网络对所有人来说由奢侈品变成日用品;对于学术界,需要摒弃一味追求性能的老路,从功耗、易用性等贴近用户的角度考虑体系结构的设计;决策层则需要提供政策扶持,对相关产业采取优惠措施,对人民群众加以合理引导。但我认为有可能出现困难的,并不在于“脱贫”的“第一跳”,而在于由基本价值向商品价值提升的“第二跳”。
  还是以银行卡为例,我国银行卡发行数量虽然已经超过人口总量,但真正用卡的人群并不大,相当一部分银行卡是通过任务摊派的方式进入低端市场的,这些卡大都成了没有任何交易记录的“死卡”。即使是使用银行卡的人,他们中的多数也只享受了其商品价值,把它当作一张可以自助取款的存折,从未使用甚至从未听说过银行卡所关联的高级金融服务。
  纵观我国计算机网络用户,其层次分化较为明显。IT界的精英人士、具有探索精神的专业人士早已享受到了信息产业的泛在价值、专业价值乃至个性价值,他们甚至可以在软件开发商与网络运营商尚未提供相关服务的情况下发扬自主创新的DIY精神,主动地让信息化为自己创造价值。而目前数目最庞大的用户群是将计算机、网络作为工具的人群,他们虽然享受了信息化带来的基本价值和商品价值,但只是在工作和生活中为了特定的目的有意去使用计算机和网络,计算机和网络更多地作为间接、高效实现传统生产模式价值的途径,而不是直接为这些用户提供新的价值。在农村和欠发达地区也有一定数量的计算机、网络用户,网吧是他们接触信息化成果的主要场所。这些用户缺乏自主探索的意识,他们通常使用仅仅使用网吧预置的网站、软件与游戏。即使是这些地区的一些官员,对信息产业的理解也有可能过于狭隘,甚至可能因为未成年人沉迷网游等问题对计算机、网络建立负面的印象,进而抵制信息化进程。然而要达到“普惠计算”的目标,必须把这类人群作为未来市场的潜在用户。目前我国的信息化还没有像工业化那样彻底地改变大多数人的生活方式;在相当一部分地区,工业化的成果甚至还没有全面应用到生产中,在这些地区谈信息化的价值,确实不容易为当地政府和人民大众所接受。单纯的基础设施建设所带来的基本价值就像提供给毫无养殖经验的农户的良种牲畜,他们只能一吃了之,不懂得通过繁殖子代、获取副产品来增值。既然信息化带动工业化已经是中国特色的跨越式发展模式,我们就应该在“脱贫”的同时完成“第二跳”的工作,提供一系列能够直接服务于生产、生活的应用。只有完成“第二跳”,信息化才不再是软硬件堆叠的花架子,其价值才能使个人用户受益。如何让人民群众真正从“为人民计算”中得到实惠,让计算机科学的成果成为人民群众喜闻乐见的事物,而不是像银行卡市场那样徒有虚名,这才是比信息“脱贫”更重要的任务。
 
  对“计算透镜”半保守半超前的认识
  Richard M. Karp提出的“计算透镜”(Computational Lens)理念被认为是未来二十年计算机科学可能的发展方向之一。其核心理念是将计算作为一种通用的思维方式,通过这种广义的计算(涉及信息、执行算法、关注复杂度)来描述各类自然过程和社会过程,从而解决各个学科的问题。Karp给出了计算透镜理论在生物科学中的成功应用。这一理论试图将计算机科学由最初的数值计算工具、仿真与可视化技术以及后来基于网络、面向多学科的e-Science平台,变成普遍适用于自然和社会领域的通用思维模式。从这一点上看,计算透镜的思路也似乎是要将计算机从传统的“为军事计算”、“为科学计算”、“为商业计算”过渡到“为人民计算”、“为人类计算”,只是这一思维工具并不一定直接为人民所使用,而是要借助学界和业界之手将计算透镜带来的好处通过生产力的发展来施惠与人。
  世界真的可以用计算过程来表达吗?在初次接触到计算透镜的概念时,我首先想到的是近代机械唯物主义的自然观,试图用力学和机械的观点来解释并分析自然与社会现象。进而联想到了人工智能发展史上的符号主义、连接主义和行为主义等学派的基本观点,它们都力图用一种简单的形式化模型来解释智慧和思维。计算透镜是不是在走这些老路呢?我个人感觉,在图灵机模型和现行的可计算性理论本身是否符合自然规律尚未被证实的前提下,不要指望计算透镜能在短期内给一般大众的工作和生活带来什么影响。但对计算透镜的研究是有意义的,其意义可能不在于用计算改造自然,而在于更好地认识自然,从而改造现有的计算模型,使之能够像一些自然过程一样高效。也许到那个时候,人、机、物界限因为思维方式的统一而变得模糊,“为人民计算”的理念演化为对人类思维能力的直接扩展,届时作为中介的信息化工具、应用可能反而变得不重要了。
  回到现实,这方面已经存在的一些问题需要引起我们的关注。计算透镜理论中包含“社会计算”。尽管社会计算的概念目前还没有精确的定义,无论是用计算机的方法研究社会还是用社会学的方法研究计算机,统统被一些机构打上了“社会计算”的标签。从“社会”二字可以看出,这一计算的执行也并非人民的个体可以完成的,要做到社会计算,仍需要掌握着社会数据和社会决策权的机构来完成。但在法治完善程度赶不上技术发展速度的情况下,如何保证社会计算的成果真正服务于民,成为“为人民计算”的理念与实践需要面对的一个重要的非技术问题。例如当前舆情计算技术的发展确实为政府机关提供了迅速准确的决策支持,但一些社会团体对舆情计算中的个人隐私保护问题仍存在争议,认为这对网民存在侵权。舆情计算技术还被某些搜索引擎公司或网络公关公司所利用,通过推送正面信息、屏蔽负面评价的方式从不法商家手中谋利,直接侵害了消费者的知情权,损害了市场的公平竞争。从这一点可以看出,“为人民计算”不是一两个新理论、新技术就可以实现的,它需要完善的法治框架、良好的业界环境,以及官、产、学、研各方对人民利益高度负责的态度。
 
  总之,我心中的“为人民计算”应该是由有价值的应用所导向的、带来明确消费动机的、架构在信息基础设施之上的信息化发展模式。它应该像电网和电器一样易用,让人民受益的同时却感觉不到信息基础设施的存在;它应该突破城乡与区域差异,惠及全民,为信息化带动工业化带来历史契机;它应该兼顾可用性、保密性与可控性,在具有社会负责感的机构的运营下真正代表人民群众的利益。
 
参考文献
[1] 徐志伟.为人民计算的三个问题.中国计算机学会通讯,2008(10).
[2] 江泽民.新时期我国信息技术产业的发展.上海交通大学学报,2008(10).
[3] 李国杰.创新求索录.电子工业出版社,2008.
[4] Richard M. Karp. Computer Science as a Lens on the Sciences. ICDCS 2008.

《程序员的自我修养》是有学习价值的

2009/05/23 | 14:20 | 分类:计算机科学与编程 | 标签: | 1,644次阅读

  不久前收到了博文视点寄来的新书——《程序员的自我修养——链接、装载与库》。比较巧的是,我这段时间正在从事一个涉及多语言互操作的项目,那些天还在看一些有关C++语言设计和实现原理的文献,这本《程序员的自我修养》和我最近的工作还是多多少少有点联系的。
  有的人可能认为读博士的人不应该再去关注这些面向程序员、纠结于技术细节的书了,而要专心于自己的研究领域,做偏科学、偏理论的事。这是有道理的,博士教育和硕士教育的差异即在此,漂泊在茫无涯际的技术海洋很可能让本应该专注于一个方向的人不知所向。但是我们也要反思,自己的基础是否足够扎实?对计算机原理的理解是否超越了那些具体技术的层面?自己是否真的具备了从事科研的素质?我的导师在他的科普作品《电脑启示录》(中篇 硅谷的秘密)中曾提到作为计算机科研人员应该具备的基本素质,并例举了几个用来考察这些素质的问题,诸如:从计算机开机到操作系统等待用户输入,经历的一系列流程是什么?在浏览器里敲入一个网址到网页呈现给用户,期间又有哪些工作细节?这些过程看似稀疏平常,其中的大道理在本科计算机课程中也都或多或少地介绍过,然而要精确地表达每个步骤,更重要地是说明每个步骤为什么要这样设计、为什么会这样实现、其中的科学依据是什么,往往不是每个计算机专业毕业生都能说清楚的。这类问题常常能从侧面反映一个从业人员的理论功底及实践经验。《程序员的自我修养》所阐述的也正是同一类的问题:一个程序由硬盘上目标文件、可执行文件变成内存中的进程体、CPU中的指令流,整个过程的来龙去脉是什么,有什么原理、诀窍、讲究和因果联系。
  在计算机领域从事不同具体工作的科研、技术人员,静下心来分析一下这些平时被各种层面的接口掩盖了的机制是有好处的。初用VC++编程的时候,也许你会奇怪,监视窗口为什么要输出一串“烫”字;干Linux工程时,仅仅链接了几个标准库文件就把程序搞崩溃了,你会认为这是链接器或标准库的bug,还是自己没有弄明白它们的机理?如果你在计算机学科的其它方面有过一些细致的了解,你又会发现,像自举引导、延迟绑定等共性的方法,时间与空间转换、策略与机制分离等共性的原则在编译、链接、装载过程中也都有生动的体现。而即使你是做研究的,方向与程序原理相隔甚远,看看这些计算机领域内实现相对成熟、应用相对普遍的、原理相对通用的问题,对自己的工作也很有启发意义。
  尽管这本书有待市场的考验,但它研究的问题确属计算机学科中的经典。把理解链接、装载与库作为程序员的自我修养是否合适?我想,它算不上充分条件,不过确实是一个必要条件。不仅对程序员来说必要,对任何一个从事计算机研究与开发的人来说都是有价值的。

C和C++处理register关键字的一处差异

2009/04/19 | 23:08 | 分类:计算机科学与编程 | 标签: | 1,083次阅读

  C++并不是完全兼容C语言的,上次提到的sizeof('a')等于几的问题就是一例。今天我在编码时又无意中发现了一处不同:
  用register关键字修饰的变量,在C语言中是不可以用&操作符取地址的,这是我已有的经验。因为编译器如果接受了程序员的建议把变量存入寄存器,它是不存在虚拟地址的。但在C++中,用register修饰的变量可以用&操作符取地址,这是我在一段代码中发现的。如果程序中显式取了register变量的地址,编译器一定会将这个变量定义在内存中,而不会定义为寄存器变量。
  我在C99(ISO/IEC 9899:1999)和ISO C++(ISO/IEC 14882:2003)标准中得到了确认,C和C++标准对register遇到&的处理确实有不同的明确定义。但为什么要这样定义?我只能从标准的字里行间猜测。K&R C1中如何描述register我尚未查证,K&R C2(ANSI C)中说明了“register variables are to be placed in machine registers ... but compilers are free to ignore the advice ”。但在C99和ISO C++中,措辞分别变成:“suggests that access to the object be as fast as possible”、“a hint to the implementation that the object so declared will be heavily used”,不再特别提及“machine registers”。可见历史上register关键字在强调尽可能地把变量保存到寄存器,而现在的register关键字不再强调具体手段,只是建议编译器通过各种可行的方式优化该变量的访问(不过很多编译器会忽略这一关键字,而采用自身的优化策略)。C99可能是为了保持对K&R C的兼容而不允许取地址操作;而C++也许是因为没有历史包袱才放宽了这个限制吧。猜测而已,希望知道内幕的朋友告诉我更精确的答案。

对几项带有随机算法思想的工作的讨论

2009/03/28 | 11:38 | 分类:计算机科学与编程 | 标签: | 575次阅读

对几项带有随机算法思想的工作的讨论
(节选自上学期本人组内讨论课作业)

  在某些类型的数值计算问题中,我们常使用随机算法,如数值概率算法(得到近似解)、拉斯维加斯算法(不一定有解,如果有解一定正确)、蒙特卡罗算法(一定有解,但解不一定正确)、舍伍德算法(一定有解,解一定正确)。这些随机算法的原理基于概率模型,用简单的随机因子代替精确但复杂度高的最优化求解。对于数值概率算法,通过增加计算次数逼近精确解;对于后三类算法,通过增加计算次数提高得到正确解的概率,总体上都可以得到比确定性算法更低的平均时间复杂度。数值概率算法牺牲了解的精确性(Accuracy),但在很多场合下精确性不是首要要求;后三类算法有失去正确性(Correctness)的风险,但由于有办法将失败化为小概率事件,故在实际应用中利远大于弊。
  在计算机系统,特别是分布式系统的各类问题中,活性(Liveness)与安全性(Safety)往往是评价某种解决方案的关键因素,这类似于数值计算问题中的正确性,通常是不能含糊的,有必要做形式化证明。但这在实际中并不是绝对的。在本学期的讨论课上我们了解了不少工作,其中引入了概率模型,应用类似于随机算法的思想,牺牲某些情况下系统内部绝对的活性与安全性来换取性能的大幅提升,同时引入相关的机制将潜在的失败可控化,不影响系统对外的活性与安全性。这些工作包括:
  Exterminator:一种能动态修复C、C++程序堆内存错误的运行时库。基于统计规律,在多次运行时重新安排内存布局来定位错误,对错误位置分配额外的内存来降低再次出错的概率,使得原本有内存缺陷的程序可对外表现正常,同时便于开发人员快速定位错误。
  MetaTM/TxLinux:一类基于事务的内存管理模型。用允许并发访问临界区的事务管理算法代替严格的锁保护机制。同时检测临界区冲突,在发生冲突时回滚重试。它减少了系统同步操作中由严格的锁带来的性能瓶颈。
  Zyzzyva:一种拜占庭容错协议。一般情况下乐观地采纳主服务器对请求处理顺序的判断,省去了三阶段提交(3PC)协议。同时在客户端检测服务器副本可能的不一致,在发现问题时通知其更正。这种方案减少了3PC协议带来的开销。

● 我得到的一些启发
  这些工作中的随机算法思想
  Exterminator的做法带有数值概率算法的思想。设想一类能够精确地对运行时程序内存进行分析、预测和修复的机制,它应该覆盖所有可能的执行路径,并对有关变量的值域、边界条件等做细致分析,其复杂度有可能是指数级的。但Exterminator仅分析了特定的,也是实际工作中典型的执行路径,通过多次运行随机安排内存布局的实例来找出可以正常运行的实例,并利用内存布局的信息计算每一块内存分配可能引发错误的概率,复杂度下降到了多项式级。在一些基本假设下,作者给出了Exterminator对内存操作问题位置判定正确性概率的公式。Exterminator这样的设计满足了作者所谓 “automatically correcting memory errors with high probability”的需求。作为一种运行时的、治标不治本的纠错机制,使用随机算法很好地平衡了其功能和性能。它所找到的问题位置对机器来说精确度并不高,修补方案也很粗糙,但其分析结果在程序员来看足以发现错误原因并在代码中修正了,具备可用性(尽管目前版本输出信息的可读性不高,但容易改进)。
  MetaTM/TxLinux和Zyzzyva对原有机制的简化没有破坏它们的活性,但弱化了原有机制的安全性,从这个角度看,它们类似于蒙特卡罗算法。不同的是,数值计算可以通过多次运行来提高解的置信度,而系统算法是有副作用的,不能通过多次运行的方法提高其安全性。MetaTM/TxLinux 和Zyzzyva均采用额外的监督机制来发现错误,在情况不合预期时及时告知算法进程撤销错误操作,重新运行算法或者转而使用原有的安全机制运行。这样,作为一个整体,系统的安全性重新得到了保证。MetaTM/TxLinux的作者没有给出他们这种乐观的并发执行性能的理论证明,但通过在修改之后的 Linux内核上运行多个实际负载,用实验数据说明了事务内存的开销小于传统的锁机制。Zyzzyva的作者同样以实验数据说明为主。可以预测,其假设失败的概率与分布式系统中节点本身失效的概率相关。
  完备性(Completeness)与可用性(Availability)的权衡
  上面已经提到,Exterminator的算法肯定不能准确覆盖和修正所有内存错误,但作为一个辅助工具,对用户来说可用性是足够的。对它来说可用性的约束弱于完备性。在MetaTM/TxLinux中,监督机制的加入从理论上保证了系统的安全性,但测试表明对一类频繁更新文件系统同一结点的负载,事务内存的乐观估计失效,临界区冲突激增,系统可用性下降。在这种情况下可以认为可用性的约束强于理论上的完备性。可见,在有些时候系统的完备性与可用性的约束强度不能一概而论,需要针对具体上下文来分析。
  面向方面程序设计(Aspect Oriented Programming)的思想
  MetaTM/TxLinux对临界区的控制相比严格的锁要简单不少,但之所以能够对应用保证安全性,是因为它改变了对环境的假设,拓宽了系统边界,加入了外部的监督机制。Zyzzyva中客户端对服务器的监督和反馈也类似。这种通过适当修改外界条件假设,加入外部机制以解决在原有系统闭包内不能或难以解决的问题的方法是值得我们学习和应用的。
  更进一步地看,将监督反馈机制、垃圾回收机制,乃至活性、安全性这样的抽象性质独立出来,作为一种系统“切面”机制,体现了一定的面向方面的程序设计思想。这类方法或许在GOS将来的设计中有积极意义。

● 我认为这些工作的不足
  应用于数值计算的随机算法的平均复杂度和性能都是可以借助概率论证明的,但应用于系统问题的随机算法只能对理想负载计算其性能,在实际利用中负载的特性对系统的性能可能有显著的影响。MetaTM/TxLinux的事务内存的在某些负载下的临界区冲突激增的问题就是一例。作者叙述了这一现象并推测了原因,但没有给出这类问题共性的、内在的原因。如果能够进一步分析这类问题的本质,较为明确地找出不同内存管理模型的适用条件,则可能更具学术和实用意义。如果在此基础上引入一些启发式算法,根据负载的特性调整执行策略,有可能达到更好的效果。
  MetaTM/TxLinux文章以及会议演示文稿均用相当的篇幅阐述其实验方法和评测结果,作者选择的几个测试用例基本覆盖了Linux内核几大模块的主要原语,但这只是在静态层面上的。内存管理可能遇到的诸多问题只有在不同类型的原语以不同序列组合的动态运行过程中才能充分暴露。因此,如何提高测试用例的说服力是一个问题。

● 总结
  我从上述工作中学习到的算法以及其中蕴含的随机算法等思想可以说是一类技术,在初始了解到其巧妙之处后,我曾设想在其它一些经典的系统问题中如何应用这一工具,例如在共识问题(Consensus)中如何快速取得一致。但我觉得有一条原则需要注意,不能为了使用工具而使用工具,要以应用的需求为出发点,合理地分析应用对系统性质的需求,选择最适合的技术方案。这几项带有随机算法思想的工作之所以比较成功,我认为就是因为作者对问题需求把握得好,适时地放弃一些次要目标,另辟蹊径来换取首要问题的高效解决。

页面存档: 上页 1 2 下页