恢复博客

从archive.org把博客恢复了出来,写作日期也都还原了,不过正文中的链接地址应该都是经过转换的,懒得修正了,以后一年备份一次出来。

最近空闲时候写计算机图形学的代码,写着写着又跑到读数学书去了,有点回到学生时代和刚毕业前两年的感觉。大环境差,公司环境也差,近期多读读书写写代码是最能让人心静的。

写东西依然有严重的情绪障碍,原来这几年下来竟然没有半点好转,就流水账写一点吧。

数据的路该往哪走?

上周跟组里同学说,想写一篇《数据的路该往哪走?》的文章,论述一下我们的思考和看法,面向不懂大数据的高层和业务方,也把我们一到两年的发展方向确定下来。只是现在写作障碍越来越严重,怎么也连贯不起来,对着一闪一闪的光标长久的挣扎和发呆,先流水账记录一些偏技术的内容吧。

这一年多数据处理方面的进展,应该说每一步进展都在预料之内,都是在理论层面论证清楚后进行的。执行更像是验证。有趣的是,单纯看落地出来的结果,并不显得多么复杂和高明,方案简单粗暴到极点。对外分享,每每让我有种在下象棋,思考了几十分钟,最后走了一步“拱卒”的感觉。不过同是简单,是否是“有理有据,有意为之”,差别是根本性的——艺术是不是也是这样?

回顾整个思考过程,核心是三篇:

当然要理解这三篇的内容,需要大量知识储备,许多references要提前熟悉。

第一阶段的目标是解决我们自身的问题。当时面临的问题是研发效率低,组里同学工作机械重复,难度不大却费时费力,实实在在是在“搬砖”。错误在于不该按做业务需求的思路做数据需求 ,应该从数据方案出发,挑战Shasta中说得很清楚:

  • First, user query latencies must be low.
  • Second, underlying transactional data stores have complex “read-unfriendly” schemas, placing significant transformation logic between stored data and the read-only views that Shasta exposes to its clients.
  • Finally, Shasta targets applications with strong data freshness requirements.

解决的核心思路,就是变pre-processing优先为post-processing优先,利用硬件和引擎强大的在线计算能力(主要是在线join),把预处理工作消除。仅这一点,做惯了传统数仓建模的人至今也很难接受,相比之下,平台的接受度反而更高一些。

这一阶段还主要是从纯技术角度来说。当前除了类似头条这种纯信息化的业务,“互联网+”型业务对于数据应用面临的困境是,所有人都知道数据有价值,但不知道应该如何利用。加之现在DL和AI概念火得一塌糊涂,造成一个很滑稽的局面,人们拿着DL这个锤子,却找不到合适的钉子。各方能做的,就是“先收集数据”,然后依然做些初级的洞察分析。于是引出第二阶段的思考,也即文章的标题,数据技术方案的发展方向是什么?数据如何发挥价值?终态是什么?

前段时间一直在调研WolframAlphaPalantir这两家传奇公司的方案,资料很少,只是判断大方向应该如此。经过一段时间的广泛阅读,从这个slides中了解到Palantir的核心策略竟是IA,后来经Quora指点在archive.is里挖出一篇Palantir官博里已经不存在的那篇重要文章,整理思路,发觉一直以来忽视了“人”的重要性,也忽视了“人”造成混乱的能力。

IA(Intelligence amplification)是一个很老的概念,可以直译为“智能增强”,与AI对应。这个场景下的AI指的是AI的终极形态,而非简单的算法应用。AI以机器为中心,IA以人为中心。现阶段除某些纯信息类的封闭场景(如围棋),绝大部分是无法做到完全AI自主工作的,IA的思路更为贴近现实:以领域专家为核心,系统作为其大脑的扩展,提供数据支持,快速响应他提出的各类问题,步步逼近答案,逻辑思考和决策仍在人脑中进行。

Palantir文章中的数学模型很好的解释了这个思路:a(h,c) = h*c。其中a表示系统发挥的效用,h表示人的能力,c表示系统能力。效用等于人乘机器的能力,这是理想情况,现实中是不可能的,比如分析师向Hive发出一个数据查询请求,可能要两个小时才能返回结果,效用大打折扣。所以需要修正一下公式,增加一个参数f,friction,表示人机交互成本:a(h,c,f) = h*c/(1+f)

更进一步,可以认为人的能力是个常量,于是公式可以简化为:a(c,f) = H*c/(1+f) 。

我们目前的现状是,H不止一个(很多数据产品)且绝对值很低(能力不够),f值非常大(数据问题作为需求来处理,每个N天)。所以数据不太能发挥出来很大价值。这是根本原因。

根据公式可以很容易看到三个优化方向:

  1. 提升h:找个对数据敏锐的业务领域专家,带着明确目标利用系统
  2. 提升c:提升系统计算能力
  3. 降低f:降低人机交互成本

初看起来H是比较难搞定的,但实际上未必会构成瓶颈。如果f够低,普通的H也可以发挥很大价值,而且人是会成长的。这个问题有点“鸡生蛋蛋生鸡”的意味,重点还是系统要够强大,人机交互成本要足够低。然后在人机互动的过程中,双方都会有持续提升的动力,飞轮就转起来了。

按这个模型可以回答前面的问题:

  1. 技术发展方向?降低f
  2. 发挥价值的方法?以人为中心,通过低成本、高频率的人机交互
  3. 终态?f等于0

所以,第三个阶段核心是如何降低f。理想情况不难想象,就是Elon Musk的公司Neuralink想要干的事情,把电脑和人脑无缝连接起来。这个进程需要多少年我们无法预测。姑且先退一步,现阶段能做到通过自然语言交互,做到秒级响应就已经非常理想了。目前比较成熟的系统,能了解到的,只有WolframAlpha、Palantir、IBM Watson以及前些日子看到的Google Analyza,Siri和各种优秀的智能音箱产品也算,不过Siri是基于WolframAlpha的,另外百度阿拉丁(在我看来)算是一个生硬的失败版本。

从我们面临的实际问题出发,降低f其实是在解决“需求预处理”的问题。这是一个做得越多负担越大、效率越低、错得越多的事情。造成当前格局的表象原因是,多个团队按照自己的粗浅理解设想数据需求,不系统、不深入、不专业,一段时间下去,必然趋于同质化,边界模糊,互相扯皮,在一个非常初级的层次内耗。这其中,人的能力和组织架构合理性是主因。但若论本质,却与第一阶段技术方案的问题相同,我们不应该按”需求预处理“的方式做事。

怎么理解“需求预处理”,说得理论化一点,若以整个数据服务能力作为核心,当前的做事方式就是一种对Query(需求)的穷举(预处理)。不可能穷尽,事情也就不可能做完,做不到对未来查询需求的提前满足,也就不可能提效并降低friction。当需求垂直进行的时候,甚至在不同团队进行的时候,期望做到系统复用、保证指标一致性根本是天方夜谭。可惜这个道理,在团队外很难给人讲明白,给不懂技术的产品和业务团队讲清楚就更难了。

技术的发展方向是搭建一个框架,使得随着数据需求的不断迭代,系统能力持续增强,数据、指标、算法、可视化持续集成进去,产生累积效应,变加为乘。这个框架一个很好的例子就是Analyza,回到我们自身,配合第一阶段形成的在线计算能力,预期可以做到提高c的同时极大的降低f。还在紧张论证当中。

最后说点题外话,近期因为各种原因被动思考了一下“画饼”的问题。当然这个能力还是要有,即所谓的愿景,所谓的Vision。马云天天挂在嘴边“使命、愿景、价值观”,当然很重要,但我越来越觉得,根基应该还是技术突破,否则会陷入一群人反复拿一坨固定大小的事情不断重新切饼分饼的滑稽局面。我们应该做的,是将马车工业的饼升级为汽车工业的饼,螺旋攀升,而不是在马车工业里内耗——全部马车产业也比不上如今一个汽车轮胎产业体量大。

放空与几本书

快两个月了,大脑一直在放空。这应该是自上大学以来首次,尤其是工作后。突然慢下来,慢得让人不安。

不过也并非完全什么都没想,只是近期不如此前总是反复思索同一件事、同一个问题,思维可以被抢占了。所谓放空,用计算机的语言来说,就是大脑被idle线程抢占了,说抢占是因为实在仍有许多重要的事情需要思考。这当然不是正常机制,但人也当然不是机器。

除了什么都不思考,发呆数小时,有时也会想一些琐事,坚持运动,认真吃饭,同时也在持续阅读。

最近读了几本书——似乎依然有心理障碍,无法深入品评,简单记录备忘一下。

重读了《孤独及其所创造的》。“一个隐形人的画像”部分,发现作者父亲的性格和自己很相似,但是读大学时我并非目前这个德行,所以当时为何喜欢这书,百思不得其解。第二部分仍然故弄玄虚,没有读完。另外此次重读不觉得翻译的好了。

重读《金阁寺》。也不知为何,没有读完,书仍放在书桌上。搞了一套三岛由纪夫全集,以后慢慢读。说来奇怪,我发现比起三岛,我对译者唐月梅的兴趣更大。

读了《李健熙传》,发现一个现象,第二代通常是奠定宏伟基业的人,比如摩根,比如万豪的比尔·马里奥特,比如李世民,比如邓小平,比如优衣库柳井正。第一代创始人通常不具备第二代的视野和大局观,第二代在成长过程中,躲在后面看到了所有细节和问题,看到了一个个决策的过程和结果,在安全的环境和平静的心态下,潜移默化,形成了自己的思维,让人羡慕。

读了《索罗斯传》,最近又在读《原则》,对比索罗斯和达里奥,可以明显感受到两个获得相似成就的人截然不同的性格和思维模式,非常有趣。索罗斯是一个糊涂的哲学家,他对金融运作原理有极为出色且有效的直觉,这直觉深藏于他的大脑中,但他无法准确说出这直觉背后的机理,尽管他在努力。而达里奥是一个科学家,他的直觉有着完整的方法论支撑,并且可以系统化的讲解给其他人。我有一种感觉:索罗斯不太可能培养出来一个接班人,除非遇到一个同样的天才,但那不是培养出来的,反之达里奥可以将一个聪明人培养成接班人。最近觉得金融投资蛮有趣,投资这种“玄学”其实同样需要经验和思辨,而思辨本身乐趣无穷——经过痛苦思考,形成深度直觉。

读了《林肯传》和《活下去的理由》,读这两本原因是最近在试图理解抑郁症。林肯是一个彻头彻尾的悲剧老头,终其一生与快乐无缘。《活》是一本不错的书,许多另类视角和精彩洞见,也有许多在其他书籍中很难看到的引用。我从没想过抑郁症竟是这么普遍的一种疾病,最近常常感到,大脑真是一种极端复杂的化学装置,需要各种原料来维持平衡,想要在一生当中都正常运转太难了。

读了《当我谈跑步时我谈些什么》。有天晚上歪在东方广场太平洋咖啡喝了一杯新款,想不到回来后睡意全无,坐在餐桌旁读了个通宵。好多年前站在老祖书屋读过半本,那时候心不在焉,这次很认真,书很有意思。33岁那年,村上开始跑步,一直跑到今天。发现自己脑子里记了许多年龄。如村上29岁开始写小说,37岁时写了《挪威的森林》,金庸33岁开始写武侠,耶稣33岁时被定上十字架,印象里楚留香出场也是33岁,三岛由纪夫45岁刨腹,古龙46岁喝酒喝死了,等等等等。

另外在读茨威格的《巴尔扎克传》,读了很久。想了解巴尔扎克是因为古龙,想知道对古龙影响很大的这个名字很熟悉作品却不熟悉的巨匠,到底是一个什么样的人。总结来说,巴尔扎克是一个出身卑微、精力充沛、矮胖粗鄙、阅女无数、一生喝了五万杯高浓度咖啡、渴望攀龙附凤俗不可耐又实实在在充满天才的工作狂。

最近买了许多传记类图书,够读一个季度了。计划将在读的两本读完后,了解一下古典音乐和古典音乐史,增加除后摇外另一种大脑放空时可听的音乐。

《挪威的森林》

好多年没重读《挪威的森林》了。

回想以前,非刻意的,居然可以整段整段背诵书中某些段落,尤其开头直子和井那部分。算起来,这次应该是第六次或第七次重读,且不算上学时偶尔“信手翻开一页,读上一段”。但是,从研究生开始,再没翻开读过超过一页——从某个时刻起,大脑某处似乎发生了结构性变化,《挪》的魔力也随之消失了。读来索然无味,体会全靠记忆,十分悲哀。

此后读书只能读功利性质的,历史、政治、传记、家书一类,以及学术专著和科普一类,偶尔会读一点侦探小说。总之,只能看得进去理性的读物,需要感性的东西,就似同极磁铁靠近一般,不自觉的十分排斥,厌烦不已。写记录和博客,也不像以前总是三岛由纪夫的味道,冗长的句子、复杂的修辞,叠床架屋,还时而悲哀一把。现在记点东西,词句总是删减再删减。不过也不是工作以后养成的习惯,从“那时候”就开始了。对待亲人也是如此,想不起亲人的生日,总是临近才发觉,节日也几乎不记得,一切矫情之事都很厌恶,看电影从不感动,冷漠至极。像一条蛇。

搬到望京后,重新拿出《挪》,每晚端坐在空旷客厅的沙发的一角,一边喝酒,一边固定读完一整章,加上序共十二章,读了两周。一边读一边用铅笔在本子上记笔记——读《挪威的森林》还认真记笔记的,想来只有我一个吧。这一次感受大不相同,读出很多以前从未发觉的东西。我猜想,应该每个人读小说都会不自觉的进行代入吧,尤其是第一人称的书。但是对于这本书,代入似乎也是错乱的,高中时候很自然地代入了渡边,忧伤不已,然而上大学后,却更像永泽,“所做的,不是自己想做之事,而是自己应做之事”,且一直奉行至今,那几年,脑袋里总是回荡“不要同情自己,同情自己是卑劣懦夫的勾当”,就这样一直持续了不知道多少年。

这次重读,稍微找回一点昔日的感受,可惜那种强烈的感受在写记录的此刻已经荡然无存了。凭记忆写写吧,富有感情不指望了,仅仅是真的很想记录。

这次感触很不一样,尤其是绿子和初美,我发觉十几岁二十出头时候的我根本无法理解她们,肤浅的如同书皮上的介绍,“野性未脱,活泼迷人的绿子”。以前我也这样以为,并总是以我的那位代入,现在发觉真是天大的谬误。绿子的可爱,绝不是性格的表象那般肤浅,她的坦然、成熟、缺爱和坚强,对亲人、爱人的负责和胡闹,每一样都是深刻的、令人感伤的,没有一点娱乐性也不该有。她说自己是“砍柴女,悲哀不起来”,但心里其实很透彻。与渡边一起饭后看火灾时,渡边提议收拾东西,绿子说:

……
“不要紧,我不跑的。”
“这里烧着了也不跑?”
“嗯。”绿子说,“死了就死了呗!”

读到这,我内心一阵难以抑制的酸苦,我能理解绿子,她是真的想死了就死了呗。多么轻松,又多么沉重。

……
“不是累,”绿子说,“只是好久都没这么放松身体了,呼的一下子。”

才二十岁,母亲早几年脑癌去世,家里折腾的一塌糊涂,现在父亲又患了同样的病,家里积蓄也所剩无几,每一样事都要自己操办,每周去医院照顾父亲,擦汗、取痰、接尿、喂饭,其中的孤独和艰辛可想而知。高中时候上的那个贵族女校,体会拮据的难堪,却依然纯真,父母不愿意做饭,自己用买胸罩的钱买厨具,从书本上学做关西菜,这一样一样,都难得至极、可爱至极。这么多年来,我们都喜欢绿子,却不理解她。读书时候我在想,村上到底是以谁为原型塑造了绿子的,以村上自己小资的生活状态,难以理解这些生活艰辛才是,我在笔记本上写着,“村上是理解生活的,《挪》的沉重不只是直子,绿子也是。”

另外是初美。以前读到初美的结局——永泽离开去德国两年后,结了婚,又过了两年,用剃刀割断了手腕动脉——并没有特别的感受,只是觉得是一段凄美短暂的插曲,一个俗套的人物设定。而这次读,却是不胜悲哀,久久无法平静下来。村上写道:

当我恍然领悟到其为何物的时候,已是十二三年以后的事了。那时,我为采访一位画家来到新墨西哥州的圣菲城。傍晚,我走进附近一家意大利披萨饼店,一边喝啤酒嚼意式披萨饼,一边眺望美丽的夕阳。天地间的一切全都是红彤彤一片。我的手、碟子、桌子,凡是目力所及的东西,无不被染成了红色,而且红得非常鲜艳俨然被特殊的果汁从上方直淋下来。就在这种气势夺人的暮色当中,我猛然想起了初美,并且这时才领悟她给我带来的心灵震颤究竟是什么东西–那类似一种少年时代的憧憬,一种从来不曾实现而且永远不可能实现的憧憬。这种直欲燃烧般的天真烂漫的憧憬,我在很早以前就已遗忘在什么地方了,甚至在很长时间里我连它曾在我心中存在过都未曾记起。而初美所摇撼的恰恰就是我身上长眠未醒的「我自身的一部分」。当我恍然大悟时,一时悲怆之极,几欲涕零。她的确、的的确确是位特殊的女性,无论如何都应该有人向她伸出援助之手。

“当来到人生某一阶梯的时候,像突然想起似的自行中断了生命。”12月中旬,回北京的火车上,我翻看以前的记事本,那一刻也是同样的感受,我拉下帽子遮住眼睛,眼泪再也止不住。

记录到此为止,脑袋又是一片空白,再也写不下去了。

这次重读,深深觉得《挪威的森林》从头到尾,每一段文字、每一段情节、每一个人物都无可挑剔,独一无二。从青涩自我的十七岁,到阅历已算丰富、读书已极为挑剔的三十岁的此刻,仍然是这样的评价,替自己和村上感到高兴。

《创变》

刚到酒店,最近在补酒店行业和OTA领域的业务知识。花了两晚读完《创变》,收获很多。关于7天连锁,记得是研究生时候读过《从不竞争》,讲7天如何在短短四年从诞生到上市野蛮生长。印象深刻的是组建团队的过程,之后如何管理团队,如何压缩成本,因为看了《从》,有次订酒店还特意定了7天,想看看公共吹风机是咋回事。

读《创变》才知道,原来这几年下来,7天经历了这么多。期间郑南雁还短暂离职出去创业,然后回归,带领7天进行大转型,蜕变成现在的铂涛集团。书写的很好,转型思路和转型过程都讲解的很清楚,看得出来作者很专业。单是书中的第25章(来自郑南雁的一趟经济走势课)就值了整本书的价钱。

书中记录了很多郑南雁出人意料——对于我这个外门汉来说——的观点,比如郑认为:酒店将不再是人们出行中的附属选择,而会成为时尚和休闲消费目的地;酒店企业真正的竞争对手是OTA;中端酒店市场将会呈现出和经济连锁酒店市场完全不同的形态,绝对不可能几家独大,一定是百花齐放;转换思路不如换人来得快;微乱是企业最好的状态。除了郑南雁搞品牌众筹的主线思路,整本书让我印象最深的是他对于经济驱动力转移的观点:

那么,为什么产业竞争时代会向以用户需求为核心的时代过渡?郑南雁的理解是,所有产业的生产能力都很容易出现过剩情况,这与前三个阶段经济核心驱动力的转移原因是一致的:发现资本可以买到资源时,资源的重要性就排在后面了;发现资本追着产业跑时,资本的重要性就往后排了;当各个产业的生产能力都出现严重过剩时,用户就变成了最有发言权的人。郑南雁认为,酒店企业与OTA之间绝不仅仅是产品与渠道的关系,而是最后将饭碗给了谁的问题。

按照这个观点,商业发展的终极,都是以服务好用户为核心。

对于如何跟OTA抗衡,郑南雁的思路也很清晰,他认为“强势的高端品牌”具备去OTA化的能力,因为OTA的弱点就是“无法让酒店品牌与消费者产生更高的情感沟通和黏性,无法实现品牌增值”。他将OTA类比为空军,酒店运营管理类比为陆军,酒店品牌类比为防空导弹。由此导出铂涛集团的的竞争策略:以品牌构建能力为先锋,以运营管理能力为阵地,以会员平台为核心能力培育。即三个要素:品牌,运营,会员。

书中好几章讲述了从7天向铂涛转型详细过程,如何调整供应链体系、会员体系、IT及业务流程体系、市场开发体系、门店运营体系,以及这个过程中的各种阻力、不理解,但最终事情做成了,说明郑南雁这个人真是有强大的内心和事业激情。前些日子和一个同学闲聊,都认为能鼓动人跟随、具有强大个人魅力或者说煽动能力的人,本质上需要这个人自己“坚信”这个事情,否则也没法让他人跟着他坚持这个事情,就像书中说的,“当一个人有了一个愿望就会形成一种气场。”

除了郑南雁的观点,书中也记录不少其他高管的一些思考,都挺有意思。比如波涛互联网公司CEO孟令航的观点,他将互联网行业分为三种模式:一是垂直模式,做的是用户体验、价格、促销、产品;二是平台模式,定规则;三是生态模式,筑环境。

书中从酒店行业的视角看问题跟纯互联网思维看问题有挺大不同。比如他们认为,酒店企业需要做三件事:客源、品牌、运营;OTA只需要做一件事:客源。因为要做的事情多,借助OTA的客源和数据优势,可以帮助酒店更好的做运营,进而提升品牌。通过对接OTA的数据,可以分析酒店的经营情况,分析哪些地区生意更好,哪些地方缺乏酒店,对酒店开发和选址都有帮助。

最后摘一小段某位铂涛老员工的感受,“感觉铂涛是分久必合、合久必分,一件事不会长久的做下去,每隔一段时间就要调整,打破官僚机制,让员工始终处于不确定、不安全的状态,一定要把员工的潜力都挖出来。”可想而知,这种状态下很多人会不舒服甚至很痛苦。我最近时常也在想组织和个体的关系,我发现很奇怪,似乎现在这个所有企业都不约而同强调“个体崛起”的社会,处于组织中个体却并得到没有所谓的独立性甚至保留自我,每一个人,不论层级,都被迫的随着组织这个器官的移动而移动,一刻也无法停止。所谓个体崛起,更像是假释出来的囚犯,在尽情补偿自己被剥夺的生活。无奈的是,连这位老员工也认为,这种不断的变化也许才是组织活力的源泉。

Haskell的副作用究竟是如何隔离的

大家都知道Haskell是纯函数式语言,因为Haskell将side-effects隔离出来了,但是“隔离”是怎么体现出来的,一直很难讲清楚,举个例子试着分析一下。

隔离副作用的表现就是,对于一个纯函数,永远无法引入副作用,换句话说,对于相同的输入,输出永远相同。在Haskell里,你永远无法定义出一个类型为String -> String的函数,对于相同的输入,输出的String值不同。

我们举一个John Hughes的Paper《Programming with Arrows》中的例子来说明。

假设我们想定义一个函数,计算某个字符串中某个单词的计数,最简洁的方式是使用Function Composition:

count w =
    length . filter (==w) . words

可以看出来,这个函数的类型是:String -> Int。这是一个无副作用的函数,对于同样的字符串,输出的结果永远相同。

现在我们改变一下这个函数的功能,不从字符串里面统计,而是读一个文件,然后输出向终端输出最后的结果,类似unixwc命令。我们可能很自然的会想将函数改造成这个样子:

count w =
    print . length . filter (==w) . words . readFile

代码虽然很直观,但是这个函数是编译不了的,因为类型不对,readFile的类型是FilePath -> IO String,返回值是IO String,是一个IO Monad,也就是说是有副作用的。

IO Monad的内部状态是无法escape的,所以你无法使用readFile函数,在IO Monad之外,写出一个类型为FilePath -> String函数来读取文件内容并返回。通过这种类型上的约束,Haskell就可以避免副作用的引入。

如果想实现这个功能,函数类型就只能是FilePath -> IO ()

count w =
    (>>=print) . liftM (length . filter (==w) . words) . readFile

我们从后向前来解读一下。

readFile的类型是FilePath -> IO String,表示整个函数的参数类型是FilePath,其实就是String

length . filter (==w) . words的类型是String -> Int,若想将这个函数应用到IO Monad里,为了类型兼容,必须要先lift这个函数,即将a -> b的函数lift成m a -> m b。在我们的环境下,就是将 String -> Int提升成IO String -> IO Int

这两部分组合后,即liftM (length . filter (==w) . words) . readFile,大家应该可以看出来,返回值类型是IO Int,但是print的类型为a -> IO (),在这个上下文中,即为Int -> IO (),无法直接使用。如果想使用,就需要一个类型为IO Int -> (Int -> IO ()) -> IO ()的函数,去掉具体类型的话,其实就是IO a -> (a -> IO b) -> IO b,这不就是Monad的bind运算符(>>=)吗?

最后理一下:

  • liftM (length . filter (==w) . words) . readFile的类型是FilePath -> IO Int
  • >>=的类型是IO Int -> (a -> IO ()) -> IO ()
  • print的类型是Int -> IO ()

将这三个函数放到一起,就是:

count w filePath=
    (liftM (length . filter (==w) . words) . readFile) filePath >>= print

写成point-free的形式,就是:

count w =
    (>>=print) . liftM (length . filter (==w) . words) . readFile

开始写点东西

好久好久没写一个字了。

辗转一年,有许多进步,也多了许多焦虑,自省更深了:擅长的不擅长的、骨子里真心不喜欢的、禀性难移的都摸清楚不少,但是人真是不能低估自己改变自己的能力,起码不能低估改变自己表象的能力。

转眼工作三年半,搞了两年多分布式存储,搞了一年多推荐系统,接下来,搞搞业务架构。似乎一直没在舒适区待过,刚刚觉得透彻了些就换了。分布式系统、规划、带团队、扯业务、算法、架构,都有不少心得。应该不是坏事,起码按外人的眼光看没有浪费时间。

生活上变化挺大,一切十分传统。接下来尽量抽一些时间,继续记录一些读书心得和技术思考。技术主要是非工作涉及的部分。第一个计划是关注下高性能解释器实现,把之前的解释器优化下。这半年Haskell的进步挺大,已经基本可以随意写代码了。最近尝试写了一点Rust,模仿seastar的fpc,发现原本直接的东西套上Rust的ownership限制着实令人抓狂。算法上,除了组里大张旗鼓的搞DeepLearning,别组同事也在自己玩,我却一头扎进数学基础迟迟未上道,也是计划的一部分。

这半年读了很多书,主要是名著、经济学和一堆数学书,但是一个字的心得也没留下,希望搬进新房后能有空闲和心情记录。

不管怎样,还是想开始写点东西。

解释器的效率

上学时候似乎一直在词法分析和语法分析阶段徘徊,好像编译器的大头是Parsing似的,其实无关紧要,大多数时候手写递归下降就够了。AST之后的部分才是核心,代码生成还算了解,代码优化就没怎么看过了。前几天重看了一遍wingolog的博客,又看了一些解释器优化的资料,才后知后觉的发现一些其实比较浅显的东西。

解释器效率低下的主要原因是,除了执行指令本身的“有效工作”以外,有很多不能省去的“额外开销”。比如解释“result=op1+op2”,除了加载操作数,执行加法以外,其余都是额外开销。如果是直接解释AST,额外开销就是visit各种节点。如block节点,仅表示一个作用域,并不做任何实际工作,另外,遍历语法树时虚函数调用的开销也很大。如果是生成IR,那么执行IR的loop就必须非常高效。这个loop主要工作就是维护一个PC(program counter),执行指令,然后将PC指向下一条执行,跳转并执行。看似直接的过程,因为这是整个解释器的核心,“额外开销”在这个loop占的比例将极大的影响解释器的执行效率,所以需要(a)保证这个主循环所有关键变量都使用寄存器,需要(b)避免pc修改后,执行indirect branches,因为CPU执行这类jmp开销很大,很难进行分支预测,另外(c)主循环的代码要能放得进CPU的指令CacheLine中(似乎是64k吧)。如果指令集很大,通常会实现成一个大的switch语句,编译器并不善于优化这类代码,通过gcc的Labels-As-Values扩展可以避免(b),但是对于(a)(c)处理并不好,所以很多编译器如LuaJIT2就是手写汇编,缺点是每个平台都得写一遍。

上面说的是解释器,效率的核心点是降低“额外开销占比”,优化手段是充分利用寄存器、CPU的分支预测和指令Cache。要想彻底消除这些“额外开销”,就需要进行JIT了。当然中间还有一步是代码优化,还在看,先不写了。

Datomic架构的一点理解

一年多以前Rich Hickey刚刚发布Datomic的时候就看过它的架构图,当时印象深刻的是以Datalog作为查询语言,架构却理解不深。这一年多对分布式系统有不少心得,晚上无意间发现一篇《unofficial-guide-to-datomic-internals》,读完发现理解起来并不费劲,然后又找出Datomic的文档读了一通,做了一点简单记录,又翻看了Hickey的视频《Using Datomic with Riak》。夜深人静,写点粗浅理解。

应该说函数式数据库从架构上非常不同于我们平时会接触到的分布式系统,主流的分布式NoSQL(Cassandra, Mongodb, Riak, Aerospike, Couchbase)也好,分布式MQ(Kafka,ActiveMQ)也好,架构设计上除了考虑如何处理Replication和Partitioning,再就是考虑一致性和可用性之间的TradeOff,对于数据引擎,就考虑是读优化还是写优化,基本上思维都围绕这些打转。所以遇到像datomic这样的东西,架构上分成Peer、Transactor和Datastore,然后交互访问上又从来不提我们熟知的这些概念,就会觉得很奇怪。我们会想这个Peer是指什么,看起来又不是Gossip协议中的Peer,更奇怪的是它是一个CP系统,却构建在Riak,Cassandra这类Dynamo系的AP系统之上。这是怎么回事。

Datomic架构图

传统上,我们都将DB作为一个全局状态来看待,应用层进行更新、读取,整个系统行为上与一个有共享状态的多线程程序无异,要考虑复杂的数据竞争问题。而对于纯函数式环境,所有东西都是immutable的,修改即创建,不再需要考虑竞争问题了。函数式DB也同样,对数据库的修改,实际是增加新的内容,而旧的数据仍然存在,每条数据都有关联的Version和时间戳,这样有一个好处是,我们可以查看此前任意时间的Snapshot,这对做分析或者审计都非常有用,也可用来恢复由于程序bug引起的事故。

Datomic架构上还是比较简单的,数据存储使用第三方的成熟方案,冗余和分片就不需要操心了。Transactor作为独立的协调者用于处理一般写入和事务,Peer作为客户端的Lib处理读请求。可以看到,写和读是两条独立的路径,这与《turning-the-database-inside-out》的思路很相似,只不过datomic需要处理事务,要满足ACID。这个保证逻辑并不复杂(我们暂时忽略处理事务本身的细节,只关心整体流程):每个Transaction都会经由单点的Transactor来执行,Transactor将请求发给Backend如Riak,当写入都成功后,Transactor主动向所有连接着的Peer推送更新通知,Peer这方更新本地索引,新数据可见,Transactor返回给客户端。又由于Transactor是个单点,并且事务处理不并发,所以Isolation Level是Serializable,一致性是满足Linearizability的。如果理解没有错误,Datomic里面的索引(Covering Index)实际已经包含了数据,按理说更新索引也就更新了数据,不过考虑到如果这样做,Transactor可能Fan-Out太大,网卡未必受得了,所以可能仅仅通知的是Segment的UUID。系统配置上,需要配置保证写入的Factor,如Riak,N=3,W=2,这里R=1就足够了,因为不需要做Read-Repair,依靠自身的最终一致就好了。对于Peer来说,最近更新的数据已经记录在其内存索引里(细节不清楚,猜测记录的是UUID,Segment需要重新从Backend读取),如果读后端Miss,就表明数据同步尚未结束,选择其他节点或者重试就好,牺牲可用性保证一致性。这个逻辑很有意思,语义上是强一致的,但是实际的写入是最终一致。这其实就是Datomic在AP系统上实现CP的奥秘。

Datomic还有一个优势就是Caching,一般我们使用MySQL+Cache模式时,如果DB做了修改,需要失效Cache。这个过程是需要业务来实现,如果涉及到从库,从库前面也有一层Cache,这其中就有一个一致性问题。比如说,更新了主库,然后失效主库前的Cache,再失效从库的Cache,但是如果Cache失效发生于数据同步到从库之前,就很可能造成Cache又重新回填了旧数据,造成从库这边Cache出现脏数据。一种比较好的方法是搜集从库的Binlog来删Cache,但是这可能需要修改协议和MySQL实现;还有一种方法是做延迟删除,等几秒再进行一次Cache删除。这是我们遇到的实际问题,但是Datomic根本没有这类问题。数据是Immutable的,只要索引正确,数据就是正确的,而且由于是Immutable的,我们也就不需要担心数据在使用过程中被篡改,所以就可以尽可能的进行Caching,而让大部分Query都在客户端进行,避免了网络交互。客户端查询有很多优势,比如业务需要查询一系列连续的数据,如果使用MySQL,写业务时不太可能会Cache一个Range的数据,于是就需要多次Round-Trip,而对于Datomic,这些数据(Datom)很可能在同一个Segment里。

另外,Datomic的缺点似乎也很明显:Transactor是个单点,即便官方Pro版提供HA,应该也仅仅是提供个主备,实际工作的只有一个Primary,所有的写入都走这一个点,必然容易出现瓶颈。还有就是索引,Transactor为了写入效率,应该会维护一个完整的索引,假如这样,单机容量也是瓶颈(好像最初GFS那个单点Master)。不过,这个问题可能并不严重,对于一个业务系统,有ACID要求的数据一般并不太多,Datomic的一个特点就是它是工作在其他成熟的分布式NoSQL之上的,所以用户大可以直接使用下层的NoSQL来存储对一致性要求不高的数据,而使用Datomic存储重要或需要复杂查询支持的数据。Transactor虽说是个单点,但是它只处理写入,听Hickey描述,写入逻辑跟LevelDB差不多,基本都是顺序写,定期建Index,总体的性能应该还OK。另外一点担心是,Transactor到Peer是个广播,如果Peer节点特别多,假如出现慢节点,很可能拖垮整个Transactor,导致写入不可用。这些关键的细节问题Datomic如何处理就不得而知了。

除了上面提到的这些点,还有诸如Datomic五种类型索引的设计、索引根信息的维护(需要强一致存储)、Datalog查询优化等等有趣但与架构关系不大的内容,没太仔细考虑,有时间再仔细玩味。

他人的“三十而立”

看了一会0xlab成员walkingice博客,很久没耐心读一些博客了,刚才读到《三十而立》这篇,一种久违的平静之感油然而生,就如同Rock听得太久,突然听到轻音乐一样。0xlab的几位成员从实验室成立之初就一直有关注,我个人的技术之路受Jserv影响颇深,对台湾黑客天然有一种亲切感。

工作两年,想来可悲,似乎一切都与在校时的期望不尽相同。我一直认为,软件开发是与艺术家和诗人最为接近的职业,我们的技艺里有美的东西存在,美的设计、美的结构、美的细节。影响我最深的几位黑客都是搞嵌入式的,直观上嵌入式更加生硬一些,离UI那些表象的东西更遥远,但恰恰在这个人群中,总是可以发现内心清澈、有纯净技术理想的人。而互联网技术圈,到处是浮夸的炫技、秀思维,互相吹捧、互相贬低成风,大家都自封屌丝,没有人真的会想用技术来改变什么,所谓技术理想不过是做一个如何等样的系统,收获吹捧,将来跳槽能多拿几块钱而已。

我想,这是两个技术领域的区别,也是两个社会的区别。嵌入式领域有深厚底蕴,互联网技术日新月异,低层次的人不需要太多积累即可胜任,高层次的人又凤毛麟角,很像武侠小说的世界。领域的差别还在其次,想来我自己所处的层次是主因。再者,大陆与台湾这两个社会现阶段也相当不同,台湾近些年出生率低,社会福利好,工作压力相对小,人们惯于舒缓的生活,这种社会更容易孕育出文化的东西,而非商业的东西,人们对于幸福和成功的理解也不同。大陆尚处在原始积累阶段,贫富分化严重,大部分人还在为生计奔波,经济独立是人格独立的基础,贫穷的理想主义又得不到社会认同,自然与财富无关的理想都会被认为是矫情,所谓理想不过是企业家和房地产商们的理想,都是财富理想。

结尾摘一段Blog:

自己最近給自己的答案,則是分成對內與對外兩個面向。

對內,則是尋求自我的實現。 而自我實現無法以職業、收入來滿足。當然並不是說我不喜歡錢,或不希望給家人好的生活。而是在維持家庭正常的生活水平前提下,追求我認同的價值,在這個過程之中我才能感受到自己的生命被充實。

對外,一個微小改進的價值,以一年、十年甚至百年的幅度來看,可能會有不同的結論。世上的英雄少之又少,多數的人是在時間的長河中互相推擠著緩緩向前。我知道個人無力指揮流向,但我也知道流向的改變總是各種作用力加成的結果。

對內的因素我無法超越,對外的因素又是我的信仰,加總起來就是我現在的選擇。我想這就是我如此快樂的原因。

话说回来,是否有一天,我也能遇到一小群人,成立0xlab一样的小组织,以一种不虚伪、无私心的理想主义情怀为这个社会做一些事情。