撰文 | 袁进辉 我们经常面临如何评价一个大型软件系统质量的问题。首要的评价指标肯定是 功能 ,软件是否满足主要的需求(do right things)。如果有多条技术路径可以实现同样的功能,人们倾向于选择更 简单 的办法。
奥卡姆剃刀准则“ 如无必要,勿增实体 ” 非常好的概括了这种偏好,对简单的偏好是为了对抗复杂性的挑战,其底层逻辑是:“简单的才是对路的”(do things right)。
1 软件研发的亘古难题:对抗复杂性 上世纪60年代,因为软件研发跟不上硬件的发展和现实问题复杂度的增长,而无法在计划的时间内交付,一度被称为“软件危机”。 曾在IBM领导System/360和OS/360研发的图灵奖得主 Fred Brooks 在软件工程的圣经《人月神话》里描述了巨兽在焦油坑(可能和中文的“屎山”意思差不多)垂死挣扎的困境,来类比深陷软件复杂性泥潭而不得脱身的软件研发人员,他还提出了著名的Brooks法则”向进度落后的项目增加人手,只会使进度更加落后“。 在《没有银弹:软件工程的本质性和附属性工作》论文里,他又把软件开发的困难分成本质性的和偶发性的,并指出造成本质性困难的几个主要原因:复杂性 (complexity),隐匿性(invisibility),配合性(conformity)和易变性(changeability),其中复杂性居首。 2006年,有一篇题为《跳出焦油坑》(Out of the Tar Pit)的论文呼应Brooks,这篇论文认为,复杂性才是阻碍大型软件研发成功的唯一的主要困难,Brooks 提出的其它几种原因都是因复杂性无法管理而导致的次生灾害,复杂性是根源。这篇论文,也引用了几位图灵奖得主对复杂性的精彩论述:
“…we have to keep it crisp, disentangled, and simple if we refuse to be crushed by the complexities of our own making…”(by Dijkstra)
“The general problem with ambitious systems is complexity.”, “…it is important to emphasize the value of simplicity and elegance, for complexity has a way of compounding difficulties” (by Corbato)
“there is a desperate need for a powerful methodology to help us think about programs. … conventional languages create unnecessary confusion in the way we think about programs” (by Backus)
“…there is one quality that cannot be purchased… — and that is reliability. The price of reliability is the pursuit of the utmost simplicity”
“I conclude that there are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies and the other way is to make it so complicated that there are no obvious deficiencies. The first method is far more difficult.”(by Hoare) 把事情做简单是最难的事情,也是对抗复杂性的唯一途径:一方面,简单性意味着可理解性,可理解性对于大型软件的维护和迭代至关重要;另一方面,虽然简单的抽象并不保证简单的实现,但过度复杂的抽象几乎一定意味着复杂的实现。 本文将以TensorFlow中“依赖引擎”的设计为例讨论它是如何违反奥卡姆剃刀准则的。 2 背景:深度学习框架的依赖引擎 深度学习框架和其它分布式计算引擎都喜欢使用数据流模型(Dataflow),直白点说,就是一个有向无环图(DAG),图中的节点分成两种类型,一种是操作节点(op),一种是数据节点(data)。 在数据流模型中,一个操作是否可以开始执行取决于这个操作的输入数据是否就绪,因此所有操作按照有向无环图的拓扑序来执行。 以上图为例,方块表示op,一共有4个op,圆圈表示data(从生产者视角看A实际上只有一份,从消费者视角看有两份,因此画了两遍)。如图所示,{B=A+1}和{C=A+2}之间没有依赖关系,它们之间的执行顺序是未规定的,只要计算资源充足,它们可以并行的执行。如果翻译成命令式程序,以下两个代码片段都是合法的:
A = 2 | A = 2B = A + 1 | C = A + 2C = A + 2 | B = A + 1D = B * C | D = B * C