网络知识 娱乐 吴军亲述编程生涯:不用低效率的算法做事情

吴军亲述编程生涯:不用低效率的算法做事情

吴军亲述编程生涯:不用低效率的算法做事情

作者 | 吴军 责编 | 田玮靖

出品 | 《新程序员》编辑部

世界上总有一些IT难题,需要有经验的人解决。如今已55岁的吴军,认为年龄与能力共同成长才是应对“35岁危机”之道,在过去的职业生涯中,吴军正是这样做的。本文,吴军通过讲述从大学学习计算机课程到留校做科研,到出国深造,再到入职Google的经历,分享了他的工作经验与技术感悟。

吴军亲述编程生涯:不用低效率的算法做事情

吴军:博士,著名自然语言处理专家、作家、投资人,曾在Google研究院、腾讯等公司任职,现为丰元资本(Amino Capital)合伙人。著有《数学之美》《浪潮之巅》《计算之魂》《文明之光》《见识》《态度》等畅销书。

吴军亲述编程生涯:不用低效率的算法做事情

初识计算机

和如今大城市里的学生不同的是,我在进大学校园之前完全不会编程,因为那时计算机是个稀罕物。

我第一次见到计算机是在小学二三年级的时候,在父亲任教的学校里有一个计算机房,里面的计算机有十几个冰箱大。而那么大的计算机一秒钟也就运行10万次左右,不及如今手机速度的万分之一。我对计算机是如何工作的毫无了解,那时的计算机不仅非常精贵,而且操作复杂,会使用计算机的都是专家。而如此精贵的计算机也只能用于解决国家非常重要的问题,一般的工程问题只能拉计算尺计算。到了20世纪70年代末,我父亲的实验室里有了一台机械的计算器,今天想来大概相当于莱布尼茨的计算器加上打字机吧,能做运算,但无法编程。即便是电子计算器在中国出现,也是改革开放后的事情。

虽然我在上大学前没有碰过计算机,但还是选了那个专业,主要是那时受了刚开始的信息革命的影响。比我低一年级的学弟学妹们,就有机会在中学接触计算机了,他们中间有四个人因为参加计算机竞赛得了奖,被直接保送到清华大学(以下简称“清华”)和北京大学(以下简称“北大”)了。不过这四个人后来也没有再从事和计算机相关的工作。

我正式接触计算机编程是从一个可编程的计算器开始,那个计算器带有BASIC的解释器,可以用BASIC编写一些很简单的小程序。当时我虽然在清华大学计算机系,但是第一年并没有学任何计算机的课程,学的都是基础课,因此我在寒假拿了一本书自学BASIC,假期做了些统计、整理全班平均分之类的小事情。再往后,我有时到父亲的实验室,帮他编写小程序,那时IBM PC已经进入了科研单位。

特别要指出的是,自学一门编程语言不是难事,因为计算机的语言比人的语言容易多了。但是,如果完全靠自学来掌握计算机的技能,最多成为一个“二把刀”,要想在计算机领域走得远,就必须系统性地学习。而且,如果要获得较深的领悟,光看书是不够的,甚至向一些水平不高的老师学习也会有欠缺。

本文节选自《新程序员》004

吴军亲述编程生涯:不用低效率的算法做事情

与编程结缘

我正式系统地学习计算机是在大学二年级,有一门程序设计的课程教授Pascal语言。这种语言今天已经没有人使用了,甚至当时在工业界用得也不多,主要是它的执行效率并不高,而且业余人士学起来也比较麻烦。

在任何时代,使用频率最高的编程语言都不是最好的编程语言,而是最容易学,最容易实现当时各种应用软件所需要的最基本功能的语言。不过,如果学习计算机从那些语言入手,最后通常是“二把刀”,因为他们会对整个计算机的世界有一种片面看法。Pascal语言在过去对系统学习计算机的人来讲是一门好的编程语言,它的结构和描述计算机算法所用的伪代码是一致的,可以帮助大家培养起计算机的思维方式,改掉人一些不好的思维方式。比如,帮助大家从跳来跳去的思维,变成模块式思维。当我在大二学的这门课时,编程语言只是内容的很小一部分,主要内容是算法,懂算法才能解决那些比较难的问题。

在随后的一年里,我大约又学习了四门编程语言,包括FORTRAN、LISP、PROLOG和C,虽然这些语言今天都不用了,但在历史上它们有存在的理由,如LISP和PROLOG是为了处理人工智能问题。我列举这些语言是为了说明学习编程语言真的很容易,在工作中如果需要学习一门新的编程语言,自学就好,学会了也没有什么可喜的,因为它们并不是很难。我后来用到的C++、Perl、Java、Python等,都是自学的。对于从事计算机专业的人来讲,重要的是数据结构、算法和计算机系统结构基础,其次是对计算技术的全面了解。会写代码,就如同建筑工人会砌砖头,生产线上的工人会插元器件一样,不是什么了不得的事情。

到了大三,我学习了数据结构。这门课很重要,有了对数据结构的全面了解,编写出来的程序才是专业水平。这不仅是因为建立在好的数据结构基础上的程序执行效率高,不容易有Bug,而且这样的代码也容易被读懂,能够重复使用。

与编写代码看似无关,但却非常重要的两门课是系统结构和编译原理。

系统结构可以让大家对计算机从处理器到系统有全面了解,做产品深入后,不了解它们是做不好的。缺乏这方面的知识,成不了系统级的工程师,更成为不了架构师。通常在美国,系统结构被分为计算机原理和系统结构两门课,这里面还包括了机器语言和汇编语言。我在清华上学时,这门课被拆成了三门课。编译原理不仅让大家知道为什么程序可以在计算机上运行,而且可以清楚如何写程序能让运行效率更高。不了解编译的原理,靠工作经验改进自己写程序的效率几乎是不可能的,因为世界上各种各样的问题如果一点点总结经验,是总结不过来的。

编译原理通常不好学习,它完全建立在嵌套和递归基础之上,学习这门课要求人完全从常人的思维彻底转到计算机思维上,而且学好它还需要大量的练习。因 此,很多学校计算机专业并不要求所有人都学这门课。不过,如果想做一个超越“码农”的计算机工程师,我还是建议学好这门课。我在清华的毕业设计,做的就是一个PROLOG编译器。真正做过一个编译器,就知道计算机的程序如何运行,写程序时也知道该如何提高效率了。

相比美国计算机系的学生,中国学生平时的作业不少,但在学校里使用计算机做工程项目的训练却少很多。如果美国的学生一门课工程项目的负担是10分,清华的学生可能只有3~4分。这带来的结果是中国的大学毕业生如果没有参加过实习工作,很难一毕业就上手工作。

我在腾讯时,就发现很多成绩不错的毕业生需要再培训才能正式工作。我在学校里并不属于最喜欢写程序的学生,但如果有时间,还是想在计算机上做点事情。于是从大三开始,我就在教研组参加一些科研工作。当时即便是教研组的实验室,晚上10点多钟也要熄灯。所幸,学校有一个通宵机房,我们可以在那里工作到零点,这是夏天最令我高兴的事情。回宿舍时天气已经凉快下来,在宁静的夜里骑车是一件很舒服的事情。后来读到高德纳和比尔·罗伊(太阳公司创始人,Solaris的发明人)等人的经历,发现他们在大学时对计算机的兴趣比我浓多了,他们经常通宵工作。对于一份职业,如果一个人不是感兴趣而是为了糊口,成绩永远做不到前5%。

到了大四暑假时,我和十来位同学在宁波实习,为当地的工厂开发了一个财务管理系统。这次实习有件事让我此生难忘。同组的一个同学当时因为偷懒,在对账的程序中用了平方复杂度的算法,结果当这家企业积攒的数据越来越多后,对账变得越来越慢。这个问题其实有一个很简单的线性复杂度算法,但那位同学一时偷懒就忽略了,而测试时并没有多少数据,看不出性能上的差异。修补这个Bug只需要写十几行代码,但为此我们不得不派一位学生出差去专门解决这个问题。此后,我非常注重算法质量,从来不去用那些低效率的算法做事情。

吴军亲述编程生涯:不用低效率的算法做事情

做到领域内最好

毕业后,我的第一份工作极其无聊,就是通过反汇编把机器语言改成汇编语言,然后把微软原本只支持英文的操作系统汉化。这并不难,只要把处理键盘的中断程序和处理显示的终端程序修改为支持汉字即可。但当时的操作系统都没有源代码,只能通过Debug把二进制的机器代码逆向还原出汇编语言的程序,然后修改那些程序。这项工作既枯燥,又侵犯知识产权,于是,完成任务后,我就再也不愿意做这种事了,即使这让我后来Debug的水平非常高。

工作两年后,我发现在中国做计算机相关的技术工作很难开展,毕竟像操作系统、数据库系统以及常用的软件都是世界上少数几家大型跨国公司在做。由于做计算机生意,我接触了大量用户,发现有很多和具体产业相关的问题得不到解决,甚至在信息、图像和语音处理等方面,计算机都使用得不好。遗憾的是,我并没有信息处理的经验,这些钱自然就挣不到。

我在本科毕业时已被保送研究生,这个资格在几年内有效。因此当我工作两年觉得钱挣够之后,就决定回清华读研究生了。在离开学校之前,有两个系都给了我研究生的资格,除了我原先学习的计算机系,电子工程系(当时叫无线电系)也录取了我。但我并不知道该选哪个,只是图新鲜,觉得该换个系读一读,选择了电子工程系。等到我研究生入学的时候,又有两个研究方向很相近,课题组都希望我去,一个做图像识别,一个做语音识别,我也必须二选一。图像识别比较直观,我在本科时也有一些这方面的经验,因此对我更有吸引力。不过最后分配的结果是让我做语音识别,而我对此一无所知。但考虑到很多基础的技术都是相通的,便安心在这个课题组学习和做研究了。当时并没有想到,后来的我居然很喜欢这个领域的研究,而且做了大半辈子。

别看从计算机系转到电子工程系,两个专业似乎很相近,但在中国跨专业并不是易事,因为国内本科生选课的方向都比较窄。我在读研究生前,对信号处理和语音技术毫无了解,因为计算机系几乎没有这方面的课程。因此读研究生的前半年,我花了很多时间补课,那是我从本科到博士10年中最辛苦的半年。我同组的同学本科就是读信号处理的,因此他们上手比我快。不过,半年后我就赶上来了。

第一学期结束后,我就可以读文献了,很快完成了选题和开题。到暑假结束时,我已经完成了一半研究工作,最后从学习基础课,到完成硕士论文,我只花了一年半时间。当然学校没有惯例让研究生那么快毕业,于是第二年的后半年我就到一家清华的合作单位工作了。我周围的老师和同学其实很惊讶于我读书和做研究的速度。实际上,这得益于我的计算机专业基础比较好。当时的计算机都很慢,资源也有限,今天看似很容易做的事情,如统计大文本中常见的二元组(bigram)频率,在当时几乎做不了。而在电子系的研究生中,我可能是唯一个本科读计算机专业的人,因此,在他人看来非常难处理的问题,我却能找到适合的算法解决方案。

由于我在研究生期间发表了一些论文,我自觉喜欢做科研,加上工作时攒了一笔钱,当时并没有生活压力,便决定留在清华当老师。那时大学老师的薪酬很低,大多数人希望出校门挣大钱。而当老师最大的好处是时间灵活,这对我很重要。我每周都会到图书馆看文献,白天做研究,晚上整理数据、写论文。我在清华当了三年老师,语音识别和信息处理的水平大有提升,也发表了很多论文,在我所研究的领域,我已经做到最好了。不过计算机水平并没有提高。

吴军亲述编程生涯:不用低效率的算法做事情

他山之石,潜心攻玉

关于我到约翰霍普金斯大学读博士这个选择,对今天很多人来讲是难以想象的,因为要放弃很多既得利益。不过我那时还年轻,眼睛是往前看的,知道要想从国内一流做到世界一流,去世界顶级的实验室学习是捷径。

我所在的约翰霍普金斯大学语言与语言处理中心(CLSP)是世界上规模最大的语言识别和自然语言处理实验室之一,这是一个跨了很多学科的大实验室,教授来自电子工程系、计算机系、认知科学、生物医学等学科。博士生必须选择一个系,在我申请学校时,教授们根据我的材料把我划分到了计算机系。而由于我的研究经费来自电子工程系,因此在约翰霍普金斯,我就在电子工程系做研究,从计算机系获得学位。这种跨系拿学位的做法,也为我带来了麻烦。

为了取得计算机系的学位,我就要学习足够多计算机系的课程。我挑选了一些在国内没有开设的课程,如计算机算法课、信息和网络搜索课,还大量选修了自己感兴趣的课程,像并行计算这种当时不知道有什么用的课程,后来在Google做云计算时,就派上了用场。在美国读书,每一门课的工程量都非常大。例如,操作系统课,几个工程项目拼到一起,就是一个可以运行的操作系统。类似地,学完语音处理课后就能搭建一个语音识别系统。我在到Google之前,关于网页搜索的全部经验就来自信息和网络搜索课的工程项目,加上我暑假在AT&T实习的经历。这些经验已经足以让我应付Google的各种工作了。

语音识别的研究人员,通常来自电子工程(特别是通信)和计算机两个领域。通常前者的系统准确率高但不讲究运行效率,后者的系统速度快但准确率要差一个百分点。在21世纪初,世界上语音识别做得最好的两家公司是IBM和AT&T,前者是计算机公司,后者是通信公司。它们的系统就有上述特点。能否兼顾准确性和效率呢?通常很难,因为不同领域的研究人员都受限于自己的专业。通信和信息处理出身的人,会把注意力放到改进信息处理的方法上,而计算机出身的人会更重视系统的效率。

我的研究是在电子工程系做的,从导师到周围的同学,每天都在讨论如何改进哪怕是一点点的准确性。到了第四年,我已经完全达到了毕业要求,当时我做的语言模型在NIST(美国国家标准与技术局)的评测中取得了最好的成绩,也获得了世界上最有影响力的语音大会的最佳论文奖。可以说我已经达成了去美国的目的——做到世界第一。当时校报还专门报道了我的研究工作。在此之前我的师兄、师姐们平均毕业时间是6年。不过当我提出论文答辩请求时,委员会里大部分教授认为我在计算机领域的贡献不足,毕竟我是要计算机的学位,而不是电子工程的学位。于是,他们建议我再花一年时间在计算机算法上做出点成绩。显然他们觉得我离学生毕业的平均时间还早,再用一年时间,可以把论文做得更好。当时,我内心非常不舒服,因为我觉得靠自己省下了一年时间,却不得不接受这个现实。

在接下来的几个月,我的工作状态就是一沓纸,一支笔,推导数学公式,试图减少计算量,提高速度。彼时机器学习的算法训练稍微复杂一点的模型要几个月的时间,我希望找到一种至少能把计算量降低一到两个数量级的算法。如今,绝大多数算法已经被优化,不太可能像快速排序的发明人托尼·霍尔那样把一种算法的复杂度降低很多。不过,依然有三个可以降低算法复杂度的方向:一是通过数学变换,用等价的计算替代原有的计算;二是通过存储一些中间计算结果,确保没有任何重复性的计算;三是用抽样近似的方法,避免对全部数据进行计算。我在改进机器学习算法的过程中,这三种方法都采用了。

终于有一天,我和我的导师讲,我发现了新的、非常快的算法。然后我在白板上推导公式。我写完一屏时,他就用扫描器打印一幕,就这样我写了十几屏,他打印出了一沓纸。我放下笔说,计算量至少可以降低几百倍。他盯着白板又思考了一会儿说,“好像是对的,不过我得回去仔细检查一下。”几天后,他告诉我,我的推导无误,我成功了,我把那种机器学习算法的运行时间降低了两到三个数量级。然后我又用这种方法把之前很多人想做却做不了的工作一一完成了。后来想起来,这一年时间让我在算法领域有了一点点贡献。因为有了足够让人信服的成果,接下来的论文是很好写的,不过这项额外的工作让我晚毕业一年,而这也改变了我对工作的选择。如果是早一年,我会选择在IBM或AT&T做研究。因为晚了一年,让我无意中考虑了Google的机会。因此,很多时候早一点晚一点,还真不知道是福是祸。

吴军亲述编程生涯:不用低效率的算法做事情

在Google的两件事

到了Google,工作的性质就变了。当时的Google非常小,没有做研究的可能性,每天要处理大量的工程问题。思路与做研究时差距极大。例如,对于算法的复杂度,我们通常认为在大O概念下相同的算法没有再改进的必要,因为提高一倍速度在计算机科学中毫无意义。不过,在工程领域,这还是有意义的。比如提供一项服务需要1000台服务器,它一年的运行成本是100万美元,如果能降低10%的计算量,这就意味着一年节省10万美元的运行成本。10万美元对Google这样的公司当然不是大钱,但如果所有的服务成本都降低10%,那就不单单是成本问题,而是竞争力的问题。试想一下,如果有两家公司,提供的服务质量完全相同,其中一家公司的报价低10%,那么它将获得更大的市场份额,并通过市场优势逐渐确立它的市场垄断地位。当时同样的服务成本,Google的价格大约是雅虎的1/3、微软的1/10以 下,因此Google可以用价格优势完全限制另两家公司的发展。后来亚马逊也用同样的方法确立了在云计算方面的垄断地位。在Google,我写的最常用的一批代码被用在几百个项目中,这些代码如果没有优化到极致,很多服务的效率都会受到影响。在Google有两件事可以举例给大家参考。

第一件事是关于工作节奏。我到Google时,Google还是一家小公司,人少工作多,我几乎没有在零点之前回家的印象,然后第二天早上9点多就到了办公室,周末也需要加一天班。我从来不赞同公司逼着员工“996”,不 过,如果想做成伟大的公司,996是远远不够的。当时大家在Google,每周至少工作80小时。在美国的公司中,提交程序代码之前都需要进行代码审查,在Google代码写得最规范,对Google的贡献也最大的工程师是Craig Silverstein(斯尔福斯坦),他是Google第一位员工,一个人完成了第一版Google的几乎全部代码,而且Google的代码规范就是斯尔福斯坦制定的。由于我的代码绝大部分是系统代码,因此按照Google的惯例,一定要由斯尔福斯坦审核批准。斯尔福斯坦工作很忙,一般在零点之后审核我的代码。因此,我们在零点之后通过邮件讨论代码的问题,直到他批准了,我提交了,我才回家。我到Google一年后,贡献了代码库中0.5%的代码,当时Google已经有300个工程师了,而且很多人工作了好几年,因此我的产出还是比较高的。当时,斯尔福斯坦一个人贡献了Google多达8%的代码。我的朋友朱会灿贡献了1%,是贡献最多者之一。

第二件事是关于专业人士和业余工作者的区别。到了Google几年后,我放慢了工作节奏,很多项目都交了出去,开始做顾问。当时中国的工程团队有一个项目,第一个版本做得很不好,引起了大家的批评,于是,公司让我去重新做那个项目。

据我了解,该项目的所有问题出自一个根源,就是事情做得粗制滥造。比如,它占用了很多存储和计算资源,却没有给用户提供足够好的性能,显然项目组的态度就是应付差事。我接手这个项目后,估算产品应该能够节省80%的存储资源和2/3的计算资源,提供至少2~3倍的体验。当我提出要求时,得到的反馈是,“这怎么可能呢?”我说:“从你们使用的信息总量上来讲,就需要这么多的存储容量,用多了就是没设计好。另外,由于有几个子程序非常占用计算时间,因此它们必须优化,优化好了就能加速好几倍。如果这些问题不难,还要你们这些学计算机的干什么?我们从大街上找几个人培训一下也能写代码,计算机科班出身的,就是要解决别人解决不了的问题。”李开复在一旁听着,插嘴说, “你说得对,比尔·盖茨也经常讲类似的话。”随后我又讲,“作为一个计算机的工程师,就要有信心做到世界最好,你们按我说的思路去做,做到我要求的性能指标,我向公司申请,送你们去夏威夷度假。”后来大家真做到了,而且获得了去夏威夷度假的奖励。专业人士和业余人士都能实现基本功能,但既然是专业人士,就要努力做出世界级的产品。

2008年之后,我在Google的工作比较轻松,有时间重新开始做研究。我把一件原来在约翰霍普金斯大学想做而没做的事情给做了——写一个自然语言的语法分析器,能够分析并初步理解各种英语的句子和段落。我做这件事时并没有什么商业目的,只是出于兴趣。

几年后我再回到Google,发现Google的人用这种方法把网络上能找到的所有英语文章都分析了一遍。Google后来的计算机自动问答系统就是在这项工作的基础上实现的。很多时候,做研究不能太功利,做研究需要为大家解决一些基础性的问题。我很喜欢3M公司的一个说法“科学就是把钱变成知识,技术是把知识变成钱,两者不是一回事”。

吴军亲述编程生涯:不用低效率的算法做事情

结语

人们通常会觉得从事IT行业生命周期很短,主要是因为一些人的能力没有和年龄同步增长。世界上总有一些IT难题,需要有经验的人解决。我在Google时,汤普森(UNIX的发明人,图灵奖获得者)和我都在研究部门,他每日的工作就是和大家闲聊,看看有什么问题需要解决,然后能碰撞出什么想法。一段时间之后,他和两位IT老兵发明了Go这种新的编程语言,当时他已经60多岁了。

很多人会问我现在还写不写代码。我现在偶尔还会写,但只是出于兴趣,不会写任何产品的代码了。这就如同一个老兵,后来当上了将军,不用亲自扛枪上战场了,但还会出于兴趣玩枪,还会去靶场射击一样。实际上,图灵奖获得者都有这个习惯,在硅谷的图灵奖获得者甚至还自己发起编码比赛。过去,通常冠军都是高德纳。

成就一亿技术人