绕着INI走了一圈,这像是究极简化版的2021.03.07。区别在于但凡能回忆起的我都已经记过了,没必要在这里罗列。但我还是有一个很沉重的感触:从清华毕业时我觉得,不管是辉煌还是苦难,我一切都已经见识过了,但是从踏进这里的第一天起,我意识到我其实什么都没见过。我和绝大多数同学有着截然不同的成长经历,我完全无法想象他们的生活,就像他们无法想象我。
我总是会害怕,会迷茫,担心周围的人不会接纳我。我想他们最终也没有接纳我,否则我就不会独自一人在这里了。这能算是给了我什么启发吗,其实也没有,我对此依然无能为力,只是希望不久之后的下一次被人群拒绝,我能稍微从容一点。 - 节选自《2022.12.24》
本来选课的计划是10605 + 10714 + 15645 (DB),但最终没有选上DB,即使我作业都做完了。这是因为Andy Pavlo回来教DB了,他很受欢迎,而INI的选课优先级很低。学期初纠结了很久剩下的一门选什么,15662 (CG)和10605时间冲突了,我申请了冲突选课,拿到了两边教授的批准,然后交给INI审核,听说通常会通过的,但是居然被驳回了。我还考虑过10601 (Introduction to ML),上了两周感觉太无聊了,还有三次考试,听起来就很烦。最终选了10725,后来证明这也不是很好的选择。
我对ML并不是很感兴趣,因此我也不会选11785 (Introduction to DL)这种硬核的课。但也没有更感兴趣的课了,我不能接受选个Negotiations之类的,我是纯粹的技术人员。
看课程标题就像是我司做的事,而且好几个作业用到Databricks的免费Spark集群,虽然我不做ML,感觉还是很有选的必要。
课程前半段是些基本的ML算法以及相应的分布式计算,数学成分比较多,但其实都挺简单的,后半段都是围绕DL的。春季版本有六次作业,秋季版本有五次作业,和春季的前五次是一样的。每次作业都有written和coding两部分,written是一些数学题,coding前半段用Spark,后半段用TensorFlow。
秋季版本还有一个mini project,可以选模型压缩,它大概可以看成春季第六次作业的加强版,我选的是它;或者做个什么ICLR Blog Track,不是很懂,好像是写博客介绍一篇论文。mini project似乎是强制组队的,我也打算找个队友,但队友期中退课跑路了。我找教授申请自己做,倒也批准了。
模型压缩按照剪枝后的模型的稀疏度和正确率评分,有我最喜欢的排行榜,而且前三名有额外加分,我没有理由不卷。我对DL完全不熟,照着论文写完之后只能无脑地调参,靠这确实在ddl前一天都排在第一。我早就猜到可能有人隐藏自己最好的结果,接近截止才提交,结果还真有,ddl当晚我就被挤到了第三,第二天醒来被挤到了第四,连加分都没了,这些人都这么心机吗。我也没有太失落吧,毕竟我确实没有付出多少努力,这不是我最擅长的方向,我所做的也只是堆时间。事实上,努力追求,但最终无法在排行榜中拿到第一,这种情形才是贯穿我的学生生涯始终的。
有期中期末两次考试,题量不小,难度也不低。纸笔考试,允许带一张cheatsheet。我做了特别密集的那种,几乎把ppt中所有大段的文字都放进去了,结果也确实挺有用的,它真的会考的很偏。
陈天奇和Zico Kolter教的,听说最后一节课结束后大家都找陈天奇合影,直接把他晾在一遍。我看了几节录像,感觉还不如看ppt,课程的很多时间在写公式,字还不怎么清楚。我真是不理解,把公式一笔一划手写出来,这和直接看写好的公式有什么区别吗。
这门课基本是教你从零开始构建一个PyTorch。五次作业,用Python,C++和CUDA,实现Tensor,autodiff这种基础设施和一堆算子,然后搭一些简单的CNN和RNN。作业的writeup和测试还是有不少问题,而且它的基本思路就不是很合理。为了检查正确性,它要求你的实现和标准答案的输出在误差范围内相同,包括神经网络的正确率这种数据。但是很多要求是相当模糊的,很容易写出符合要求但是结果不完全一致的版本,例如因为浮点运算没有交换律和结合律,(a * b) / c
和a * (b / c)
的结果就是不一样。我们做作业的时候确实就碰到了这种错误,教学团队也觉得不合理,把那个算子的容许误差范围提高了。但是这没有消除本质上的困难,只要有任何地方写的和标准答案不一样,就存在这种风险,即使过了每个单独的测试,最后拼成神经网络时也可能因为误差累积而出错。这样调试起来会非常困难,而且也是毫无意义的,相当于在猜TA是怎么写的,把一个正确的版本改成另一个正确的版本。可能确实没有更好的办法来测试正确性,只能希望他们以后把writeup写的更严谨吧。
还有一个组队自选project,要求拓展现有的框架,比如加一些算子,并且用拓展的内容实现简单的应用。我组队了,但我直接认清形势,放弃幻想,根本没打算让他们参与,自己全写完了。报告让他们稍微参与了一点,但主要内容也是我写的,只有代码的作者才能写出深刻的内容。这里并不是他们在划水,是我直到写完才通知他们,他们可能是有实力也有意愿写的,但我不放心,选了一条最保险的路。
我对数学可能还是有点兴趣的,但是选它的主要原因是我听说这是门水课,没想到中间发生了很多波折。我尝试尽量客观地评价一下吧。我首先声明我绝对尊重教授和TA团队,但在很多其他因素的作用下,这门课的体验变得非常差。
凸优化应该是一门很深刻的课,若干个学期之前它也确实是很难的,但是Yuanzhi Li接管之后把它变简单了很多,他的说法是,凸优化已经过了它的黄金年代,在现代ML中没有太大的作用,因此我们只需要掌握算法和结论的精神,不需要太在意证明过程。课程评分只有作业,基础和bonus题分开计分,只要达到基础部分的50%就是A-,75%就是A。最多六次作业,这学期实际上有五次。每次作业最多可以用14天的late days,所有作业总共可以用28天,这实在是宽松到夸张了,各个方面都体现出它是一门水课。前三次作业确实很简单,我基本没有听课,每次花不到一天就能过一遍ppt加上至少把基础部分全做出来。
但是第四次作业突然变难了,基础部分的两道分值最大,合计超过50%的题我都完全没有头绪,看完ppt和课程录像还是一样,到处找凸优化的教材,最终非常艰难地写出并不是很完善的证明。有人在Piazza里问为什么这么难,Li贴了一段Machine Learning Department给他的警告,大意是,“凸优化是很重要的课,然而自从你接手以来,课程材料被严重淡化,变得完全没有挑战性,几乎每个学生都能得到A或A-”。他说他不能反抗系里的决定,所以不得不提升剩余的作业的难度。
我觉得这个难度也还可以算是正常,之前确实太简单了。然而第五次作业一直拖到期末考试周才布置,这其实也可以理解,因为需要时间重新设计作业。但它的难度再次远超了第四次作业,并且基本可以看作不能用late days。我是没有考试,但很多人需要在同时有考试的情况下在不到一周内做这次作业。不少人直接就放弃了,其实我也完全没必要做,靠前面的分数肯定能拿到A-,但是我必须拿A,既然是我自己的选择,我也不能抱怨谁。我感觉每一道题都是一个全新的领域,什么Stochastic Difference Equation,Stein method,我不得不自己去找教科书,甚至是论文来入门。后两次作业涉及了太多高级知识,远超课程本身的内容,它也没有列什么先修。如果确实如Li最初所说,只要求感性地理解结论,那么即使没有基础知识也是可能的,但现实情况是没法用这些感性理解来做后两次作业。折磨了大半周,做了一小半,剩下的实在是不会做了。如果我有自己大一的数学水平,也许还能挑战一下,现在早就全忘光了。
最后还是如愿拿到了A。从我自己的视角来看,这课的整体难度仍然是偏向于轻松的,但难度分配太不平均了,不是所有人都能花一整个期末考试周来做作业。也许下个学期课程设计会变得更合理,但肯定不会回到前几个学期那种水课的状态了。
到这里就结束了
这学期我当了15-746 Storage Systems和15-618: Parallel Computer Architecture and Programming两门课的TA。这其中还有些波折,一开始我申请了两门,但系里只批准了PP,我到处问好像也没法解决,但后来它又把SS加上去了。
PP这个课什么都有,书面作业,编程作业,project,考试,课上quiz,TA的工作量也大得多,而且TA人数还更少。PP每周两次,每次两小时office hour,SS每周一次一小时。
PP的后两次作业基本完全是由我设计的。教授选了三个TA负责,另外两个都在划水,就连这种组队我也还是不得不自己做了所有事。按照惯例后两次作业是从题库里选的,但教授认为之前的版本过于简单了,我也认同,HW4几乎是重做了。我是很担心作业出问题的,这是我的责任,感觉哪里没做好都要受千夫所指,我并不相信别人会体谅我,因为我并不会体谅别人。
SS第一个project我还回答了一些问题,第二个project就少了很多。冬天到了之后我每天很早就回家了,所以晚上在INI写的人也没法问我。很惭愧,我一直觉得没有怎么帮到别人。
做两份TA工资并不会增加,工资是按自觉填的工时来发的,每周大约$300,我觉得是挺多的了,完全可以cover我的房租和生活费用。当然学费还是太贵了,还是得父母出,我以后会还给他们的。听说有些学校当TA可以免学费,这对于CMU是不可想象的。
2022.06.25和同事一起出去玩,他们提到摄影,看着旧金山美丽的风景,我也真想买个相机记录下来。在他们的推荐下买了索尼的A7M4,之后还买了几个镜头,24-70mm F2.8 GM II,70-200mm f/2.8 GM OSS II,这真是挺贵的。我确实不需要这么好的镜头,尤其是我极少拍人,根本用不上大光圈。但是很遗憾,我确实买得起,所以也不愿意用便宜的。不过我还是很确信我需要的就是变焦镜头,定焦对我更没有意义。
学期当中发现了延时摄影这个东西,很有意思,真是打发时间的利器。可惜的是前期拍的几次间隔都太长了,合成出来只有十几秒,我当时觉得没人愿意看更长的视频,其实加上配乐之后更长的视频看起来也会心情很舒畅,现在也没有机会再回到那些地方去拍了。
拍摄于2022.11.23,West End Overlook Park。
坐在公交上我想到了发朋友圈的文案,“想起许多长江边踽踽独行的夜晚”,其实走路的时候并没有想这么多,但休息下来,疲劳酸疼的腿真的让我想到了。这句话确实很有我的风格,从那时到现在,甚至从出生到死亡,“我一直都是一个人啊”。 - 节选自《2022.11.23》
拍摄于2022.11.24,Herrs Island。
回想起来有种不真实的幸福感
《2022.11.24》是我2022年最长的一篇日记,但是这次真的拍的不太好。
拍摄于2022.11.27,Gates and Hillman Centers 6th floor。
拍摄于2022.12.20,Gerst Way Pedestrian Bridge。
噪音让我感觉到和世界隔绝了,记忆中2022.06.25在金门大桥上骑车和这很像。太阳下山之后特别冷,我有一次看到相机没有在充电,怀疑是因为充电宝温度太低,把它放进袖子里,用手臂贴紧它。有几次冷风吹来,我唯一的取暖手段是蹦跳。最后我想换个角度拍一下天桥,搬动三脚架时手接触到金属部件,真是冻的生疼。 - 节选自《2022.12.20》
2022.08.29从西雅图回到匹兹堡。之前住的Amberson Plaza不能租半年,想着之后转租会比较麻烦,就搬到松鼠山一个能租半年的公寓去了。这个公寓确实挺老挺破的,可能这就是它能租半年的原因。所以我实在不想待在家里,有日记为证,在匹兹堡的literally每一天我都去了INI。
一开始在其他人的介绍下认识了一批秋季入学的新生,后来再碰到的人我几乎都不认识了,我真的就是没有勇气去问。
2022.09.02有个INI的活动,坐游船在匹兹堡的河上观光,很多人参加。我总是四处徘徊试图找人说话,等到真的开始对话一句话也接不上来,这是我参加集体活动的常态。
“天气挺不错的嘛,要是能早点这么想就好了。”
好吧,看来大光圈还是有点用的,这是套机镜头拍的,这噪点简直没法看了。
2022.09.18去Carrie Blast Furnaces玩了一趟,这是一个废弃的钢铁厂,还是挺有意思的。但是不能指望我对这种东西产生任何感触,去了解它的历史文化什么的,我不是这种人。
2022.10.12尝试去downtown的DMV考Permit,不出意料被拒绝了,因为我的I-20有效期不足6个月,同时OPT还没开始,即使已经拿到EAD卡也不行。据说让不让考完全是看工作人员心情的,但他按规章办事我也没有什么可说的。听说Bridgeville的DMV更加宽松,但我也懒得去尝试了,我本来就不需要开车。
秋假去纽约玩,主要目的是去看S赛。观赛体验肯定是不如线上的,我的视力很差,也听不懂现场的英文解说,也没有弹幕看乐子。但是现场看比赛为的就是一个氛围,像我这种不懂足球的人,后来和人群一起看世界杯也会激动地跳起来,但一个人就是不会。英雄联盟的线下赛氛围也很好,尤其是GAM赢TES,当时真的乐死我了。
纽约也是很有意思的,你可能会发现我只会这一个形容词,没办法,文化太贫瘠了。我很享受在High Line上散步,但我是绝对不会去博物馆的。
2022.11.01,我的电脑坏了。第一反应是震惊,我的电脑居然会坏,我一直觉得能把电脑弄坏肯定是人有问题,但它确实就是坏了。自己尝试碰运气修了一下,取下电池释放静电之类的,并没有任何作用。这段时间借czj的电脑用,他有好几台电脑。在美国修电脑可太麻烦了,已经过了联想的一年保修,我给客服打电话,似乎付费也不能修。Best Buy也许可以修,但在网上查到不少负面评价,可能修的价格比原价还贵,而且匹兹堡的店也比较偏远。最后把它送去uBreakiFix碰碰运气,2022.11.11送过去,到2022.12.16都没有任何进展,我打电话说我就快搬走了,如果你们真的能修,能不能给我送到加州,他们说不行,现在给我看一下吧,也就是说这一个多月里根本就没有开始修。然后很快打来电话,说修不了,让我拿走。
最终在搬家时我把它扔掉了,当然硬盘取出来了,非苹果的电脑还是有这一点好处的。它几乎陪我度过了整个CMU,但没能等到黎明的曙光。
去湾区之后我自己装了一台台式机。Ryzen 7950X + RTX 4090,不算显示器4000刀,换算成人民币和国内的价格差不多,或者稍贵一点。为什么选4090,还是那句话,很遗憾,我确实买得起,所以也不愿意用便宜的。我真就是用4090玩英雄联盟,虽然也会修图,剪视频,但都是很轻的负载。为什么选AMD的CPU,这就有点渊源了。我的前三台电脑是:Ryzen 2700 + GTX 1060,Ryzen 4800H + GTX 1660(笔记本),Ryzen 5800H + RTX 3060(笔记本),我对AMD还是有点信仰的。
2022.11.08有一场月全食,碰巧这是我的生日。3:20起来拍照,在中国应该是同一天的傍晚。前一天晚上还是晴天,但凌晨天空中一直笼罩着薄薄一层云。拍出来效果太差了,太可惜了。我毫不怀疑在地球上的其他地方手机真能拍的比它好,风景本身比设备重要得多。
人类诞生以前,灭亡以后很多年,太阳系第三行星和它的卫星都会如约精确地表演这段舞蹈。我作为一个微不足道的人类个体,碰巧在出生后23年整的凌晨观测它,所能做的也只有忠诚地记录。
我梦想融入这永恒。
我曾经感慨于百万光年之外的光子,跨越如此遥远的时空洒在我的身上,诉说着遥远的过去那颗星星的孤独。但是现在我更喜欢月亮:它是一面镜子,不管希望多么渺茫,这面万有引力维系的镜子总有可能把路灯在我身上反射的光子,或者是可见光之外的黑体辐射,反射进她的眼睛。那闪耀的银白光芒中,也可能就有一个来自于她的光子。星星们太远了,太亮了,作为镜子,它们还不如尘埃。我还想过,即使真的有这样一个奇迹,两个百万光年的征途都没能阻挡那个光子,等它回来,柔如草芥的生命也早已沦为一滩弥散于空间的粒子。后来又自觉可笑,八光分外的太阳不也是一颗星星吗,真是想的莫名其妙。 - 节选自《2016.12.31》
高中的同学们没见过这些文字吧,以前我怎么肯跟人说。那时的日记总是有点文艺的,虽然也是很幼稚很好笑的,现在嘛,高情商的说法是写实。
关于生日还有一件事,在别人的对话中我听到过很多次“给你好好办个生日聚会”之类的,我觉得这是难以想象的,首先我就不知道,如果不是我写在这里,别人为什么会知道我的生日呢。本来这也很正常,有人在乎,找一群朋友热热闹闹庆祝一下,有人不在乎,那就像完全没这事一样。但我偏偏是在乎,却不知道怎么做。
… 我说我也在国内生活了二十几年,我难道不知道那是什么样子的吗,我只觉得所有人都讨厌我。我这样说大家当然都会安慰我,尤其是zjz,他说如果有人因为我优秀而讨厌我,那是他们的问题。但是并不是这样的,他们讨厌我是因为我的性格有根本的缺陷,长的也太丑了。安慰不会改变事实,我也并不能脱离其他人而活着。 - 节选自《2022.12.19》
2022.12.22去参加了rh的生日聚会,他们能邀请我真的应该感到非常荣幸和幸运。饭后大家聊了很久,我基本上记下来了每个人说的话。我当然也想说,也说了一点梗概,但是没有人继续往下问了。
听着这些故事,我最大的感触是,如果人都是这样的话,可以说我这一辈子从来就没有哪怕接近过恋爱。 - 节选自《2022.12.22》
没关系的,都一样。
在我离开前几天匹兹堡下了很大的雪,零下18度左右,其实也说不上特别冷吧,但我还是穿的太少了。我有且仅有一件羽绒服,我根本不打算把它带去湾区,在此之前就送人了。但我依然去了INI,虽然只是为了放一下东西,然后在校园里拍拍照。
实在是太冷了,每一次户外的行走脸都冻的疼。我经常感叹,我写了这么多流水账,驾驭文字的能力没有丝毫提升,我依然没法刻画这种感觉。两天之后到了湾区,我就会彻底忘记它。 - 节选自《2022.12.23》
2022.12.24,打包所有的东西,也就是两个箱子,装不下的就扔了。拎着它们去INI,因为坐公交去机场必须在这里转一站。然后是开头那一段话。晚上9:30上了公交,在机场很艰难的睡了两三个小时,我根本就没考虑过住酒店。在圣诞节当天到了湾区,因为这一天的机票在十天里最便宜。事实证明这非常不方便,我连枕头被子都没有,当天所有的店也都不开门,只能盖着很薄的外套睡觉。又只睡了三四个小时就冻醒了,翻来覆去冷的睡不着,煎熬到六点起来,看到了很漂亮的云,我兴奋地朝日出的方向跑,但最后也没有找到特别适合拍照的角度,后来也没有再见到这样的云了。
2023.01.01,只有一个人对我说了新年祝福,我的朋友实在是太少了,经常会有一些奇怪的“只有”。如果你在看的话我还是对你说一声谢谢,虽然这也没什么意义。这次生日因为我发了朋友圈,还是有几条祝福的。2021年我的生日,除了妈妈之外也只有一个人对我说了生日祝福。如果你在看的话我也对你说一声谢谢。
有一个小细节,从2021切换到2022的时候我几乎没有写错过日记的日期,但最近20天里我几乎有一半的日记,抬手就是一个2022。
2022.10.25妈妈说要申请B签来看我,让我给她发我的I-20和邀请信。我为此焦虑了一段时间,2022.11.20她终于告诉我签证被拒了,当时我情不自禁地喊出来了,好__,开香槟咯。他们再也不能干涉我的命运。
真的不能吗?这是我最害怕的事情。
开始工作之后我的心态没有丝毫的转变,就像之前临时住了半年的公寓一样,现在的一切都是临时的。我真的很喜欢这里,真的很想永远留下来,但是我的想法从来都没有任何意义,未来依然充满了不确定。当然,说是充满,其实是很具体的几件:能不能抽中H-1B,会不会被裁员,绿卡排期要等多久。这是我眼下能看到的余生中仅有的重要的事情,但它们都不掌握在我的手上。每天担惊受怕的日子到底要到什么时候啊!我应该清楚,不太可能短于十年。
这里有一个很有趣的问题,十年之后我还活着吗?
]]>我想起2016.08.05,清华暑期学校晚会后我发的说说,“欢呼是留给那些不欢呼就找不到存在感的人的”,那时候真是志得意满,年少轻狂啊。这句话本身可能没错,但很遗憾我就是自己描述的这种人,而且我还失去了欢呼的能力,所以我就是没有存在感。这个词好像被用的过于泛滥,以至于意思削弱了,这其实是个很重的表述:我感觉我不存在于这个世界上。 - 节选自《2022.06.20》
2022.01.19到2022.02.26,我度过了相当艰难的一段时间,细节完全不能透露,当然肯定也没人关心。这一整篇总结都写的很一般,因为我不愿意写完整的事实。现在回想这段时间,最主要的感觉是尴尬。想要嘲笑我都可以随便,我自己心里已经嘲笑过很多次了。
后面半段日记全程哭着写完,又是那种熟悉的,“从心脏延伸到指尖的酸痛”。按平时的习惯,写出来的字通常要简单读一遍,现在读的声音都是颤抖的。第一次在日记中记录这种感觉是2016.02.11,但在此之前肯定还有,只是没有明确提出这个概念,比如2015.10.23。上一次记录是2021.01.20,后来很顺利地度过了大四下,甚至觉得自己已经习惯了孤独,应该不会再有这样的痛苦了。
…
“我真的一定要孤单度过一生吗?” - 节选自《2022.02.26》
我自己看着这些日期,每一个背后都是相当悲惨的故事,偶尔会有一种奇妙的成就感,史诗感。但读者肯定只会觉得莫名其妙,什么nt谜语人。
也许孤单度过一生也没有那么不能接受吧,但我总是没法彻底放弃尝试,因为我忍不住设想,但凡有一个人能陪我,那该有多幸福啊。这句话是字面意思,就是我不知道它具体是什么感觉,毕竟我从来没有体验过。
这些都已经过去很久了,碰巧我现在心情还行,不是很能与过去的自己共情,我也不想再写什么了。说到这个,我感觉我最大的问题就是不能共情,别人的事我是发自内心的不关心,也不擅长装成关心的样子,这样的性格想拥有朋友真是痴人说梦。每个人都是独特的,但人和人之间并不是只有“不同”这种关系,而就是有高低好坏,我的性格无疑是坏的。
接下来其实都是比较有意思的部分。我在CMU过的总体来说很压抑,这是因为有意思的事太少了,做完作业之后多数时间都是整天整天的无聊。
高中积累作文素材时记过一句话,叔本华说:“人生实如钟摆,在痛苦与倦怠中徘徊”,应该从来没有真正在作文里用到过。但现在看好像挺有道理,至少对我是很贴切的,有作业的时候我会非常焦虑,完全不会考虑ddl,甚至是在作业还没发布的时候,我就是必须马上把它做出来,这里的痛苦是不少的。但是做完之后,我实在想不到我还能做什么。很多人对我说我这种性格应该去读PhD,我知道自己读不了,这种劲头仅限于有确定结果的事,也就是广义的做题,我害怕探索,不能接受失败。
我单挑了,不过这是因为我提前“几乎”全做完了。
https://www.1point3acres.com/bbs/thread-198949-1-1.html,这里面说的基本符合我的体验,我就不重复了,但有几点需要修正:
所有project,slide等都是公开的,在https://www.cs.cmu.edu/~410/。现在可能显示看不到,不过只要加上年份后缀,比如https://www.cs.cmu.edu/~410-s22/projects.html,看往年的就行了,我也就是用往年的handout和starter code提前做的。读者可以自行验证以上观点。
我从很早开始就想选这门课,早到2021.05.26,那一天我就做了P0。当时啥都不知道,打算第一学期就选它。后来当然知道这是不切实际的,但肯定不是我做不到,只是教授不会批准。开学后这事一直放着,但正如我所说,我一直非常闲,从2021.11.04就开始做了。断断续续做了很久,也没有一个明确的日期能认为做完了。总之到开学之前我已经做完了P0到P3,但还没加注释,没有特别仔细地测试过,所以说是“几乎”。事实上剩余的工作量还是远超我的想象。
开学后差点没选上课。这门课是很抢手的(因为学生容量不大),需要academic advisor批准后向OS的教授推荐,然后他根据你的项目,上过的系统课数目和成绩,毕业学期来考虑捞人,我并没有什么优势。我一直在邮件骚扰教授,说我已经做完了project,请务必捞我,但他也不理。一周之后还是没捞,开始考虑备选方案15-662 Computer Graphics,和清华的计算机图形学基础差不多。我也并不是很感兴趣,可以说想选纯粹是因为学过。闲着也是闲着,直接开始提前做它的project,选课在第三周截止,到第二周周六得知我被捞了,已经几乎做完了662的四个project中的前三个,严谨起见也得说是几乎。没有任何犹豫直接把662退了。
之后开始加注释,做一些早就计划好的重构,然后仔细地测试。这时才发现了很多问题,花了一两周调试。几乎全是race相关的bug,多数都非常难复现和调试。这门课用Simics来模拟运行OS,而不是多数人更熟悉的QEMU。
QEMU obtains better performance than single-instruction intepreters such as Bochs or sub-instruction simulators such as Simics, but this performance comes at the cost of reduced fidelity. QEMU frequently runs correct code correctly, but it also frequently runs incorrect code incorrectly. - 节选自 https://www.cs.cmu.edu/~410/doc/enthusiast.html
QEMU能正确运行正确的代码,但不一定能暴露出错误的代码中的错误。有的bug要在Simics上跑个一两小时才能复现,但这至少是可以复现,在QEMU上就完全无法复现。不知道是不是我的配置有问题,Simics的调试功能完全是残废,几乎不能解析表达式,各种找不到符号,没有类型信息,struct字段都按内存偏移量0显示,有好几次我忘了这事,看值都一样还以为我的问题。函数调用栈也经常看不到,更别说前几层函数的局部变量,有时候必须读出一大片栈内存,然后逐个字节自己理解,按汇编里的栈大小往上找。因此想看bug现场信息非常困难,但提前输出信息几乎也不可能,普通一点的加个log就无法复现,有些bug加上一个不会触发的assert
都无法复现。
四点多惊醒,特别真实地梦到mutex又出bug了,我在Simics里调试,看到state是我设置的zombie值。类似以前很多梦中做题的经历,感觉特别无奈,精力没法集中,只能看着干着急。直到醒来才发现这是个梦,应该没有比这更可怕的噩梦了吧。醒来后我还完全沉浸于其中,我的第一反应是觉得很可惜,因为我没记住是哪个测例,不然可以直接去测。 - 节选自《2022.02.13》
有些bug发生在我从没怀疑过的基础设施,比如梦里的mutex就是睡前才解决的。调试过程中经常遇到修改一些无关的代码,似乎就能work了,但OS的世界里绝对没有解释不清楚的bug。最终我可以说我解决了所有认知范围内的问题,得到了一个相当稳定的版本。讲道理如果不考虑工作量,这项目就该一个人做,随便一个小bug都是牵一发而动全身,两个人写实在不靠谱。
P3/P4代码行数如下:
其实也不多吧,但有两点需要说明,一是相比别人的工作,这应该是相当精简的,普遍应该在五千行左右。二是调试才是最困难的,至少要占一半的工作量。
每个project都有一些公开和隐藏的测例,到最后也不会知道隐藏测例是什么,以及我们是否过了。这些都只是辅助评分,可以认为code style分占100%:助教会打印并逐行阅读代码,手写评语,分析设计是否符合要求,是否有潜藏的bug,测例能触发和不能触发的bug是一视同仁的。当然,本质上找bug肯定是个不可计算问题,不排除有人能逃过助教的判罚,我只能说他们水平确实挺高的。
考试需要手写代码,强烈推荐用修正带,代码几乎不可能一遍写成。用铅笔先写再誊抄当然也可以,但会比较慢,而且抄的过程中又发现问题还是很麻烦。修正带是我从中国带来的,我已经说了很多年,只要我还写一天字就会用一天修正带,不过以后大概也不会写字了。
他们说可以借我,但我坚持要回家拿,还是这一套用着顺手。笔袋从高中开始用,这种笔从高二下学导数时开始用。它们见证了我最纯粹的快乐:为自己的理想而努力,至少当时自以为如此。我对它们一直有些很中二的想法,握着笔像是曹操横槊赋诗:“我持此槊,破黄巾,擒吕布…纵横天下,所向披靡,不负大丈夫之志!” - 节选自《2022.03.02》
期末考试没有样卷,不过我把考题记下来了,回家之后直接默写了出来,不过为了保险起见我还是不分享了。
讲课内容其实不是很重要,我怀疑所有课的内容都不重要,不过为了完整性还是记录一下。前半学期就是正常的OS的各种原理,和清华的操作系统差不多,但顺序差别很大:
以前OS启动能考一大堆八股,都是些x86的奇怪的细节,MBR的512个字节,从实模式切换到保护模式,ljmp
刷新CS缓存之类的。这些过于底层且古老的东西真没什么用。当时学虚拟内存的时候很不理解为什么需要这么复杂的机制,之后讲到进程才理解它是用来实现地址空间的隔离。我觉得CMU的顺序显著地更好,从OS提供给用户的抽象讲起,启动放在很后面,而且只是简单过一遍。它的教学和考试都更注重设计而不是知识,或者训练人肉算法解释器。手动跑几个页面替换算法,从页表里读地址,确实不能增加对OS的理解。
后半学期是些高级内容,文件系统,虚拟化,安全,密码学之类的,考试几乎不涉及其中的细节,核心考点还是同步互斥。
我单挑了。
课程内容和清华的高性能计算导论差不多,主要内容都是并行编程的理论和工具,都有基本的OpenMP,MPI,CUDA。这门课还涉及一些无锁编程,异构调度,DSL之类的拓展知识,不过我基本也都接触过。我说过很多次了,我不愿意探索新东西,我选的课基本都是多少已经学过的。
陈天奇讲课没有什么突出之处,比另一个教授Nathan Beckmann还要乏味一些。可能是因为这和他的研究方向也没多大关系。秋季的PP由Todd Mowry教,他创造了PP这门课,他的要求会严格很多。
一共四个assignment和一个自选project。前两个assignment每年都一样,A1是写报告,做一些小实验和问答题。A2用CUDA写圆形渲染器,我觉得它的并行策略设计是最有意思的。A3和A4每年都换,但也是从一个不太大的题库中挑的,而且一定是同一个主题,A3用OpenMP,A4用MPI,共享内存和消息传递的差别确实会影响算法设计。这就比高性能计算导论强很多,它的几个作业基本就是把同一个算法翻译成各种版本。听说它后来改了不少,我看过新的页面,现在应该更有趣了。
我做的project是无锁二叉树和跳表,其实都是照着论文写,我感觉如果能找到靠谱的论文,写起来还是很简单的。报告终于可以放在公开仓库了:https://github.com/MashPlant/15618-Project,不过大概也不能公开代码。一般人只会做一个数据结构,我觉得只做一个含金量不是很够,就做了两个,也可以说是做对比。
期中和期末考试都是take home exam,期中还挺难的,我做了四五个小时,期末简单很多。不过这课的秋季版本都是闭卷考试,时间限制也很紧,所以春季确实轻松很多。
我单挑了。
MSIN必修14-736 Distributed Systems,不能选15-640 Distributed Systems,我们这一届很少有人成功申请替换过,听说下一届的申请基本都成功了。
Patrick延续了14-760的稳定发挥,几乎每次作业都能延迟发布,关键是作业相比去年也几乎没有变化,这都能拖到底是干什么去了?实在要说可以理解的话,他教的课确实太多了,根本责任还是在限制我们选课的INI。
一共四个lab和一个自选project,都是可以组队的。lab 0确实是Patrick今年新造的,因此有一堆问题,我劝他还是别造了,没那个能力知道吧。它是用socket写一个没有任何意义的小游戏,目的是训练网络编程…只能说就挺无语的,和后面的lab也没有任何关系。lab 123都和往年一样,分别是RPC模拟框架,Raft,分布式文件系统。他的Raft是抄MIT的。分布式文件系统,并不是很分布式,直接不需要错误处理,假定机器都是可靠的,同步通信就可以,完全退化成了简单的业务代码。
lab 012只能选Go或Java,后面的lab 3和自选project可以任选语言,我就用Rust了。身边有同学反馈M1 Mac跑Java会有奇怪的问题,我也不懂为什么,只是记录一下给后人看。
project是从几个pre-approved的主题中选择,或者自己申请另外的主题。多数人选了消息队列,我做的是blockchain。
课几乎完全没上,什么Raft,Paxos,没听说过。
我单挑了。哦本来就是单人的,那没事了。
虽然有不少人来问我,我自己其实全靠n+e指导。总结就引用他的吧:https://trinkle23897.github.io/posts/cmu-1st-year#15-719-advanced-cloud-computing。
个人体验,难度排序为:P23 > P22 > P11 > P21 > P13 > P12 > P32 > P31 > P30。平均每个耗时一天左右,P3拉低了不少平均值。
学期当中我闲着没事干也做了10-605: ML with Large Datasets的作业,感觉ACC的Spark使用比它复杂太多了,所以我也不是很理解n+e的想法,感觉先学过605并不能让ACC变得简单。
Greg一直很关心学生的就业。Greg和Majd两位教授非常贴心,众所周知2022秋的就业市场对学生非常不利,他们群发了邮件,说你在ACC这门课上表现地很好,如果offer被撕了或者开始时间特别晚,邀请你下学期来帮助改进ACC这门课,从而不至于因为没有工作被迫离开美国。他们真的好温柔,我哭死。
和APD1类似,区别在于大家都知道讨论问题没什么意义了,其实从APD1后期开始就是这样,每次上课默契地开一个Google文档,直接在里面写“讨论”的内容。
我一直以来的一个遗憾就是没有真正地在中国实习过。也许失去了一些交朋友的机会,但更重要的是,我没有机会亲眼看看中国的互联网公司到底是什么样的。那我只能把它往最坏的想了,这是我不愿意回国的理由之一,重要性大概排第三。
回想大学四年,可能只有没找实习称得上是失误,其他的遗憾,比如做不了研究,没有认识更多朋友,都是能力限制,再让我做一遍也是一样。直到大四下我才意识到有实习这个东西的存在,而且也没想到可以去互联网公司,我觉得像我这么热爱工作,它们应该会欢迎我的。最终就去了那个垃圾公司,浪费了最后半年。甚至可以说它直接降低了我对中国的评价,美国就不会有这种不正规的实习。 - 2022.05.17 朋友圈
我对很多人说过,我在清华四年,根本就不知道腾讯,字节这些公司,居然是招实习生的。首要责任当然在我自己,我没有收集信息的意识和能力,但是我还是想抱怨一下,我事实上就是没有听说过这种东西。当时我唯一的社交圈就是寝室,这里没有;微信群里确实有不少人转发实习广告,但都是和AI相关的研究岗位,我以为也只有这种公司招实习生;中介也只说要找暑研,即使我的目标就是申MS然后工作。
后面的一个学期我当了TA,参加了一些强制的TA Seminar,我觉得放在这里非常合适。
他多次提到为面试科技公司做准备。这里有很多本科TA,他也清楚这一点。这是清华一直没有给我的:一个明确的目标,明明大多数本科生最后都只能平庸地工作,为什么不早点告诉我,我也不至于没有正经的实习。清华试图证明自己的学生可以科研,社工,参军,运动,还就那个礼乐射御书数全面发展,但就是不说最后多数人都是要工作的。 - 节选自《2022.11.30》
不管之前这些事了。5.16-8.05,我在Databricks度过了非常充实和快乐的一段时间。你看,我就说了不是我的问题。但是也没什么可说的,每天的日记都是解决的问题的细节,而不是大家都能理解的情绪。细节我也完全不透露了,谁知道会不会有什么法律问题呢。工作体验很好是因为几乎不需要我设计,上手就能写,和我的一些同事比起来感觉非常幸运。
没做完的时候特别着急,有段时间大约是877,这完全是我自己选的,周末不加班也没别的事可做。做完之后剩了一个月没什么事做,体验稍微下降了一点。经常有人抱怨WLB不好,但我感觉大家也都挺喜欢这里的,抱怨真的就只是随口说一句。对于我来说没有什么WLB,我的life就是work,work就是life,在可以预计的未来这一点都不太可能改变。
这个世界上,似乎并没有我的位置。 //最后一句也许看起来莫名其妙,以后再看时,可好好琢磨琢磨那时的想法 - 节选自《2018.02.20》
其实只要有工作我就会很快乐,别琢磨了,都是闲出来的。
今天很多人都发了朋友圈纪念离职,我也不能免俗。憋了很久才找到九张图,其中一张是mentor的最后一条PR评论:刚才的饭局里我看到最后一个PR的测试挂了,没有VPN没法重启,留了条评论让别人帮我merge。他回复说没问题,Thanks for all of your awesome work! 饭桌上扫一眼没有任何感觉,自己一个人读完就哭了出来,我终于感觉到还是有人关心我的。最终朋友圈内容如下:
“我对许多同事都说了,这是我度过的最充实和快乐的暑假,因为我可以做自己感兴趣的事,而且你们是我遇到过的对我最友好的一群人。
不过我总觉得,这应该也不是太高的要求,为什么之前从没有满足过。我想世界上也许还是有我的位置,只是注定局限在这一方小小的电脑屏幕里。”
我总是要折衷一下的,纯粹的乐观并不符合我的人物形象。 - 节选自《2022.08.05》
没有太多有感触的事,简单记一下生活的概要吧。
翻看那时的日记,发现当时做梦的频率相当高,醒来后经常能回忆起相当生动的剧情,现在很少了。
开学之前特别无聊,外面还一直下雪。我一直在家玩英雄联盟,我知道这不是什么好游戏,我玩的很一般,也并没有多么喜欢玩,只是实在是没有别的办法打发时间,感觉其他游戏上手难度都太高了。有段时间过于无聊了,甚至想做compiler (15-611 Compiler Design),它的starter code是不公开的,但我找人要到了。但我看了之后感觉它几乎就是完整地实现C语言,重复自己已经做过好几次的事,再往上堆砌更多的工作量,真的不是很有意思。而且后来知道它之前都是秋季开的,但是今年改成春季开,我并没有机会上,毕竟还是不敢同一个学期上OS和compiler。
然后是这个。
哪怕记住这些过去,还是会一遍遍犯错,并且未来或过去看到这时的记录,一定是完全不理解。一个人不同时刻的心理状况尚且不能互相理解,更何况不同人。这些话说的都非常保守,敏感,因为根本上我不相信我可以拥有陪伴,这是从来都没有过的东西。 - 节选自《2022.01.13》
大半个月在做OS。
我尝试了一次滑雪,然后决定再也不滑了。我做任何事情都一定要做好,不可能从做不好的事情中找到任何乐趣,尤其做不好还会让我感觉生命受到威胁。也许实际上是很安全的,但我的感受也是真实的。
然后是这个。
不出生是最好的,一切痛苦都不必承受了,但既然已经出生,只剩次优的选择,艰难地活下去。
OS期中考试前,和n+e和他的队友一起复习。
走在路上有种沧海桑田的感觉,上次和他吃饭是2019年夏天在观畴园,那时我问他“我知道很多课讲的内容都没有意义,比如网原,我应该把心思放在科研上,但我就是忍不住卷成绩,我该怎么办?”,他可能是叫我不要太在乎成绩,记不清了,提问只是因为我害怕,本来我也不可能采纳。在这之前以及之后的很多路,我都是跟着他在走,最终能在异国他乡见面也算是缘分吧。 - 节选自《2022.03.02》
春假很多人出去玩,我也想参与进去。根据我的经验,我在群里问有没有人一起,结果几乎一定是没有回应。我会觉得这是自取其辱,所以很不愿意发。尽管我这样想,我也还是发了,但结果也确实是没人理我。无奈只能写OS P4,四天写完了,剩下的时间又没事干了。
p4大概写了四天,实验指导这句话说的真好。
“这也是我眼中美丽的风景,不过我也总觉得要是能有人和我一起欣赏就更好了。” - 2022.03.07 朋友圈
之后我偶然想到研究一下rCore,这是清华的一个项目,是用Rust写的OS,有一个相当完善的版本https://github.com/rcore-os/rCore和一些教学用的版本,这两者其实没有太大的关系。教学版本最初只有RISC-V,我碰巧看到@equation314在做ARM的版本,我就想做个x86-64的版本。CMU的OS用的是x86,我也是想学习一下x86-64,它的启动复杂很多但我不用管这部分,页表复杂一点,系统调用和中断复杂一点。花了一周差不多写完了,结果在https://github.com/rcore-os/rCore-Tutorial-v3-x86_64,但是并没有太大兴趣继续完善,使之可以用于教学。
我对Rust有不小的意见。一是CMU的OS的code style里有一个非常强的要求:必须处理所有内存申请失败,现实世界的OS应该也是类似的,但rCore简单很多,并没有这种要求。这对于除了C之外的语言都是非常困难的,它们几乎只能抛异常或者panic,想象一下每一个std::vector::push_back
或者Vec::push
都必须检查是否成功,如果失败需要回滚之前的操作,这样写出来的代码和C语言也差不多了。二是,我对一些人说过,我喜欢Rust除了安全性之外其他所有元素,如果能把借用检查完全去掉,这就是我心中最完美的语言了。因为我事实上就是比编译器聪明,能更好地管理内存。有些地方真的特别适合用链表,手动管理内存那种,你非要来一个Vec<Arc<Mutex<...>>>
,我看着就难受。
去Niagara瀑布玩了一趟,有人能带我出去玩真的很难得。冬天的风景不是很好,有些区域不让进,而且最重要的是,实在是太冷了,我又穿的太少了。我们准备过桥到一个岛上,这时雪突然变大,天地都灰暗了。很遗憾在这种世界奇观面前,生理上的痛苦完全占据了思维。
这是当地的Theodore Roosevelt Inaugural National Historic Site外的一个石柱,上面刻着Time Takes All But Memories,我喜欢这句话,但是并不完全认同。记忆当然非常珍贵,但人脑的记忆是脆弱的,很容易就会被时间抹去。任何其他形式的记录,不管是纸笔还是电子的,都比它更可靠,所以我要写日记。
又没事干了,开始提前做作业,做了10605,15645 (DB),10714 (Deep Learning Systems),这是下学期准备选的三门课,然而最终没有选上DB。其余时间在挂着电视剧当背景音玩英雄联盟。作业都能提前做,上课的意义是什么呢?当然没有意义,谁来读MS不是为了混个OPT呢。哦对了,其他人还可以交朋友,这对我来说可太困难了。
复习了很多天,直到5.06 OS期末考试。复习是很无聊的,考完之后就更无聊了。我经常在INI闲逛,遇到人就问一句“你在干什么”,换位思考一下,这句话是挺有侵犯性的,除非正在做的事情很乐意分享,否则大概率不会很想回答。但是我实在不知道还有什么别的话可以说,而且别人回答之后我通常也接不上。
好不容易熬到5.14,和zjz一起去了旧金山,实习期间我们合租。我对他说了我的部分故事,“有人问我,我就会讲,但是无人来”,我可能愿意对每个人说,但是不能对所有人说。
我自己最近也经常回忆,就像是老年人回看自己的一生一样,初中高中我大概就很害怕和陌生人说话,但对同学还算是热情的,很多老师评价我是“哗众取宠”,真是很贴切的词。但这恐怕也只是因为有个教室把我们每天框在一起,离开这个环境以后就变成彻底的孤独了。有些人可以安于孤独,但我不能,我是个内心世界贫瘠的人,所以一直挣扎着试图改变,到目前为止这带来的都只是痛苦。 - 节选自《2022.05.04》
实习结束之后和zjz一起去西雅图找czj玩了一段时间。这里发生了很多我认为值得记录,并且确实记下来了的对话,但是我审查了一遍之后觉得没有适合公开的内容。
实习开始之后就不戴口罩了,因为周围的人都不戴,戴着也确实有那么一丁点的不舒服。从2020年初武汉封城结束后第一次戴口罩走出家门,到现在终于摘下了口罩。整个实习期间都没有感染,直到在西雅图,2022.08.20才终于第一次阳了。我的评价是普通感冒,不知道发烧没有,因为没测,嗓子疼也就是普通感冒的疼,四五天就好了。2021-03-07里提到,我在北京冬天有两次特别重的感冒,都是持续20天的级别,与之相比这次感染真是小儿科。之后的一学期在匹兹堡每天挤公交,甚至没有过一次感冒症状。
]]>近期n+e写了他的研二上学期,全都是connection,与此完全相反,我的主题是“我将孤单度过一生”。我没学到他的精髓,甚至可能是学到了糟粕。这也没什么,他能力比我强,理应拥有更远大的未来。读的时候我就有感觉,数据也确实证实了,他的约八千字中有93个“我”,我的约一万字中有337个“我”,这还是我反复精简,删去很多主语之后的结果。我的文章,我的世界,唯一的核心永远只能是我自己。
大家出国是为了开拓视野,建立connection,我是为了上课和找工作。
4.26填CMU的I-20申请,6.07收到I-20,直接在使领馆网页预约6.18的面签。n+e的预约时间查询工具帮到了很多人,但我完全没用上,似乎形势一片大好,出国不存在任何障碍。
这里无意提供面签教程,网上有很多资料。我的日记追求极尽还原每个细节,只是因为记住了就顺便写下来,难免很啰嗦。从我自己的体验来看,唯一的建议就是轻松点,这好像是件很容易的事,不知道是不是幸存者偏差。
在门外排长队,几乎全是同龄人,一开始没注意,觉得理所当然,工作人员用“孩子们”称呼我们才意识到,基本只有申F1签的学生,我们马上要离开这个熟悉的地方了。向警卫展示资料后进入,再查一次资料,安检,进入露天区域,柱子上挂着宣传平等的彩虹旗。进室内,几个窗口排小队,叫我去2号口,我看错去了3号,我以为这是面签,怎么这么随便。实际上这是中国工作人员看I-20和护照。排队录指纹,上二楼,这才是。队不算长,十几个窗口,工作人员看哪空了就让队头的人去,没有选队的机会,怪不得只见过上海哪个vo比较好过的经验。
轮到我,是黑色西装的白人男性,看起来很和蔼,不知道这个头发症状叫什么,就是谢顶的补集。前面男生很快通过,蓝单子。第一句我就没听清,模仿他交护照和I-20,然后又没听清,直接问pardon?是要CV和学习计划。问父母职业,我按准备磕磕绊绊说了,但把consulting说成了counselling,好在差不多都是咨询。问未来计划,还是按准备,我要回国去互联网公司当SDE,例如腾讯网易,我也是真能说的出来。然后他看了很久资料,打了很多字,我的右手因为紧张不自觉地蜷缩。最后问去过美国吗,又没听清,直接问what?回答没去过。他又看了一会,最终说你通过了,刚才在确认资料,抱歉让你等这么久,Have a nice day。啊,那真的很感激他,按他们的说法这应该叫水果,存款/收入证明,户口等资料全都没用上。 - 节选自《2021.06.18》
我的口语和听力水平都挺垃圾的,说实话阅读和写作也一般,现在每天主要靠谷歌翻译。我的最擅长的从来都是考试,而不是英语的某个方面。
我想开学后可以多选点课,所以暑期提前选513,回想起来总觉得这让我失去了很多认识朋友的机会。外国学生只能看录像,不过我连录像也没看过。前期还认真看过几篇ppt,之后只为了做书面作业零星查阅几次,到考试复习时再看一遍就够了。
清华的汇编课很多内容是抄513的,似乎不同年还会抄不同的内容,我们做过Bomb和Attack。不过不知道它现在改成什么样了,我们那时是暑期小学期上,听说好像变成正常的课了?
我眼中最难的两次lab是Data和Cache,我可以明确地说,对于我而言,它们都比Malloc难。Data是在一定的操作次数内,用位运算实现很多功能,Cache是设计矩阵转置的方法,降低cache miss。都像是脑筋急转弯,这从来不是我的强项,我的智力只能说是一般,每次都要花很长时间才能想到。
开学后我加了513的群,偶尔回答一些问题。似乎有人说,例如某操作的次数实在降不下去,只能这样交了。这我完全无法理解,不是满分肯定就不算做完了啊。
Cache及之后的lab我都是在布置之前做的。我总是特别焦虑,缺乏安全感,我不仅不会把事情拖到ddl,如果我能提前知道一个实验,只要我没能在它发布之前做完,我就会觉得焦虑。我不能忍受有想不出来的问题,因为我觉得发布之前想不出来,之后也没道理能想出来,ddl前更没道理能。
也是因为提前做,所以排行榜上经常很靠前。我做的是挺快的,但也没那么快,没法一天写完Malloc,怎么说也得两三天,还要为了code style分,再花时间加一大堆我觉得很无聊的注释。这耗时肯定比Data和Cache长,但长不等于难,写代码有个切实的进度,但是否想到正确的方法只有0和1。
实际上布置之后我还得面向测例修改策略。很多人说只要按讲义里的策略就能满分,不知道是我写的有bug还是怎么样,反正就是不行,而且每年测例是会变的。要求说不能依据测例优化,但没有具体解释,理论上来说malloc
本来就可以依据分配模式,改变分配策略,只要不是直接检测名称,也没什么直接证据说我是依据测例优化。
最后的TSH和Proxy就是按要求写代码,“看图说话”,没有技术含量了。据说有人把513的lab写进简历里,我觉得这很不可思议。
8.10回武汉后复习,8.12晚上考试。往年题给我的感觉都很精妙,质量很高,但不知道为什么,这次考试质量非常低。这并不是因为我只会拟合往年题,它实际上比往年题简单太多了,但是有一大堆错误。
复习时做到一道题,背景是用加法实现double转int,大二在知乎上看过,当时不能理解,把它当魔法用在FFT里。但题目循循善诱,这次终于理解了。继续查背景,这个技巧出现在2006年lua5.1,09年考题,15年知乎回答。
…
考试流程突出一个随意。总共只有7题44分,题型和数量和往年题完全不一样。…即使状态这么差还是约1h20m做完,之后是无尽的折磨。很多题没有正确的选项,但几道小题计算相关或方法相同,我肯定担心方法错了。反复做各种无意义的尝试,比如也许是这个数据给错了,把它改改看能不能让所有小题都有正确选项,全是徒劳。接近结束时助教才在Zoom聊天说4.6题目错了,选最接近的,但4.8应该也错了。提前10分钟交卷。 - 节选自《2021.06.18》
double转int具体来说是A fast method to round a double to a 32-bit int explained。
我之所以怀疑自己,是因为根据经验,我选择信任老师和课程。但如果题目出成这样,那出题人的水平应该还不如我,而且一定是极不负责任。注意不是“或者”,是“而且”。
当时是打算在国内学车后,短期内在美国可以用驾照翻译件,长期内可以再考美国的驾照。但实际上我现在并没有勇气开车,也没有考美国驾照的想法。我太喜欢自行车了,希望之后实习,乃至工作时,也可以每天骑自行车通勤。
6.09下午在海淀驾校五道口报名处报名,3880元C2驾照班。然后去做驾驶员体检,路上一直担心要测视力,我不可能合格,果然是的,左眼看不清最低要求,而且我的左眼应该比右眼视力好些。我上大学前配了一副眼镜,大约在大一下,可能是上形式语言与自动机的课上弄丢了。但我上课通常坐在前排,不用眼镜也能看得清,所以一直没再配。只能找人借眼镜再去测。那天下午一直在下雨,一路骑车都淋着冷雨,自己没觉察到什么不舒服,只是特别冷,照镜子才发现嘴唇都紫了。
北京的驾校都在非常偏远的位置,但是有免费的班车,来回各需大约一小时。不知道其他北京的驾校怎么样,至少海淀驾校的学车体验可以说是特别好。提前在网上预约位置后,整节课都是一人一车一教练。武汉的情形是:小时候我跟着看别人学车,几个人共享一辆车,自己练习的时间很少,据说现在也没有什么好转。
我每次都预约下午的课,上午先去公司放书包,做点自己的事,从这里坐班车出发更方便。上完课五点多回来,吃完饭直接走人。它给我留下了那么糟糕的回忆,我消极怠工也是应该的。科目二前上六次课,这有点太多了。侧方停车,直角转弯 + 曲线行驶,倒车入库,三次就学完了,之后三次都算是在复习。科目三前上三次课,第一次就把车开到外面道路上练习,两次课就足够完全熟悉。
6.21考科目一,7.15考科目二,7.28考科目三,7.29考科目四。实际上7.01才上第一节课,时间可以压缩很多,从报名到结束可以控制在一个月左右。
之后在支付宝上申请驾照翻译件,但我到目前为止还没有尝试过在美国开车,所以也不知道这东西有没有用。
最后在北京转转。
8.10回武汉办理户口和档案的迁移,如果我不打算再回去的话,这应该没什么用。最后再见一面若干亲戚。因为担心疫情影响,在武汉没有自己出门的机会,我最终还是没能在东湖再划一次船。8.18飞去上海,准备出发。
后会有期…吗?
我和高中同学wyx结伴出发,他也在CMU,学MSMT。后来有件好玩的事,碰巧这一届还有个MSCB的同学,姓名拼音和生日都和他完全一样,因此他们的医保卡寄错了,是我介绍他们认识才解决的。之前申请学校的时候,有些系统只用姓名和生日登录,我就想过会不会有这样的巧合。
飞行路线是大韩航空的上海 -> 首尔短暂停留 -> 纽约,提前用国内app(携程)买达美航空的纽约 -> 匹兹堡机票。得益于我的记忆力(和随手写的日记),我以分钟级别的精确度记录了出国过程中的每个重要事件,但现在看来实在太无聊了,尽量节选点有用的吧。
这将会是物理上漫长的一天。3:50闹钟叫醒。昨晚删完数据后想到,检查是在入关时,中途还有很长时间写日记,先装回来。4:48到T1航站楼,五点排进队伍,来的早唯一意义是在队伍里排在前面,中国人太喜欢这样了。六点整工作人员沿队伍查I-20,护照,贴个东方航空标签,这个标签之后也没检查过,出境检查也太随意了。6:12排到我们,办值机和托运,再检查护照,核酸,飞美国的证明。6:52通过中国边检和安检。
北京时间9:25于上海浦东国际机场起飞,首尔时间11:55于仁川国际机场落地。12:17排队检疫,近一点才过,只是收单子,看一眼后续行程证明,不知道为什么这么慢。13:26排队转机。韩国和日本人英语都很有特色,找我要P吸R,但我知道她说的是核酸报告。14:22办完,三个窗口,每个人5-6分钟,排这么久也合理。之后我还没忘了在机场做Leetcode每日一题。
首尔时间21:11起飞,前半段睡觉,后半段看电影。约14个小时后,纽约/匹兹堡时间/EDT,同一天的20:12于肯尼迪国际机场落地,有种平白无故省下来半天的感觉。排队过海关,找最长的,中国人最多的队准没错。只查了I-20护照,只问了我学什么,我说Computer Science,这应该不算僭越吧,项目不算正统,说学的是CS总可以吧。我这时还在准备迎接审查,但走出来看到几乎都是外国人,各种商店,最长的队伍是门口的Taxi停车点,反复确认几次,我已经出来,或者说入境了,刚才经过的就是全部检查,我觉得这有点过于顺利了。
坐AirTrain从T2到T4,这是个小楼。不算很困,在椅子上硬坐一晚上。第二天七点过安检,这时很困,全程睡觉,没注意起飞时间,9:45于匹兹堡国际机场落地。感谢zjz的接机项目,坐车到我的公寓,Amberson Plaza。 - 节选自《2021.08.20/21》
我只带了一个小箱子,背书包,美国境内的航班直接没用到托运。出发前我观察,周围的人几乎没有少于一大一小两个箱子。很多人听说我这样之后表示震惊,但我觉得这也没有什么奇怪的把,我只是打算其他东西留到美国之后再买,路上轻松点。我当然是不能理解,为什么有人会有好几箱衣服。
公寓选择Amberson Plaza非常随意,我只想找个离学校近的,可以每天骑车来往。拖到很晚才开始找(签证通过后),很多公寓已经没有房了,所以其实也没多少选择。后来去过Webster同学家里做客,明显比我的好,价格差不多,而且离学校更近。
出发前花了很长时间删敏感照片,聊天记录。其实也没多少,最敏感的可能是军训,天安门之类的。我把它们保存在清华云盘,它在毕业后几个月内都能正常使用,大概十月左右用不了了,网络学堂到现在还能用。不过这些准备都是多余的,因为我完全没被检查。
我没有办理漫游套餐。其他运营商不清楚,联通默认可以直接在境外使用,收短信总是免费的,只是不办套餐的流量会很贵。国外的机场WiFi不需要手机验证码,可以直接联网,微信联系。直到到公寓我都没有使用一字节流量,但最终还是用了一点,在国内预订的Mint Mobile电话卡已经寄到,插上卡尝试激活好几次都提示Act Code错误。尝试各种排列组合,意识到可能需要联网,我很有点心疼,纠结很久,但没有别的选择,硬用流量把它激活了。
提前在Amazon上买好各种家居用品,它们比我先到。一般的公寓都有存放快递的地方,不必担心丢失。
更新:疫情已经结束了。我劝大家多看观察者网,少听那些境外势力的造谣和煽动!
选课时所有人都在建议,这学期要找实习,应该少选点课。我选择相信前人的经验,导致的结果就是这一学期实在是太闲了。后半学期我就开始提前做OS的proj,为下学期选课做准备。后来我知道有不少人选了四门课 + APD,如果选课时我就知道,我一定会再选一门,别人能做到我不可能不行。
“所以你来CMU反而比本科轻松了吗?”那是当然,“如果不主动选OS,编译器这些,CMU肯定比清华本科轻松”,我以为这是理所当然的,我从来都不期望硕士会更忙。她这样问我才想起来,对于很多人来说不是这样的,他们C/C++都没学过,或者大一学过后再不接触,更别说汇编,上个513就要命了,还有很多人转码来这里,压力当然比之前大很多。 - 节选自《2021.12.07》
这里绝对没有歧视转码同学的意思,尤其是从数学,物理之类的转码,我非常敬佩他们,这比计算机难多了。我经常对别人说,计算机可能是我唯一能学会的专业,当然也只是学成当码农的程度,真正的计算机科学,我是没有机会企及的。
绝大多数时候,我都是整天整天的空闲。我在晚上的工作效率非常低,很少在十二点之后睡觉,别人当然也对这表示震惊,但我觉得这只是生活习惯不同。平时最耗时间的工作就是写日记,平均一千字需要一小时多一点,2021年总共超过50万字,大概可以估计总时间。
为什么选?因为n+e选了,我在复现他,之后(目前计划是下下学期)我还打算复现15-645 Database Systems。2022.01.01我在朋友圈宣传大四上及申请总结,他评论说“你太保守了 全在复现别人(我)的经历”。但我还能怎么办呢,没有人在前面确定此路可行,我怎么敢走呢?这是无数次尝试失败的痛苦积累起来的自我保护机制。
实际上体验确实还行。前半学期讲存储底层实现,SSD,磁盘,FS,RAID,今年新加了讲师Huaicheng Li,他讲NVMe,细节有点过于繁琐了。后半学期讲分布式存储,有几次Guest Lecture,我基本都没听。很多课都给我这种感觉,前期很合理,到后面就空洞起来,我对前沿不感兴趣,只想学尘埃落定的东西。根据我的经验,前沿意味着困难,意味着我做不出来,意味着学了也没有用。
lab设计的不错,但和课程也没什么关系。myFTL是模拟SSD控制器,将逻辑页映射到物理页,从而达到尽量好的读写性能,磨损均衡。ckpt1和2就是模拟,而且对程序本身的内存占用没有要求,照着写就完事,不用考虑任何优化。ckpt3是自己设计映射策略,因为n+e的总结,我直接跳过了block-level mapping,尝试失败的感觉是很糟糕的,但因为有他的尝试,我可以避开。这会让我少学到什么东西吗?反正我是想不到,如果有人认为有,请详细地告诉我。同时我可以确定page-level mapping的可行性,那么不管遇到什么困难我都有信心和动力解决。
CloudFS是用FUSE实现可以利用云端存储的文件系统。我在存储技术基础用过FUSE,可以省掉一点学习时间。ckpt1是对单个文件依据大小决定存储位置,需要一种记录文件元信息的机制,我这时设计的不好,到ckpt2必须推倒重写。ckpt2是块级别的存储和去重,写入文件中间涉及相当繁琐的下标运算,并不算难,但很容易出错,我大约花了两天。ckpt3是备份和cache,备份需要仔细处理块的引用计数,cache就用简单的LRU,我大约花了三天。
n+e说他写了两千行,同样的功能我只需要1174(不计注释,空行)/1529(计注释,空行)。我知道这种比较很幼稚,但我总是喜欢敝帚自珍。
要求的paper我一篇都没读,没有影响。考试是开卷的。允许带cheatsheet,但这没有任何意义,因为形式是当堂电脑考试,直接允许访问所有课程资料。而且它很贴心地提供了很多往年题和详尽的解答,也不用刻意拟合,每道题都是系统设计小问题,多读一下积累经验就很好。
因为AIV的限制,课程项目的代码将不能再公开在GitHub上。但在我所知道的所有国内本科,参考往届的实现(只要不是照抄)从来都不被认为是学术不端,反而是被提倡的。
两个字来形容:离谱。
据说以前Greg(Gregory Kesden,不是SS的Greg Ganger)上的时候,这课是比较水的。多数“据说”的来源都是这篇:https://www.1point3acres.com/bbs/thread-617566-1-1.html,我们这一届在选课时很大程度上参考了这篇帖子,但现在换成Patrick,时代就变了。我可以明确地说,Fall 2021的760,workload显著大于SS。这个定语不得不加,谁知道他以后又要玩什么花样。
去教室看Patrick远程Zoom上课,这模式现在很常见,不过我是第一次见。课不是重点,我大概出勤了一半,但都在做自己的事,什么也没听到。
五个单人lab(从0开始计数) + 一个组队自选proj,四次quiz。原计划还有期末考试,不过最后时间不够取消了。
lab0很正常,熟悉虚拟机环境,后续实验都用它的CORE模拟器,我觉得挺好用的,要是大四上的网安工有它就好了。
lab1:请使用Raw Sockets实现TCP协议,需要实现三次握手,四次挥手,滑动窗口,超时重传,IP分块,TCP分块,基本的flow control,congestion control。starter code基本上只有int main() { return 0; }
。
???
当然了,这仍然是非常简化的TCP,其实工作量并不是那么难以想象,我觉得最大的问题是没有starter code,如果规定了编程的范式,在明确的接口上做点代码填空,像SS一样,那应该不会特别难。但这有个重要的前提,那就是他自己(或者助教团队,如果有的话)得提前做过一遍,提炼其中合适的部分,这才是设计实验的精髓。我本以为这应该是天经地义的。
但实际上他根本就没做过,这不是推论,是事实。不仅没做过,而且对工作量没有最基本的估计。原定时间是9.11到9.21,9.17看大家都没做完,改到9.28。9.26说“我在这个周末的大部分时间里都在试验我的lab1实现”。最终彻底取消了ddl,到期末都可以交,不扣分。
到这里相当多的人已经退课了。我基本上是抱着看戏的态度,虽然确实是很难,但大概还没有超出我的能力范围,只是需要反复试错,我愿意相信之后的lab会更好。最后写了七八百行C,勉强算是完成要求。
但遗憾的是,这只是个开始,甚至可以说是之后的每次lab的缩影。每次布置时间都会推迟,因为是学期当中才设计出来的,中途总要在Piazza为实验指导里的含糊不清做各种解释,ddl也总是需要延长。一个经常出现的情形是,他让我们对比几种情形下的数据差异,并分析原因。实际测试,数据没有任何差异。一开始我也会怀疑,现在我倒也不是确信我的实现没问题,只是我没有理由先怀疑自己,实验本身比我不可靠多了。我更愿意相信实际上数据就不会有任何差异,但没有差异他为什么要让我们分析呢?还是那个最根本的问题,他没做过,他以为会有,脑门一拍想到个好问题丢给我们。
按照指导里ICMP的方式转发TCP包,不出意料地失败。和之前的所有实验一样,不管看起来多么合理,一定有不完善的地方,不可能仅靠初版指导做出来,只能重复无意义的尝试,学不到任何东西。根本原因是他布置前绝对没有自己做过,也不可能辩解说他以为我们会,如果这么高端的用法都会,根本没必要介绍基本的。这其实根本不可理喻,只是给未来的读者看,证明我已经想到这点。Patrick这么喜欢造实验,放在清华不就是他们喜欢说的课改吗?但这里没有一起骂的环境,我和他们聊不到一起去。如果whj在肯定能好好骂一下,他还真在MSML,我经常忘记这个事实,但和我们也没关系,不用上这种垃圾课。
尝试到后期已经想到是广播MAC地址的问题,这是指导里的方法,但我可以想象为安全起见系统不接受。需要类似ARP的机制学习填写目标MAC,指导里当然没有,网上也搜不到,这种工具根本没有完整的教程,man page只能用来查询,没法学习。Piazza上有人问一样的问题,Patrick让他发图,我替他发了。他回复大意是我确信(pretty sure)这样不行,等会再写个指导。哦,你原来知道啊,那是肯定的,毕竟是CMU的教授,但你知道为什么不早点写在指导里呢,是觉得我们都会吗?还是浪费我们的时间很好玩?等了约半小时,接近四点,他还是没写出来。 - 节选自《2021.11.13》
一门计算机课程能让我从头讨厌到尾,这是非常难得的。印象中我很少真正讨厌计算机课程,总能找到点乐趣,哪怕是大作业非常折磨的数据挖掘,lab也有点意思。恐怕只有大数据与机器智能这种莫名其妙的课能与之媲美,方向不感兴趣,内容一塌糊涂。就像我昨天说的,“我们是不是对Patrick太温柔了,他布置实验幸苦关我们什么事,我们只应该关心我们是否辛苦,能否学到东西。”后面没说,但我是这样想的:“我们付学费,他拿工资,他辛苦不是应该的吗?”有些幼稚的小学初中生可能对老师有类似的看法,也许我以前就这样,但大学教授和义务教育阶段的老师完全是两个概念,本质区别就在于,我们真的要付很高的学费,他真的能拿很高的工资。 - 节选自《2021.11.24》
据我所知清华有很多人不太喜欢课改,如果我要对他们解释,我可能会说这样的实验设计更符合现代发展方向,能学到更多的知识和工程经验,一定不会说“我们设计实验也很幸苦,请你们体谅我们”。2019秋在清华当编译TA时,我花一个暑假,兢兢业业地做编译器实验框架(decaf-rs),写文档,每次实验都自己提前做一遍,大家还是说我们不考虑难度和工作量(我也觉得工作量太大了,但实验内容不是我定的)。这课的实验能做成这种垃圾,堂而皇之地把半成品拿给我们做,怎么可能不骂啊,还要相互体谅?我想不通啊。
再说了,我也不认为他真的很辛苦。lab都是拖到学期末才批改,没有严谨的评分标准,基本只看报告怎么扯。
i didn’t actually run your code, since i have so many of these to go through, but i looked at the logs and the code. it all seems reasonable to me. the screenshots and graphs are an excellent way to see that things are working as expected. thank you for including the great graphic output, and keep up the excellent work!!! - 《lab2评语》
没有跑代码也就算了,更离谱的是,有个同学说他收到了和我逐字相同的评语,他连评语也是复制的。
但这还没完,它还有个组队自选proj,体验也非常差,但这基本算是我自己的问题。
一是我很不喜欢这个形式,自选最大的问题是我需要自己做可行性分析,没法提前“确定一件事能做”。最终选定从清华的网原lab开始魔改,实现RIP和OSPF路由协议。当年写的RIP其实挺简单的,我觉得“造路由器”这个词可能给人的心理压力有点大,和组原的“造计算机”完全不能相提并论。OSPF就复杂得多,而且proj不同于lab,没有实验指导,我得自己决定精简掉哪些部分。
第二个原因,组队。四人组队,其中一个中途退课了,不过这没有影响,因为最终是我一个人完成了几乎所有工作。我有很多话想说,但故事还没结束,我能说的比较有限,引用删去了一些更激烈一点的言辞。
类似的想法最晚在大三就有了:以朋友圈为主的社交平台上,经常能看到学期总结时“感谢大佬队友carry”,“度过了幸运的一年”,但很少看到“大佬”抱怨队友划水。真抱怨的多半是自己能力也一般,说队友不作为害得项目没做好。是不是能力强的人都不喜欢说话?还是说他们的道德水平都和能力一样高,心底里就不抱怨队友?那对不起了,我的道德水平没那么高,但我对我的能力很自信,我就是喜欢说,就是要抱怨。你们是幸运的,那我是不是就是不幸的? - 节选自《2021.11.15》
当然也有我的问题,我事实上并没有给做任何计划和分工,可以认为队友并没有出力的机会。但我感觉这就是没法分啊,我的想法和造计算机时一样:“因为我总是想着,这么困难的工作,恐怕我的队友应该不能胜任吧。”反正大家分数一样,我也只是在课程结束之后抱怨一下,我觉得我还是有这个资格的。
我还是不理解我们为什么要必修经管。计算机就是我的一切,计算机之外的课程对我来说不能调节workload,没有什么比做不感兴趣的事更累了。例如我绝对不能想象同时选经管和OS(感兴趣但workload大),那我要被折磨死了。
我选的是8:35的早课,早期这不算很困难,我通常七点左右起床,但冬天天气变冷,自然地想睡的更晚。而且和760一样,去了也不会听,这也是因为我的英语水平不行,如果是中文课,我可以在做自己的事的同时听课,但英语不行,如果想听见任何内容,甚至不要求听懂,都必须全心投入听课。
几次quiz + 组队的公司模拟经营和三次相关presentation。
寄,怎么又要组队。
睡到九点多,看到经管的短信,说我们又没提供模拟输入,ACTION IS NEEDED。我记得10.20直接降低一个等级的警告,但我也没办法,以为已经错过了,而且也不会,在群里问一句,再躺一会。又看到邮件说十点前完成,还有希望,没人回复我只能自己随便操作。9:40起来,我不知道有没有输入分别长什么样,已经有一堆数字,我乱改一通。改完群里才有回复说已经做了,也许如果没做就没有数字,只能又麻烦他改回去。昨晚只是组队的一面,今早是另一面,再一次完美地验证了我的看法,只有别人拖累我和我拖累别人两种可能。是不是人类社会不太欢迎我? - 节选自《2021.11.16》
第三次去Tepper 5222。九点等几分钟一组印度学生出来,他们好像是Company3,两个产品都做的非常好。我第一个讲,我们所有指标都很低,质量,green value等。我一次input都没参与过,也懒得研究。销量也是倒数第一,昨天下午看到Performance分数,40/50,表面上看还行,但规则是最低分40,40-50间线性评分。这完全可以理解,并且再一次证实我的认知,我所在的组永远是做的最差的。 - 节选自《2021.12.01》
“我所在的组永远是做的最差的”并不准确,组队其实分两种情况,通常我只能想起来其中一种,只有11.16这样的特例,短期内经历了两种,我才能同时想到。一是我感兴趣,那我通常会完成绝大多数工作,结果是不确定的,但通常不会很差;二是我不感兴趣,那我碰都不想碰一下,其他人在我的影响下似乎也会变得不积极,结果当然会很差。
其他情况倒也不是没有,但非常罕见。
“我曾经和两个很厉害的同学一起参加编译比赛,这是少有的积极合作体验,每个人都出力了,通常是我做全部或者别人做全部,前者更多。” - 节选自《2021.12.07》
两个很厉害的同学,指的是cjj和csq,他们都是我眼中比我强得多的人。不知道他们怎么想,是否也会觉得我在划水呢?说实话我不太相信,但不能排除这种可能性。这次我们当然不是最差的,但也不是第一名,相信大家都很不满意。
每周依据指定的话题,在Zoom上随机分组,三到四人讨论,并提交讨论记录。
印度人的口音真的是一言难尽,不知道他们听我说感觉怎么样,大概也很糟糕吧。
很无聊的一门课,好在工作量也不大。
一开始我只想去大厂。没有什么理由,就像我选CMU一样,我只认可以前就听说过的。我毕生所追求的,无非就是名声。同时也是因为前一段实习的糟糕经历,我对小厂无法放心。早在2021.08.05就提交了Amazon的申请,至今没有回复,这肯定等价于拒绝,但如果它直接拒绝,我会舒服很多。开学之后找学长内推,提交了一波大厂的申请,Google,Facebook等。Facebook很快拒绝,其他的也至今没有回复。
我就这样一直等着。到10.08的INI Picnic,我还没有收到任何一个OA(online assessment,在线做题)。很多人说他们每天忙于OA和面试,我不知道他们是真的在抱怨还是在炫耀。他们说的公司我多数都没听说过,所以不能再等下去了,之后两天我就申请了十几个,每收到一个拒绝就再申请几个。
然后终于能收到OA了,每次收到就立即做,先花点时间看看一亩三分地汇报的题目,然后基本可以秒杀。到最后我也没有体验到“忙于OA”,这本身是一件过于容易的事,忙不起来。
面试也几乎没有超出过汇报的题目,难点在且只在英语口语和听力,但多数面试只需要我大概介绍思路,然后在浏览器里写代码,只是相当于作弊难度更高,因而可信度更高的OA。从10.14 Duolingo的Karat面开始,到11.03 Databricks的Hiring Manager面结束,总共9个面试。现在计算起来似乎很少,但我那段时间确实感觉到不小的压力。
有几次是中国面试官,有一次对方主动说了中文,然后就全程用中文,体验极好。没想到他也是清华的,二字班,面试就变成了嘻嘻哈哈的聊天,然后他女朋友也凑过来,结果就变成他们两个人聊天,我只能尴尬地看着。他给了我很多未来的建议。BQ(behavior question)对我来说是最困难的。面试官让我讲前一段实习的经历,这实在是太难说了,再加上我蹩脚的英语,简直就是灾难。我全程眼睛斜向上看,这象征我在紧张地思考,我知道对人说话时要看着对方,从而显得自信,但我明明就是不自信,装不出来。
最终走完面试流程的有三家,Duolingo,Databricks,Tiktok。Duolingo拒了我。11.08收到Databricks offer,这次的生日礼物比去年好多了。此时还有几家正在流程当中,但我觉得都没有Databricks好,直接全部中止。这就是唯一的选择,所以我立即接受了。之后还收到了Tiktok offer,它的方向是Privacy & Security,不知道它为什么把我分到了和简历内容完全不相干的组,我本来就不感兴趣,而且INI要求接受实习offer后不能撕。这个要求确实很奇怪,我听说只有INI有,具体来说是签字之前需要找INI批准,否则到时候它可能不给发CPT,但可笑的是,公司在任何时候都可以随便(at will)撕我们的offer。
刷题是我的娱乐活动之一。到现在我还在每天做Leetcode每日一题,截止2022.01.05,总共98天,其实更早就开始做了,但有很多没记录上。没有坚持,自律之类的大话,只是因为太简单了,这么容易就能收获成就感,多划算啊,我反而要约束自己,不要漫无目的地刷着玩,浪费时间。如果换成洛谷估计就不会很愿意,每天至少要多花一小时。
更新:Google在2022.01.10给我发了OA,这是否有点…
我想去Google,什么工资,未来前景,WLB都不是我考虑的因素,我唯一在乎的只有名气。这次只能放弃了,希望找全职时还有机会。
等地铁时他们聊到回国,jsh今年春毕业,打算开始全职前回国一趟,担心绿码和签证会有问题。他们问我的计划,我说“我不想回国,反正我在国内也没有朋友”,他们说“我们只是说毕业后回国一趟”,似乎认为我是误会了,但我一直听得很清楚,“一趟我也不想回,我并不想念我的家人”。 - 节选自《2021.12.27》
我将留在美国。
“我不宣誓,在这个世界里我感到自己是个外人,没得到过多少快乐和幸福,也没得到过多少爱,当然这都是我的错……”他在说这番话时,双眼微闭,语气舒缓,仿佛在浏览自己凄凉的一生。 - 节选自《三体III:死神永生》
如果有这样的机会,我应该会欣然接受。我最感到可惜的地方是实际上没有这样一支舰队。
]]>西操附近的小卖部。清华报道前几天我来这里熟悉环境,买了辆自行车,意气风发地骑车绕校园转了转。当时是阳光晴朗的下午,天气很热,在西操附近的一个小卖部买饮料,应该是一瓶矿泉水和一瓶维他柠檬茶。但后来在我记忆中的那个位置没有再见过那个店,也许拆掉了,就像宿舍楼下的小卖部一样,也许是我记的不是很精确,实际上就是这一个。
西操的手球场。非常幸运在大二上选到手球,非常水的一门体育课,当时好像也没有人指导和宣传。老师人很和善,要求也很宽松,最后考试时的评分标准比第一堂课上说的松了太多。每节课很多时间分队打手球,我当然打不好,动作很慢很僵硬,也不敢和别人冲撞,我只是体重比较大,并没有什么力量,但即使只是追着球跑好像也挺有趣的。期末考三千米那天很冷,跑完感觉脚和袜子粘在一起。
数学系馆。上线代和复变时每周来这里交作业,然后去上OOP。有次来晚了一点,作业已经被收走,加上助教微信才顺利补交。线代和复变都是ygw的,大家都说他的作业和考试难,但我真的觉得他讲的很好,很负责任,作业确实难,但实在做不出来通常可以在网上找到答案,而且考试相比于作业并不算难。我那时觉得学校安排这样的基础课程肯定有它的道理,为什么认为教的简单的老师更好呢。当然后来我知道这是我用来安慰自己的谎言,这些课基本都没用,有用的部分也完全可以自学,有些人选水课确实只是逃避困难,但有些人用节省下来的时间做了更有意义的事情。
校医院。我记忆中来过这里三次,第一次是军训时上午来体检,要求空腹,很饿。第二次是大一上的冬天,我感冒了,嗓子非常疼,下周英语课有期末pre,周末早上来这里开药,然后去听涛吃早餐。吃完坐在位子上感觉头很晕,眼前发黑,一段时间没有好转,我想去买杯热豆浆,回位子路上支撑不住跪在地上,幸好没有人注意到我,我艰难地站起来走回去,趴在桌子上缓了很久。第三次是大二上的冬天,又感冒了,那天数据结构课上,邓公问:“怎么结合快速排序和插入排序的性质来优化排序速度”,我知道答案,这是std::sort
的原理之一,而且他们好像都不知道。嗓子几乎发不出声音,但还是尽力说了:“快排达到一定大小时就退出,再整体插排。”而不是通常最直观的想象,快排达到一定大小时在小区间上插排。这可能耗尽了我最后的力气,下课后去校医院,排队挂号时晕倒了,体验了被人扶到病床上,推去病房的感觉。我也希望当时的我能想到点什么,以此获得一点内心的满足感,但是很可惜,确实什么也没想到,只感觉手脚好冷,嗓子好疼。
荷塘月色。水抽干了,不知道什么时候能恢复以往的景色。这附近有数学系老师的办公室,我大一时来问过微积分和线代的题目。大三上我妈来学校找我,在中间岛上的餐厅坐下来讨论未来。那段时间中介说很多学校都要求入学时有效的托福成绩,我需要重新考一次,而且很多课程学不懂,每天还浪费很多时间在看比赛上面,cy那边的科研完全做不下去,压力很大,心情非常低落。她并没有给我任何安慰,只是在不停地追问我未来到底想做什么,我不觉得这有什么奇怪,从小到大理所当然都是这样。我说未来已经看不到任何希望了,我们两个人都哭了,她问我为什么到了这个地步。现在来看那次对话对未来没有任何指导,只是让我难受了很长一段时间。那个学期就那样度过,课程是学的不错,但还是做不了任何科研,到现在还是不行,但似乎也没那么重要。
西门。去火车站会从这里出去,所以每次回家和返校都经过这里。但印象更深的是从西门骑车出去上托福和GRE的课,同一个地方,分别是19年寒假和暑假。自己一个人住在寝室里,每天骑车五公里去上课,下午再骑回来。上托福时有天下了不小的雪,中午提前放我们回来,景色还挺好,手机都能拍下来雪花的大小。迎着寒风或烈日骑车,让我非常自我感动,感觉自己是在为未来而努力,其实这种生理上的痛苦是完全没有必要且没有意义的。
清华附小。从没来过这里,我甚至不知道清华附小有个门在清华里面。路上在这遇见lyk和他的女朋友。我先注意到他们,就打了个招呼,说我在散步,其实我挺好奇他们在这干什么,但没问。
南门。有几次和室友出去吃饭经过这个门。大二上有段时间上一个托福口语的课,被电子学基础的作业折磨一下午之后从这里出去上课,再被口语折磨一晚上。骑车回来时看见很多灯火辉煌的建筑,这被认为是城市繁华的象征,但实际上意味着里面的人还在工作,未来的某天我也要成为他们其中的一员吗?
李兆基科技大楼。大一来参加过几次社团活动,第一次来面试时因为不熟悉位置来早了很多,后来有一次在很大的雾霾中过来,cjj还有谁讲Git,Docker之类的,这些东西我一直觉得很重要,但现在也没完全学会。之后逐渐失去联系,好像整个社团就这么散了。从小我就有个感觉,只要有我在的组,做什么事情好像永远都是最差的,包括科研也是这样。不可能每次都这么巧吧,这只能是我的问题,毕竟我最擅长的事情,考试,一般认为是不需要团队合作的。
精仪系馆和站在这个楼梯上看到的FIT楼和李兆基科技大楼。高三前暑假的清华暑校期间来参观过精仪系馆,就站在这个视角看这两栋楼。那时我在想,精仪是干什么的,眼前这两个建筑是干什么的,我的知识是否已经覆盖了这些?我真的配得上这些建筑吗,这些建筑真的配得上我吗?我自认为我已经理解了高中的一切(当然实际上并没有),但未来会怎么样呢,是只要来到这个地方,就能自动地理解这个世界,做出什么成就来吗?
明理楼。大二上在这里上电子学基础,前半学期还像是个正常课,后半学期实在是一句话也听不懂,出勤率不到一半,其实与后面的课相比也不算低。但那时我觉得,我是好学生,至少不能逃课吧,虽然听不懂,但他说的至少是汉语,我还是能逐字抄写在笔记本上,尽管我并不觉得这样有任何用处。也是那个学期,在这里排练129合唱,没有留下任何美好的回忆,他们为什么不一开始就把我刷掉呢,如果一开始就知道是这样,我肯定要故意让他们把我刷掉。
通向主楼的路。2017年夏天高考后,领军计划面试前一天晚上来学校,从东门进来,看到路上有群学生正在交谈,灯光中能看见自信,笃定的笑容。“他们看上去都是那么高贵和完美…眼中都透出神灵般睿智的亮光…像奥林匹斯山上的神祇。”这就是清华的学生该有的样子吧,这么多年过去了,我好像最终还是没有成为这样的人。
主楼。西主楼应该属于电子系,在那里做过几次电子学实验。东主楼现在我几乎天天去,但正经做学术的人可能早在大一大二就已经是这里的常客了。大三上某天cy在群里说开组会,但我不确定我是否在他通知的人的范围内,那天下午我在东主楼门口徘徊了很久,还是没有走进去,因为我觉得我去组会也没有什么可说的,他也从来没有布置过什么实质性的,可操作的任务。如果那天我走进去了,现在和未来会是什么样的?不一定会更好,甚至不一定会更差,可能最终证明只是我自作多情,我的选择从来就没什么意义。拍照时有几个人从面前经过,清华学生通常没有这种闲情用手机给熟悉的建筑物拍照,他们也许会认为我是一个通过某种方式混进来的游客。
建馆报告厅。大一在这听过两次体育课的讲座,上下学期各一次,分别讲长跑和引体向上该怎么练。但引体向上确实不是我的力量能够企及的,如果一直考长跑,也许我不会那么早失去对体育的兴趣,以至于后面需要一次又一次地减肥。在这里也考过几次试,比如OS的期中考试,最后时刻发现自己内存可能读错了,但是没时间改。那种紧张,兴奋,害怕,期待融为一体的感受,大概只有在考试时我才能体会到。
艺术博物馆。我只在报道后军训前短暂的闲暇时间来过一次,那段时间这里免费开放。后来再没有来过了,我确实是很难欣赏艺术。雕塑的四个人是清华国学四大导师,高三准备了很多作文素材,不少是关于他们的。那些素材当时刻在我的思想里,我真的认为我应该成为一名学者,为了一个崇高的理想献出自己的一生,社会中的不和谐终将得到解决,责任就在我们身上,尽管我可能会远离社会,但最终目标仍然是为人类整体做出贡献。现在我明白我什么也无法改变,人类社会也越来越割裂,已经没有希望了,我能做的只有用我的技术为自己谋一个还算体面的生活。
罗姆楼。大一上组织过一次业余选手的编程比赛,就是让高考生体会和学习一下吧。那天我正好要献血,倒没有太影响我的精神,但耽误了很多时间,来的时候已经开始很久。我和jzk组队,基本是他提想法,我写代码。快速解决了几个简单题之后,有段短暂的时间我们不是倒数第一。他真的是相当聪明,这一点比我强太多了,他找出了最后一题的规律,我觉得这完全没法想象。但按照这个思路,那题要完全做出来,应该需要实现高精整数取模,实在是没时间了。最后我们还是倒数第一。
北门。大二上喜欢晚上骑车出去漫无目的地逛,从北门出去,不考虑骑到哪里,反正有手机导航肯定能回来。五一和校庆假期,白天做OOP大作业,寝室的空调相当冷。晚上也出去转,照片表明我去了“龙旗购物中心”,在那里的肯德基或麦当劳吃冰淇淋,奥利奥口味的。回来后看着紫操上零散的人群,发了条说说:“有没有可能,我也会孤独,也会害怕呢?”说的太委婉了,我已经是非常孤独害怕了,我很想和人说话,但我可以,或者说只能,写一整天代码,一句话也不说,并且心里清楚自己写的不是什么有意义的东西。之后的某次国庆,我认为是19年,因为记忆中它是个大庆,也从这里出去转,返程时看到路边有灯笼,但一张照片都没有,也许我确实没拍,也许是我与2018.10.02的一张夜景记混了。那天群里有人说他在某道数据结构题上用块状链表达到了最快的速度,我急着赶回寝室用B树再做一遍。我要证明平衡树可以比块状链表快,这当然也没有意义,但我就是想看到自己的名字排在排行榜最前面。
]]>有了规律的日记之后,总结的主体就只是引用它,只需要偶尔发表一点评论。现在大家可以好好感受一下什么叫“单调乏味”的文字。
寒假的1.08-1.15在上海一带旅游,产生了两个很重要的想法。长期来看,它们对我的影响都是积极的。
回去吧,我完全是在通过走路消磨时间。电梯和地铁上我都看错站提前出来了,但为了避免尴尬,不愿意再回去,就走更远的一段路。走到酒店想在旁边找吃的,但看了一圈觉得没有合适的,就在便利店随便买点,我发现自己说话的声音很轻,是真的累了。我感觉非常孤独,路上很多男女生或者两个女生一起走,但我从来都只有一个人,从来指这一整天,也指我这接近22年的人生。我是需要陪伴的,大概除了少数骄傲的时间外我都这样想,但我没有勇气和能力去找一个愿意陪伴我的人。 - 节选自《2021.01.09》
一个人旅游并没有多么难以忍受吧,2020.09.27在宁波不就好好的?这涉及另一个背景,即我眼中的传世经典《2021.01.19》,之前的一个月受与它相关的约定的影响,而之后的至少一个月里我都沉浸在它的余波中。这虽然是已经结束的故事,但我还是不想公开。
我非常喜欢进击的巨人S4 OP开头的字幕,“你将孤单度过一生”。不必区分孤单和孤独,只是因为原样引用才有两个词。我知道这是自作多情,但我觉得这就是作者对我说的话。我并不是主动追求孤单,但现在来看,这似乎是唯一可能的结局了。
我将孤单度过一生。
另一个想法,我愿意说的细节就多一些。
快四点才到科技馆,它只开放到五点,那就算了吧。我来过三次上海,10年世博,就是这次去过科技馆,中考后高一前,大一寒假,这是第四次。不知道十年过去它有什么变化,现在几乎所有科技都被计算机的光芒掩盖,它们的发展似乎已经停滞,人类社会也变得越来越割裂,国家和种族之间的矛盾似乎已经无法弥合。高中时我还挺关心社会问题,虽然有积累作文素材的目的,现在不再关心了,大概是因为认定它不太可能改变吧,我能做的只有用技术为自己谋一个还算体面的生活。 - 节选自《2021.01.10》
后来演化成更极端的思想:人类不再可能发展了,同时地球上的资源是有限的,继续推论下去,结局是很显然的,我就不说了(不过其实后面日记里提到了,细心的读者不难发现)。这是以己度人,鼠目寸光,因为我做不出科研成果,就认为不再会有有价值的成果。但我现在就是这样坚信着,让我改变想法也非常容易,你们做出来有价值的成果,我可以等着享受,这样的打脸我会非常高兴。
在尚未形成规律的日记的远古年代,另有一篇非常重要的日记。
很多人在讨论一个非常厉害的学长,zyh。我以前听说过他,但没有想到能到这样的地步。除了CPU和OS上的成果之外,他还修了数双,似乎别人眼中无比劳累的任务,对他来说像不需要时间一样。
我想到我自己,这真的就是我希望拥有的人生啊,我在报数双时,也许还真的想象过我未来是否能这么辉煌。现在我知道了,不行,完完全全的不可能。我在进清华前的那些梦,这些年来基本就是被反复碾碎,也许某些时候给过我一点实现的希望,比如微积分线性代数的满分A+。我因此才有勇气报数双,但在课程的压力下至今一门课也没选。后来证明这些希望只是泡影,未来也不会有了,只有一片黑暗。
我在知乎上写了个匿名回答,这段文字还算是勉强能体现我的绝望吧:“我真希望也能拥有这么精彩的人生,然而我现在知道这是绝无可能的了。但我又不愿意做坐在路边鼓掌的人。那能怎么办呢?看来只有我死了最轻松。”
最可笑的是,我居然还真的有过这样的梦,我还真的觉得我只要努力,就有希望拥有这样的人生,想来都要嘲笑自己一下。当天终于下定决心退了数双。 - 节选自《2019.09.14》
这里跑个题,我看到这个知乎问题时就想到,列举的成就中的运行“自己实现的编译器生成的程序”,这个工作可能和我有点关系。2019.05.02,在很多人(主要是cjj)的帮助下,我成功在rCore上跑起来了我自己写的第一版Rust版Decaf编译器。很遗憾没有日记,因为值得记的事情本身挤占了所有时间,也就是俗称的“太忙”。但也许后来他们已经可以跑起来Java,那就可以跑他们自己写的版本,和我没有任何关系了。而且即使和我有关,这也只是他所有成就中最简单的一项,却是我尽了全力才做到的。
我一直有个相当矛盾的想法。一是我比绝大多数人都优秀,但同时有一小部分人比我优秀太多。其他领域我不清楚,至少计算机领域的运转和进步,如果后者可能的话,只需要这些人。自我以下,包括我在内,所有人都没有存在的意义。
现在,我倒也不是不再这样想,只是不再因此感到痛苦。那时我痛苦是因为希望自己能为人类进步做出贡献,但又做不到。现在既然认定人类已经不能再进步,那就不是我的责任,我可以安心地,平庸地活下去。
代码在plant。内容本身没有价值,但心路历程值得一说。
周四中午和zjd聊毕设。他给了两个选择,在多GPU上优化GNN,和研究改进一个Polyhedral编译器框架Tiramisu。回去就开始读两篇论文,以及Tiramisu的代码,大概两万行,这让我觉得是有可行性的。我觉得可以造一个这样的编译器,而不是去改进它。这听起来也许很厉害,其实是没什么价值的,如果不能改进它的话,重新造一遍也没有道理可以比它更好。 - 节选自《2020.12.05》
我的全部大学生涯都在追求复现别人的结果。我必须确定一件事能做,才有动力去做。用于确定的信息来源可以是现成的代码,或者绝大多数正常的课程项目,这是出于对老师的信任,应该不会布置什么奇怪的东西。这里当然有个问题,确定能做的事为什么还要我再做一遍?从小到大答案都是确定的,训练和检验,但这真的能适用一辈子吗?未来还有谁这么好心地给我设置训练和检验,并且因为我能够通过,就给我发工资吗?
但这种对未来的担忧不影响题目的选择,毕设本来就不要求创新。最终选定是Polyhedral Compiler,多面体模型编译器,感兴趣的读者可以自行查阅,反正我是不感兴趣,也懒得介绍。
花了很长时间给它起了个我认为不错的名字,PLANT,PoLyhedral bAsed teNsor opTimizer,基于多面体模型的张量编译器。这当然是取自MashPlant,同时plant还有工厂的意思,作为编译器的名称非常合适。
我发现个好玩的事,原来报告首页日期写的都是2020,现在才发现。展示时怎么没人提醒我,大家都没看出来吗?
Tiramisu是用C++写的,我想改成Rust。按照我的个人爱好,肯定是在静态语言里面选,选择Rust原因很简单,一是它有很多蹩脚的字符串操作,用Rust估计会方便些;二是换了语言,强迫我理解之后重写一遍,这样再高明的查重手段也不可能认为它是剽窃。至少我是这么认为的,这涉及到理解人类的思维,我不相信现在有这样的技术。
做多面体模型依赖ISL库,它没有Rust绑定,我就自己造一个。用Clang分析头文件,输出Rust代码。我写Rust从来不考虑安全性,为了方便,函数里全部套个unsafe
,也不检查合法性,因为我确实比编译器聪明。还造了个方便随意使用的裸指针,我不想为了借用检查动一点脑筋。我一直在想,要是可以直接关闭这些检查该多好,那Rust在我心中就完美了。
然后就是照着Tiramisu写,一月前差不多写出来基本的调度,能跑最简单的矩阵乘。
开题肯定要批判现有的方向,多面体 + 手动/自动调度,非多面体 + 手动调度/机器学习辅助。Tiramisu是多面体 + 手动调度,所以我的实现方向肯定也是它,但打算加一个基于模板的Auto Tuning,即AutoTVM的思路。我不相信多面体那套看起来很优美的理论真的能给出优秀的全自动调度,这太困难了,开源的代码实验性能都很一般。TVM + Ansor似乎做的非常好,所以我肯定不能详细讲,而且我认为这套系统超出了我的能力范围。
寒假在家继续写,其实没多少内容,Tiramisu居然能写的这么啰嗦。总结里都说过好几次了,日记里更多,我直接引用另一篇总结里的评论。这次不是学长代码,但结果也一样。
开始是照着学长的写的,越看越难受,他们的代码怎么这么垃圾,这全是漏洞和性能缺陷啊。垃圾归垃圾,如果让我从零开始自己写,我觉得我就是做不到,我就是需要别人给我个框架才能往上面加东西。 - 节选自 大三上总结/数据库系统概论
啰嗦也就算了,论文里说到的功能,稍微非平凡一点的就没有。它只会原样执行用户的调度指令,甚至没有合法性检查,会并行化一个有数据依赖的循环。多面体模型的基本功能,通过Pluto算法,自动循环分块来消除依赖,它也没有提供,需要手动分块。我发邮件问他们,没有回复。在GitHub上提了个issue,他们说有有未开源的版本实现了这些功能。我希望它真的有,而不是只是在论文里胡吹。
不管它实际上有没有吧,反正开源的版本没有,那我就不可能写。写完CPU代码生成,然后是GPU,反正是手动调度,只需要加几个指令,生成高效的代码是用户的责任。然后研究它的矩阵乘测例中的shared memory用法,我早说过我对GPU只能算刚入门,好几天才看懂原理,但这应该是最基础的内容。最后写Auto Tuning,改成照着AutoTVM写,这也不难,用户定义几个参数,用XGBoost训练一下就好了。
到2.21返校时,实际的编译器工作已经接近做完了,后面都是小修小补。我不是很好意思叫它编译器,它只是个稍微方便一点的代码生成工具,仍然要求用户完全理解系统结构,才能生成高效的代码,当然报告里我是不会这么说的。但既然Tiramisu都能叫编译器,那plant也没有理由不能。
但真正的工作量到这时才刚开始,我要用它写测例,至少得实现矩阵乘和卷积。前期一直不知道该怎么写,因为我并不理解系统结构。到处找论文和代码,尝试一个方案的成本很高,因为编译器前端很难用,当然也不比Tiramisu更难用。而且必须完整写出来才能知道速度,而稍微慢一点的就没有任何意义。尝试期间实现了GotoBLAS的矩阵乘,但比Intel MKL慢一倍,Intel Xeon Gold 5218上的2048矩阵乘只要约10ms。
这时想到可以用Ansor自动调度,照着它输出的调度来实现。我并没有什么思维体系,能想到纯靠幸运,也没想到它真的能比MKL再快一点,后续的几个算子也全靠Ansor。但这也不容易,有很多非常琐碎的细节,还要修框架的bug。3.09-3.13终于实现了和它一样快的矩阵乘,最后一天还差1ms,研究一整天,最后发现是它用了AVX512,加个编译参数就解决了(目前的-march=native
不会用AVX512)。
到中期答辩时,我的矩阵乘和卷积能比MKL,cuDNN快,这是实话,但Ansor能全自动地达到一样的速度,我只是借它刷数据。一开始就可以想象到,我的工作没有任何意义。不要觉得是我太敏感,妄自菲薄,这个世界上确实有很多领域,除了最优秀的那一个之外其他的都没有意义。
组会我第一个讲,说我能达到和算子库相当的性能,调度是通过Ansor得到的。zjd问我是把Ansor接入自己的系统吗,我说不是,我只是抄它的调度,他问那我的工作在什么位置,我说没有任何位置,这件事可以完全由TVM完成,我的工作没有任何意义。本来我不想这么说的,我确实一直这么认为,但已经几乎把这个想法忘了,但他非要这么问,我还是只能这么说。他说希望zly后续参与我的工作,还希望我在poly会上做报告,这次我直接说我不想再做下去了,这个没有意义的工作不值得投入时间。 - 节选自《2021.03.25》
这都是大大方方地公开地讲出来的话,我没什么可隐瞒的。
中期之后只是加了一堆噱头功能,比如跑ResNet,这只是卷积和矩阵乘的堆砌,不过这个调度太多,没法照着Ansor抄,我真用到了自己写的半自动Auto Tuning。这结果看起来还不错吧,其实比Ansor慢,而且再说一次,它是全自动的。
还有用块ARM板子做远程执行,做个简单的Python接口,这就纯属是在玩。四月和五月的一大半用来写论文,最后波澜不惊地答辩。
总体上我感觉我熟悉的人做的工作都扎实一些,当然可能只是我对那些气象,存储的工作不了解。有个人做稀疏加法,讲的没什么问题,但大概是内容太单薄,zjd问:你之后什么打算,出国工作保研?他回答保研。大家都笑了,这确实是个尖锐的问题,每个组要推一个最差的再次答辩,很可能就是他。平均每个人略长于十分钟,到我时总计拖了不到十分钟。我讲了六分半,语速比自己练习时略快,后半段嗓子有点哑,hwt听完直接说那就到这里吧,没问问题就结束,这太随意了。 - 节选自《2021.06.10》
原先写了一些,后来删掉了。祝大家都有光明的未来。
签证,513等内容放在下一篇总结。
打疫苗后在学校里散步,17段小回忆的集合,内容太长,放在另一篇:2021.03.07。
3.21骑车去香山,天安门,再返回学校,路程超过50km,步数五万多一点。这是我最喜欢的两张照片,北京的天空给我一种对比度很高的感觉,除了雾霾都是蓝天。这并不是废话,例如武汉就有很多二者之间的多云天气。
有个毕业100天纪念活动,我提交了上面链接中的“通向主楼的路”,被选中了。
我给拍照那群人发了我的话,就是把3.7日记中那段简化一下,他们说太消极了,可能要放到清华微博上什么的,但我投稿时就是这么写的,我本来以为他们选我时有认真考虑过。算了吧,清华的毕业生怎么能说自己没能成为自信笃定的人,我们当然都是一样的积极向上,准备好未来为祖国做贡献,他们想怎么写怎么写吧。 - 节选自《2021.03.26》
有个出国留学讲座,因为找不到愿意讲的硕士,就找我了。
前两个讲的人是jxy和zx,他们都申请博士,学术上肯定有成果,至少比我强。听他们讲我才意识到他们成绩也比我好,GPA,GT几乎全面碾压。到我了,我一开始也没有计划讲的特别悲观,但讲着讲着还是变成,我一事无成,你们不要学我。跟周四组会一样,我心中确实这么认为,但是这个想法一般埋的比较深,通常不会表现出来,对生活其实也没什么影响,但非要问我,我就只能这么回答。 - 节选自《2021.03.28》
需要声明这是当时的认知,我的最终年级排名是4/202,年级第一大家应该都清楚,我想不会这么巧他们分别是二三,所以大概我还是有一点强项的。
参加4.10校马和4.18的苏州金鸡湖马拉松半马。这个运动水平相比我认识的一些高手差的非常远,希望他们看到不要笑话,这对我自己来说已经是非常大的突破了。
最开始一两公里人堆积在一起,没法跑快,我尝试从路边往前超,但也不快。到东主楼时约2km,我在计算大圈和小圈分别是多长,既然半马是9小圈,10km是3小圈,那6小圈是11.1,每小圈1.85,大圈4.45。之后的路程中还验算过很多遍,不然实在没别的东西可想。终于到了小圈,还有很多力气,但我不想加速,不必为这种事拼命。有个小个子带着绿色发带的女生一直在我前面一点,但在第二小圈近结束时我大步超过了,我确实有这个体能。到终点时稍微冲刺一下,整体算是轻松完成,成绩为56分钟整。 - 节选自《2021.04.10》
六点多出门,悠闲地坐地铁,三号线转一号线,两次都差点可以赶上,但我不想赶,就等下一班。存包时脱下外套,感觉很冷。7:42开跑,我直接按下腰包里的手机的开始键,没看屏幕,这埋下一个伏笔。几公里处就觉得肚子有点疼,就这么一直忍着,如果没有3.31自己跑的那次,在这么早的地方就开始难受,我肯定会很绝望。虽然是环湖跑,但多数路程看不见湖,在公路上。我好像比人群的整体速度慢一些,当然也有被我超过,或者干脆在走的人。说我不关心落在我后面的人是不准确的,事实上我根本看不见他们。15km处有几个拱桥,上坡下坡增添不少乐趣。我一直没拿补给,18km左右我看到补给站有巧克力,想着到下一个可以拿一块,但真的到了,我还是因为人太多而不想挤过去,随手抓一块,是香蕉的头部,全是皮;又随手拿杯水,是咸的,喝一小口也扔了,我只是嘴唇有点干,并没有出太多汗。最后几公里几乎没有任何不适,这和上次差别太大了,我不认为我的体能有很大进步,应该是因为上次没吃晚饭,这次吃了早饭,我的消化和循环系统还是给了点作用。路上经常有老太太或者学生组队给我们加油,我想那些学生自己更需要加油,她们未来的路应该比我剩下的路程要漫长的多。18km后我刻意跑快了一些,但听手机配速还是六分几秒,那时我认为是疲惫状态下自己觉得用力了也跑不快。最后1km拿出手机,调出僕の戦争,这歌对精神有很大的刺激作用,它只有一分半钟,我就放了三次。
很遗憾,伏笔的结果是我的跑步app设置成了跑步机模式,没有记录路程,所有配速都是估计的。这个模式存在就离谱,谁在跑步机上跑时会把手机带在身上?不用它看点东西吗?损失比绕着紫荆操场跑开了跑步机模式大得多,因为这段绕着湖的轨迹是独特的。半马对我来说可以算是巨大的成就,但我居然没发朋友圈,都是因为这。
之后顺便在苏州旅游一圈,前面说的“上海一带”就包括苏州,那时绝对不会想到三个月我会再来一次,而且是为了跑步。这里我本来想引用一句歌词,但实在太尬了,我放注释里。
五一假期去石家庄旅游,选它只是因为我觉得住宿太麻烦,不想规划一天以上的行程,所以挑了个最近的。这里真没什么可玩的,就是看人,各种各样的人。
三点到人民广场和长安公园,广场很空旷,让我以为没人,其实人都在公园。公园里有一大片相连的水域,很多人在划船,我突然也想划,找到码头。上次划船是什么时候?我很难想象初高中做这种事,如果是小学,那可能有十年了。我问看守的大爷一个人能划吗,我确实没见到单独划的,都是一家人或情侣。他没注意到我,对另一个人说人太多了,现在没船。还有更多买了票的人走过来,那就算了吧。如果有机会回东湖,我一定要划次船。路过篮球场,所有人都身材高大,肌肉分明,这对我来说已经不可能了。我想到了高中的篮球赛,好像是季军赛,chf和wyx上场随意地打了一下,至少我眼中是这样的,大家都为他们喝彩,我心里很不舒服。我一辈子都在尝试吸引别人的注意,那时是特别强烈的渴望,现在心中慢慢接受做不到了,但不代表我不渴望。 - 节选自《2021.05.03》
我不为别人鼓掌,我将孤单度过一生。
拔智齿。4.14第一次记录左侧第二磨牙不舒服,4.19想到可能是智齿的原因,想到出国再拔可能比较麻烦,所以就现在拔。4.22左下,5.6左上,5.10右上,5.17右下,总计开销126元,校医院真是便宜的离谱。我也强烈推荐大家趁早拔牙,只要稍微觉得有点不舒服,拔了就算是利大于弊。
左下是最艰难的,后续都比较轻松,不过主要时间是在五月,所以放在这里。
约九点开始,他对女助手说要磨和切,这很好的概括了手术过程。先打麻药,有点疼,几分钟后左边脸感觉麻和胀。一开始用镊子,剪刀等小工具,应该是切开牙龈。助手拿着管子,后来想到这是吸气的,用来清除口水和血水。看到圆柱+尖头的工具,然后响起很大的声音,吸气力度也变大,后来想到这应该是钻头,这就是磨。看到螺丝刀状的工具,在牙齿上用力凿,这就是切。这个过程重复了五六轮,我能看到夹出来一些碎牙,但他一直说没有适合着力的地方,嘴必须用力张着。某次切之后,有种疼痛从牙缝慢慢深入,没有任何手段可以阻止。双手在腹部交叉,我正常站着时这个肢体语言象征紧张,但躺着也没有更合适的姿势,有很多次两手紧紧捏住来分散注意力。我能听到“螺丝刀”和牙齿摩擦的声音,但不知道是成功凿下来,还是像拧不动螺丝时一样滑动。最终用上小锤子,把“螺丝刀”砸进去,每一下好像整个头骨都在跟着震动,然后他用力撬,我能感到牙齿脱落,这是很陌生的感觉,来源于小时候换牙。撬完他说一个根出来了,还有一个,不过第二个根耗时短了很多,也没有用锤子,直接撬出来。到这里就拔完了,塞入白色物体,好像叫胶原蛋白海绵,然后缝线,看着线从嘴中拉出,但牙龈却没有任何感觉,这感觉很奇妙。线把嘴角磨的疼,后来照镜子确实有点红。到这里约十点,听他们交谈说这个比昨天那个好,那个花了一个半小时。他没有问拔上面的智齿的事,我当然也不敢问,这已经足够煎熬了。 - 节选自《2021.04.22》
但煎熬也就这一次,完全可以接受。之后的校庆晚会彩排没去看,还是在寝室躺着舒服。恢复过程比较顺利,按医嘱吃药吃东西就行了,没事别乱想。
5.22-5.23参加THUPC志愿者。大一下我作为队员参加过一次,没有任何贡献,这次也是全程划水,就到处走来走去,我觉得看人很有意思。如果有人问我问题,我通常会把他转交给附近别的志愿者。本身没什么故事,全是我自己在回忆。
三年前接近结束时我应该只感觉无力和紧张,剩下的时间不可能做出任何题了。我没法感知选手的想法,我猜大概也是这样,这不是高中考试,最后一分钟改变不了什么,所以三点整结束前不少人就站起来,结束后也没有明显的骚动。
去主楼后厅看颁奖,在这看过特奖答辩和智能体大赛。硬等到四点才开始。12题的出题人依次讲题,一道也没听懂。我想到2018夏天,学托福休息的日子里,一个人在空调房里在洛谷上做算法题,很可惜完全没留下日记。有一整天调试一道题,结合印象搜提交记录,可能是NOI2005 维护数列。2018.07.28凌晨过掉完全没有技术含量的LCT模版题,发了条说说:“还要走多少路才能追上你们”。但是我好像一直没明白,我学这些东西到底是为什么?是为了下学期的数据结构吗,那完全偏离方向了。我好像只是让自己觉得在学些难的东西,从而获得一点安慰。莫队,LCT,后缀数组,数位DP,CDQ分治,NTT,好多那时已经或者试图学会的东西,现在都忘的一点不剩。 - 节选自《2021.05.23》
这是在堆砌一些我认为很厉害的名词,它们的难度也许不能并列,搞竞赛的读者看到这里应该要笑话我了。大学刚入学时我很早就做出了精准的预言:我的算法水平永远不可能追上竞赛生,不止是知识储备,就是思考能力本身。毕业时没想起来,现在确实验证了。
即将离开清华,没有故事,只有回忆。
第二次大规模清理东西。先清从柜子里临时搬到中厅的一堆纸,有些有意义的。口罩真的太多了,只能扔掉。下楼扔垃圾时注意到45楼梯间有捐书捐衣的箱子,把GRE资料等放进去,保留了填空习题。也许捐的东西也会被扔掉,但不关我的事,眼不见心不烦。惊奇地发现大箱上有白色虫子,多数已经死了,但好像也有活的。小心翼翼地带上口罩打开,内部还好,只是有点痕迹。虫子应该是生在棉絮上,大概是爷爷奶奶准备的,我一次都没用过。这是个不错的借口,长虫的衣服捐了也不好,整箱拎下楼倒进垃圾桶。整理奖状,献血证明,跑步号码布,一加3包装盒,里面装的主要是票据和贺卡。
每件东西背后都可能有故事:灰色小书包,心电图,蓝墨水打湿的军理资料袋子,箱子中的蓝色薄外套,学生节物资,坏掉的小米耳机,接头丢失的蓝牙鼠标,积攒的保卡说明书,藏在信封中以为丢失的中国银行卡,数设忘还的线,没用完的物理实验表格,几乎全新的Haskell,Python书,唐宋词选。现在我还能回想起来,但失去了实体作为凭依,很多年之后注定会忘记吧。 - 节选自《2021.06.22》
6.25去C楼打印成绩单填学信网的认证,顺便打印排名证明,这就是我奋斗四年,得到的全部结果。
这…不得不说,真的是很有意义…吗?
我不是优秀或优良毕业生,这我完全不能理解。我确实不认为自己算得上优秀,但系里优良三十多个,怎么样也该轮到我吧。我实在不知道除了成绩,他们到底还能有什么评价指标,我能想到的唯一理由是寝室卫生不达标。约三千五百人毕业,约三百五十个优良毕业生,高中班上来清华的三人只有我不是。
]]>7:12开始进场,坐在东操东南角。计算机的方阵在第一行,所以我能看清屏幕,放各种宣传片,勉强让我放下手机的娱乐,毕竟只有这一次了。我曾经真的是个有理想的人吗?清华一直教育我们成为对国家人类有用的人,过有意义的人生,我也曾幻想着自己能成为奉献的一员。的确,那样的人生该多有意思啊,创造价值就是最持久的欲望源动力,不像吃喝玩乐,肤浅且让人陷入自我怀疑。但过往的经历告诉我这个欲望注定无法实现,只会带来痛苦。同时我也在怀疑,做这些真的能阻止甚至延缓人类的灭亡吗,体育艺术文科,乃至多数工科。我想他们应该很快乐,但他们为热爱挥洒的汗水,真的有意义吗?
7:55领导开始进场。没想到八点多这么热,手机甚至高温报警了,可能是因为草地上的水汽。而且因为早起,昏昏沉沉的,闭眼休息很多次。8:50结束,第一批进综体参加学位授予仪式,即拨穗,几天前第一次看到这个词,在纠结读音是sui还是hui的同时,把拨看成了拔。坐着等,六个台子领导并行操作。轮到我,队前的老师说你的衣服怎么这样,辅导员没有教怎么整理吗?还真没有。她简单整理后我上去,和后面的人有个交错,取照片凭证是序号,我有点担心可能弄混。我不认识拨穗的领导,他手伸向帽穗,我以为是说它不整齐,伸手去整,他说是他来拨,还在台上给我整帽子,挺尴尬的。最后,举起学位证书外壳拍照。
…
毕业倒计时结束了,92天前的照片属于我。最后的一点感慨:“自然赋于人们的不调和还很多,人们自己萎缩堕落退步的也还很多,然而生命决不因此回头。”作为作文素材收集这句话时没完全理解,到高考受挫展望未来,再经历了这四年,还是不敢说真正理解了。我没法控制自己的身体,精神也常陷入颓废堕落,但生命只能向前走,这不是积极向上的宣言,是无奈,我只能随时代的波浪漂流,不知道未来坐落在哪里。这个理解是可以的吗? - 节选自《2021.06.27》
- “因为我必须如此。”
20年寒假的疫情期间,基本确定不可能去美国暑研,但线上暑研的机会还是要争取。对着CS Ranking给不少教授发邮件,主要是系统,编译方向的,不知道是清华邮箱没收到还是确实没有回复,最终收到回复并交流过的教授屈指可数。那段时间没什么事做,人也非常消沉,每天早上打开邮箱看一下,对回复既期望也害怕,我认为没有这封推荐信我就不太可能找到国外好一点的项目,但也认为自己没有必要的能力,害怕和教授进一步交流。
有次一个教授的几个学生和我约早上的面试,但那天我完全忘记了,一小时后在邮箱看到一大堆询问的邮件。第二天再来一次,我表现的很差,说话磕磕巴巴,对他们的方向也不了解,面试后就一点联系都没有了,我不敢也不想再发邮件问。这基本上是那段时间的一个缩影。
最终确定是UChicago的Prof. Shan Lu,做系统,程序分析方向,她回复说可以申请官方的暑研项目,我就去申请了。
主题是分布式系统中的数据格式bug。一头一尾两篇日记,基本可以概括我的感受。没有什么例外,这次又失败了。
他们好像并不是很在乎我的进度,本来就和这些学生没关系,又不是为他们争取推荐信。跑跨版本的实验,基本能复现结果。他们让我看看是不是分析工具汇报的问题引起的,但代码量实在太大,完全没有头绪,我想加点log。首先花了很长时间才成功编译,Ant这套工具链真的是反人类,每次都能遇到新问题。编译好了没法启动,我问为什么,目前还没有结果。他们让我在服务器上跑,又遇到Docker的问题,我问他们,应该是在他们的工作时间,但是一觉起来,也还是没有回复,这应该不是很难的问题吧。我真的担心自己能不能做出点东西,现在卡在一个任务上就没法做下一个,完全是在浪费时间。 - 节选自《2020.07.04》
整个下午都在写,中间太困在床上躺了不到半小时,六七点才写完,还要写报告。肚子非常饿,而且感觉自己做的事情很无聊,真的做不动。开会时别人先讲二十分钟,教授问我有没有要讲的,我就讲了,但完全没说清楚。她好像很不满意,一直强调我们要分析general的情况,而不是关注这一个bug。我觉得这就是唯一的情况,但没法用英语表达我的想法,很难受,就一直听她讲。终于说完,这好像是最后一次会,我长舒一口气,折磨终于要结束了,之后还说有任务什么的,我没听清。结束后一个学长还找我聊了一下,安慰我,说我做的工作还是有价值的,我其实也没什么意见,因为我知道我做的确实是没有价值。 - 节选自《2020.08.26》
我说过很多遍,我从来没有在研究项目上取得过任何成果,暑研进一步验证了这个想法。从之前的种种经历来看,如果一定说我有什么过人之处的话,应该是中短期的记忆力,这简直就是为考试而生的能力,我能考上清华应该也是因为这个能力,但我不认为它在研究或工作上有什么意义。即使是考试,有些需要智力,想象力,或者托福口语,我的能力也没什么用。
返校前我纠结了几天做出最终的决定,本校硕士,本校博士,国外硕士,国外博士。我不太记得那时我为什么排除本校硕士,这看起来好像是最容易的一条路,也许是觉得这样浪费了我的成绩,或者是害怕短短几年后就要面对中国的社会,很多我一直觉得很遥远的东西一下子涌到眼前,我接受不了。跟清华几个教授联系问有没有位置,但我心里也清楚博士名额应该早就确定了,别人都是早早定下明确目标并为之努力,我这几年都干了什么。国外博士更不可能,在国内都做不了研究,凭什么在国外就能做。我完全无法想象自己用英语对着一群教授讲解和辩论。
只剩下唯一的选择,国外硕士,之后大概会留在国外继续工作。但至于那时具体怎么决定,要取决于我看到的另一个社会究竟是什么样的。
听说很多学校要求入学时有效的托福成绩,所以九月末考了托福。我本来想线上考,可以省去很多麻烦,复习时间也会多一点,但我妈一直坚持说也许有学校不认网考,我确实不能肯定,而且出去考试能顺便旅个游,所以勉强接受。线下考位很难找,我写了个简易的脚本来爬,GitHub上有些开源的查考位工具,但速度太慢。放考位那天爬到一个宁波大学的考位,准备一个多星期,去宁波考试。
周末计划去六教复习托福,也确实去了,但是效率低的难以形容。每天主要的时间,可能都花在百无聊赖地翻GitHub,知乎,找没人的教室上面,一天可能只做了一个阅读和一个听力。我纯粹是在打发时间,或者说是故意浪费时间,可是时间真的很多吗?我感觉以前那个我的状态,再也找不回来了。大一我可以坐在六教一整天,只是看数学分析习题,甚至不是在考试之前,只是平时的积累。现在做一两题就非常烦躁,做不下去,急切地想放松。 - 节选自《2020.09.20》
成绩30+28+24+21=103,比上次还低,这也是应该的,本来准备就没有上次充分。主要问题是作文只有21,也许是因为我用的还是19年寒假用的模板,可能是被检测出来了吧。这种场合让我自由发挥是不可能的,我只能依靠记忆力,记的倒是没错,可惜记下来的不是什么有用的东西。
考完一个人在宁波转了两天。
在路上想着,这是不是我以前在追求的生活呢。高二下期末考试后去南京玩,短暂地体会了自由的感觉,回武汉后坐在回学校的地铁上,心中长久没法平静下来,这样自由的生活和现状格格不入。后来设法重新适应和习惯了高中的生活,到现在这么久也没觉得有什么不对。现在我又一次自由地在城市里闲逛,找到什么有意思的就凑近看看,但心里也知道这样的生活不可能永远持续下去,不管未来是更好还是更坏,都必须往前走。 - 节选自《2020.09.27》
在中介指导下选了二十多所学校,因为我害怕,很多学校看起来都差不多,既然申请了一个,为什么不申请另一个呢,万一最后只有没申请的那个有机会录取呢?我完全没有目标,听起来耳熟的学校就申请吧,甚至那些学校在哪我也不知道,我到现在都分不清楚江浙一带哪个城市是哪个省的,要我了解美国的地理实在有点强人所难。选了两所英国的和两所加拿大的作为保险,以免美国整个出问题。我对欧洲那些国家没有任何好感,现在感觉即使只有英国的录取,可能也不想去。
做申请相关的事情我都很消极,按照我看到的说法,我能准备的一切材料似乎都没有太大意义,最终还是要看教授的推荐信和已经无法改变的成绩。因为疫情,我最引以为傲的GRE成绩在很多学校都是optional,甚至直接说你交了我们也不看。也许也和疫情有关,申请时才发现多数学校都只要申请时有效的,要求入学时有效的那几个不申请也没太大损失,这意味着九月的托福基本也算白考了。
所有这些事情,每时每刻都在提醒我,我做的一切都没有意义。
没有意义也得做,毕竟材料总得交齐。几乎所有学校都是拖到接近ddl才提交,申请季从ddl最早的12月初拖到2月。
在路上悠闲地走,顺便看看比赛,Xiaohu节目效果实在太好了。这是旅游的最后一天晚上,路边响起纯音乐,旁边的西湖吹来凉风(只是随口一说,不要纠结风向),脑中浮出四个字“完美谢幕”,这确实是个不错的结束。但这时收到中介的微信,发来几篇改好的文书,我早把这事忘了,原来还有些ddl 1.15的学校没有申请。只能回宾馆去看文书填申请,这悠闲就戛然而止了。还要做Columbia的video interview,今天才发现有这东西,也不知道允不允许ddl后做,不敢冒险。 - 节选自《2021.01.14》
清华在我们这届中间进行了GPA改革,A和A+保持4.0,其余等级+0.3。这对我不利,因为我对感兴趣的课投入很多时间,当然也可以说是很卷,基本都是A或A+,我有10个A+。但不感兴趣的课就一点时间也不想花,比如四节英语课,还有D+,C-级别的体育课,这些课很少有能到A-的。知道这个改革时我沮丧了相当长一段时间,这应该是我大学的转折点之一,在此之前我总觉得未来是充满希望的,但是看到我珍视的东西别人可以如此随意地改变,我第一次意识到“我做的一切都没有意义”,后面就都是下坡路了。
排名8/205是按照旧算法得到的,按照新算法我大概排在16(非官方)。我们这届保研仍然使用旧算法的排名,我后来问辅导员,确认教务提供的就是新算法的GPA和旧算法的排名。
教务不会提供新GPA的排名,即使是在申请出国的时候。所以也许我可以同时以这个比较高的GPA和比较靠前的排名来申请。如果在GPA改革的时候我知道这一点,当时是不是就不会那么失落,后面的故事是不是又会不一样? - 节选自《2020.09.08》
现在国外大学都越来越不重视GRE,这就对了,我早就知道我的能力在未来没有用处,不应该往这上面考。大二下准备GRE,每周末去上次课,但一直没有很用心地背过单词,一学期也没什么长进。期末只有两门考试,就在两周内把单词书好好地背了一下,后面就很简单了,单词都认识就不需要做题技巧。当然,现在我应该已经忘记其中90%以上的词了,这是只对考试有用的中短期记忆。作文这分数应该算比较差,我用的还是托福模板,差也正常。
用n+e的CV的模板做了一个。写了堆成绩比较好的课程,还特意写Junior Year Rank: 1。没办法,成绩是我最大的优势,必须仔细描述。放了几个项目:编译比赛的作品 TrivialCompiler,我造的Parser Generator lalr1,图形学课的光线追踪器 ray_tracer,这种课程项目我还真敢往上放。
放了三段研究经历:
还写了答疑坊志愿者的经历,不然实在填不满一面。
大概就是讲讲做过什么,心路历程,为什么选这个学校之类的。
多数学校要求1k词左右。有些学校要求500词以下,就删掉P2,其余几段再简化一点。在中介指导下第一遍写好之后基本就没怎么改,只是语言上做点润色。不过每个学校写的课程/教授不一样,申请的主要工作量就是去学校网站找这个“某课程/某教授”,但也一直怀疑这种东西对申请是否真的有任何帮助。
有些学校还要求一篇讲diversity的文书,我没有什么diversity,就随便扯扯。讲高中做VPL,助教经历,答疑坊经历,说我希望用我的知识和技术帮助他人,这是实话,不过也算不上什么diversity。
推荐信找cy,cwg和Prof. Shan Lu写的。cwg其实和我没太多关联,基本只是一个课程推,勉强和编译比赛有点关系。大四上我跟着zjd在实习,现在看让他写推荐信可能更合适一点,但是决定谁写的时候还没有做过什么事,其实也没什么可写的。
整个申请过程我最担心的就是Prof. Shan Lu的推荐信,我只问了能否帮我写推荐信,没加strong之类的定语。暑研前中期我们交流还是比较顺畅,但是最后阶段她对我的评价应该不是很好,因此我对她会给我什么样的推荐信心里完全没数。
多数学校都允许申请的ddl之后再提交推荐信,只有少数学校有明确的推荐信ddl。UToronto是第一批ddl的学校,推荐信ddl也很近,当时我好几次发邮件给cwg都没有回应,无奈只能加他微信,他说不好意思没看邮件,幸好我加了微信,最后赶在ddl前交了。
前期记录的比较仔细,后期就很少关注了,我也懒得为写篇总结再去考证。
2021.01.08 收到UToronto教授的邮件,说想和我交流,但要先问清我要读MS还是PhD。我模糊地记得有人说UToronto不招国外MS,但还是回复MS,在我心中PhD是比全聚德留在国内工作优先级更低的选择。一天后收到回信,说培养一个国外MS要花太多钱,所以不考虑我了。虽然交的钱是浪费了,但至少传递了一个信息,我的申请材料没有太大问题,尤其是我最担心的Prof. Shan Lu的推荐信。
2021.02.13 REJ @ CMU MRSD 上午看到拒信,有个瞬间我真的想了一下如果全聚德,未来应该在哪里,但很快打消这个念头,现在即使是在清华应该也有不少只收到拒信的人,我不应该这么担心,这不是我能考虑的。
2021.02.13 AD @ CMU MSIN 下午收到MSIN录取,虽然不是正统的计算机系的项目,但我完全可以接受。按照25天前(2021.01.19)的设想发条朋友圈,分享这段歌词:
我是星
利剑开刃寒光锋芒的银星
绝不消隐
不回顾
永难再折返的故园的光阴
– 夜航星(Night Voyager)· 不才/三体宇宙
当然我得声明这“不代表本人想法”,不然该怀疑我有移民倾向了。
2021.02.25 AD @ UChicago 没有什么意外,也并没有很想去,只是因为暑研在这里做的,一封推荐信是找Prof. Shan Lu要的,觉得如果不申请会显得很奇怪。
2021.02.26 REJ @ CMU MCDS 前一天晚上和人聊天,剩下的项目里我最期待CMU剩下两个还没拒我的计算机系的项目,MCDS和MSCS,我觉得前者机会相对大一点,但也不大。遗憾的是一语成谶,早上醒来看到一封20分钟前发来的拒信。
2021.03.01 REJ @ Princeton 这是啥学校,我啥时候申请的?
2021.03.02 AD @ Purdue 这是啥学校,我啥时候申请的?
2021.03.02 AD @ Columbia 这是啥学校,我啥时候申请的?
2021.03.02 AD @ UCSD
2021.03.06 REJ @ CMU MSCS 至此申请的三个CMU SCS项目全部被拒。擅长考试的人已经太多了,尽管我很自信,我应该是他们当中相当优秀的,但也许整个群体都已经不被认可了。
2021.03.30 AD @ UCLA
可以开始最后的选择了,CMU MSIN和UCLA。为什么不是UCSD?因为UCLA我读着比较顺口,所以也许更有名。
我已经思考了好几天,在CMU MSIN和UCLA中做选择。第一反应更倾向于UCLA,我想去享受生活。翻了很久论坛,我寻求的并不是建议,而是认同。在各种比较帖子中,主流的言论都是不推荐CMU,比如MSIN的牌子没多好,找工非常卷之类的。这是我关心的核心问题,如果承受了更大的压力反而找不到好工作,只是学到些所谓硬核的知识,就像这几年我一直在做的一样,那实在没有什么意义,学来干什么,继续考试吗?
但我仍然没法做出最终选择,也许国内很多人都没听说过UCLA,如果是UCB就好很多。之前中介说它不招大陆硕士了,所以没申请,但我没验证过这个信息,现在报UCB offer的人很少,也许确实如此。突然想起2.20说的“我要为自己而活”,这样看是要不在意别人的看法,为了快乐轻松的生活选UCLA吗?依然不是,对于我来说这句话的意思和其他人理解的大概不一样。我个人能获得的物质和精神享受都非常有限,所以为了自己心理的满足,为了“为自己而活”,我必须时刻考虑别人的看法,这一点从未改变。
这确实是个艰难的决定,后来又想到,我努力这么久到底是为了什么,我如果要让最初的自己觉得这些努力没有完全白费,应该选CMU吧,它是四大,那时的我也应该听说过。其实四大这个概念我已经很长时间没有想到了,但对大学低年级的我这应该是清北级别的名词。 - 节选自《2020.04.09》
卷就卷吧,不然我还能干什么呢?
之前的每次学期总结,课程都是唯一的主题,这次终于只是一个个副标题了。
课程安排是前八周上课+后八周project,只给了一个project做。课的内容和project没关系,也不考勤,所以并没有去的必要,工作量可以说是相当小了。
这一年和前一年都是在树莓派上使用摄像头,前一年好像是识别人的石头剪刀布的动作,这次是用摄像头正对屏幕上的图像的一部分进行拍摄,计算拍摄到的部分位于原图像的实时位置。感觉这个简单不少,前一年的怎么也得用点AI的技术,这个调OpenCV就完事了。
最困难的部分是调摄像头焦距,给了我一个很好的生日礼物。
试着把助教的代码跑起来,准备先把树莓派的摄像头焦距调好,它是一个正方形孔中的塑料环,怎么调也扭不动,而且刮花了。问老师怎么做,他说有些凹槽可以发力,我再试一下,还是调不动,一用力,结果胶水脱落,把摄像头排线拆下来了。把它拆开,要拧开螺丝才能把摄像头装回去,一开始拧反了,后面改过来,螺丝几乎被固定死,而且十字被抹掉,完全拧不动,像是之前装电脑一样。后来勉强通过微操把相机装回去,这时已经十点多,一上午就花在这种完全没有意义的事情上面,而且还没有任何成果。 - 节选自《2020.11.08》
后续还是有点意思。有识别率和效率两个评价指标,但没有严格的评分标准。我完全不管识别率,只用最基本的OpenCV,因为这个指标太模糊,也不是我擅长的领域。效率就很容易衡量,响应越快,系统越小越好。把助教的Python版本改成C++,用musl静态链接,最后反复尝试,用Buildroot裁剪出一个能运行识别程序且尽量小的树莓派Linux系统。用到一些OS课相关的技术,勉强算是锻炼了点系统能力,至少我是这么觉得的。
最后检查时老师说我做的很好,把系统裁剪到8.1MiB,这是他见过最小的。
我差一个RW课,所以就选了,很幸运工作量不算大。如果放到现在,在CMU的环境中,非计算机相关的课都被认为是水课,是用来调节工作量的,但对任何时候的我,这些课都是折磨。
主要评分是小组展示和写一篇相关的小论文,课还是没有听的必要。
晚上就要做宗教学基础的展示,但是直到吃完晚饭ppt都一点都没看。六点多开始随便看了大概半小时,也没确定到底哪些地方要讲什么内容。我们第四组讲,前三组ppt上都只有很简短的内容,仔细看才发现他们在对着手机读稿子,我还以为他们真的能脱稿长篇大论那么一段。我认为这纯属个人风格,没有必要为了形式的优雅而追求简洁,展示的目的是传递信息,又不是炫耀演讲能力。
我们组在九点左右开始,调试ppt花了点时间,我第一个讲,基本没遇到问题,只是到最后感觉“可能”说的有点多。第二个人也差不多,但是后两个感觉做的连我都不如,讲的很卡顿,时间也拖的很长,ppt还经常出问题,没想到即使我准备的这么不充分,队友还是能更拉跨一些,我所在的组永远是做的最差的。 - 节选自《2020.12.06》
“我所在的组永远是做的最差的”,这个想法从小学开始伴随我到现在,之后的总结里还会经常提到。
论文就东拼西凑,从根本上就不可能指望我对计算机以外的领域投入热情,深入研究。
晚上差不多抄完改完,用PaperPass查重,结果是34%,比我想象的低不少,我几乎80%都是抄的。这个结果已经够好了,尤其是考虑到很多我引用《坛经》的内容也算重复,但老师应该不会介意这个。 - 节选自《2020.12.26》
配置环境太困难了,我怀疑我可能任何一次lab的环境都没配置好过,结果都是强行照着往年的说。现在想如果有14760的虚拟机,做起来应该会方便点,不必陷在毫无意义的尝试中。到退课前已经出的分数都不理想,还有些一直拖着不出,我担心成绩不好,也实在不感兴趣,就退了。
宗教学课上想起来问zjd毕设的事。他说GN优化,还有Tensor Core优化。我对GPU编程还算有点兴趣,但是真的不希望毕设做这样的项目。我只能算刚入门,可能很多时间都得花在学习上,即使这样最后还是比不上本来就熟悉它的人,可能就是这导致结果不好。优化一个东西的性能,那要是我没能成功优化,这个毕设又怎么办呢?别人做的东西可能有一个明确的进度,要是到中间的什么时候我还一点改进都没有,要靠什么尚未想到的创新才能成功,我到底算做了多少呢,前面的那些工作有什么意义呢?我害怕,我对清晰地看到未来有一种执着的渴望,就是在现在一切都还没开始的时候,我就迫切地希望看到结果。 - 节选自《2020.11.20》
但是对安稳的未来如此看重的一个人现在要去面对完全未知的未来了。
]]>build.rs
中借助cc这个库链接C++代码,不过我在使用wasm后端并试图链接C++代码时遇到了一些问题,这里简单记录一下解决的过程。最终代码位于github.com/MashPlant/ncmdump-rs/tree/wasm,演示网页位于mashplant.online/ncmdump-rs/。
网易云音乐设计了一个ncm格式,用来保存会员专属的音乐,这样可以做到手机客户端上会员有效期结束后不允许播放下载的会员音乐,别的音乐软件也不能识别它。ncm格式并不复杂,ncmdump这个库实现了将它解密为mp3或者flac文件的算法,出于好奇我也照着它实现了一个,并且希望能够用wasm来运行这个算法。
解密过程大致分为两步,第一步是将纯音乐的部分解密出来,涉及一些aes,base64的库,用Rust写起来非常方便;第二步是将ncm文件中包含的封面,作者等媒体信息添加到解密出来的音频文件上,这一步是借助C++库taglib实现的,如果要重新造一遍轮子应该会相当复杂。
taglib提供了C API,也有一个相应的Rust binding的库,但是C API非常不完整,比如我需要给一段内存中的音频加上信息,C API就做不到,必须用C++ API来完成,C API只提供了给文件系统中的音频文件加上信息的接口。所以大致思路就是把taglib编译成一个wasm的库,然后写一个C++函数接受Rust传来的数据并使用taglib来生成加上信息的音频,用emscripten工具链编译这段C++代码,再把这个库和这段C++代码链接到最终的wasm文件中。
1 | $ git clone https://github.com/taglib/taglib |
cmake
可能会报一个错:
1 | -- Check size of wchar_t |
意思应该是找不到wchar_t
的大小,不过可以直接忽略掉这个检查,注释掉ConfigureChecks.cmake
中的相关检查即可:
1 | # check_type_size("wchar_t" SIZEOF_WCHAR_T) |
make
生成了build/taglib/libtag.a
,这就是我们需要的wasm库文件了,但是之后链接时会报错说它缺少index,让我们用ranlib加一个,所以这里就提前加好:
1 | $ emranlib ./taglib/libtag.a # 假设仍在build文件夹中 |
原理上,Rust这边从ncm文件中提取出音频文件和各种信息,把这些传递给C++,C++调用taglib得到加上信息的音频,再传回Rust。至于这些信息怎么安全地跨过FFI边界,不是这篇文章的重点。其实我实现时用了一些FFI不安全的结构体,但是我心里清楚它们的内存布局,理论上虽然没有保证,实际上应该不太可能出错。
如果是编译在本机运行的程序,需要在本机安装libtag1-dev
(以Ubuntu为例),这样就往系统include路径添加了必要的头文件,往系统library路径添加了必要的库,build.rs
就很简单:
1 | fn main() { |
但是要编译到wasm后端,它的include路径和library路径中就没有这些东西了。
先解决include的问题。taglib仓库中的头文件分布比较零散,要添加include搜索路径比较麻烦,可以利用libtag1-dev
添加到系统include路径中的头文件目录/usr/include/taglib
。直接用emcc编译时搜索不到它们,这是因为emcc的include搜索路径并不包括系统include路径,而是emscripten自己定义的一系列头文件的路径,需要手动指定系统include路径:
1 | fn main() { |
但是这会让类似stdio.h
这样的文件也都在/usr/include
中搜索,而不是使用emscripten自己定义的版本,emcc就没法编译了。解决方法是先把add_tag.cpp
中#include <taglib/fileref.h>
这样的语句中的taglib/
去掉,然后build.rs
中改成.include("/usr/include/taglib")
。
再解决library的问题。这里不再需要-l stdc++
了,-l tag
改为-L<taglib>/build/taglib -l tag
,其中<taglib>
是taglib仓库的路径。但是这样没有把emscripten的标准库链接进去,为了知道要链接哪些库,可以随便编译一个程序,看看链接器是怎么执行的:
1 | # <emsdk>表示emsdk仓库的路径,<emscripten>表示<emsdk>/upstream/emscripten |
中间还省略了一些.a
文件,虽然这些库应该不是都有必要,但是写上也没什么坏处,优化编译时会自动忽略掉没有用的库。最终的build.rs
如下:
1 | fn main() { |
现在就可以编译了:
1 | $ CC=emcc CXX=em++ AR=emar wasm-pack build --debug |
按照预期在pkg
文件夹中生成了wasm文件和一些js胶水文件。
按照教程(例如Tutorial: Conway’s Game of Life)写一个简单的网页来使用这个wasm库,会出现这样的错误:
1 | $ npm run start |
普及一下wasm的基础知识:一个编译好的wasm文件不一定可以直接运行,它可以从外界import函数,在初始化wasm模块时必须给它提供这些函数(这个过程就是“链接”)。可以用wabt中的wasm2wat工具来查看具体import了哪些函数:
1 | $ wasm2wat ncmdump_rs_bg.wasm |
从env
中import了emscripten_memcpy_big
,__cxa_throw
等,从wasi_snapshot_preview1
中import了environ_sizes_get
和environ_get
,从./ncmdump_rs_bg.js
中import了__wbindgen_throw
。最后这个函数在pkg
中初始化时会提供,而前两组函数,如果使用emscripten工具链,它生成的js胶水文件中初始化时会提供这些函数,但Rust的wasm工具链生成的胶水文件不会。
考虑这些函数的语义,应该只有emscripten_memcpy_big
和emscripten_resize_heap
是需要实现的。其他的函数应该都用不到(__cxa_atexit
是注册函数在程序结束时执行;environ_sizes_get
,environ_get
与环境变量有关;abort
, __cxa_allocate_exception
, __cxa_throw
,__wbindgen_throw
都与异常或程序中止有关),输出一句记录一下。
执行em++ test.cpp -s ALLOW_MEMORY_GROWTH=1
(开启ALLOW_MEMORY_GROWTH
时才有emscripten_resize_heap
的实现),看看生成的胶水文件中的emscripten_memcpy_big
和emscripten_resize_heap
的功能:
emscripten_memcpy_big(dest, src, num)
:相当于memcpy(dest, src, num)
,dest
和src
都是整数,把wasm的内存看成u8
数组,它们就是数组下标emscripten_resize_heap(request)
:调整内存的大小,要求至少包含request
个字节,返回布尔值表示是否成功wasm模块export了一个memory变量,类型是WebAssembly.Memory,依据文档和胶水文件中的实现很容易写出上面两个函数的实现。其实直接复制胶水文件中的实现也是可以的,不过我觉得它的emscripten_resize_heap
太长了,自己写了一个更简单的。
pkg
中用的是import
的方式来初始化wasm模块,我不熟悉js,不知道应该怎么把它改成WebAssembly.instantiate
那一套,同时还能把wasm中的函数export出去。所以我选择把这个wasm文件拷贝到npm项目下,然后直接调用WebAssembly.instantiate
,不依赖pkg
中的胶水文件:
1 | let wasm; |
到此wasm模块就可以正确地初始化了,但是调用其中的函数时仍然可能会遇到运行错误。
一个错误是这样的:
1 | Uncaught (in promise) RuntimeError: function signature mismatch |
这个错误真是折磨了我很久,一点头绪也没有。错误栈中最底层的taglib中的函数基本就是add_tag.cpp
中的第二句话,所以完全想不出来问题出在哪里。后来想到做点对比实验,用一样的输入数据调用这个函数,但是用emscripten工具链来编译,结果就能正常运行。用wasm2wat
查看它的wasm,偶然看到这样一个函数:
1 | (export "__wasm_call_ctors" (func $__wasm_call_ctors)) |
应该是在初始化全局变量,翻了翻taglib的源码,确实定义了一些需要调用构造函数的全局变量,但是我的代码中没有任何地方执行了这个初始化的过程。
用wasm2wat
查看cargo编译出来的wasm,发现根本没有__wasm_call_ctors
。这应该是因为没有导出它,也没有任何其他函数调用它,链接时就被优化掉了,所以需要给rustc提供参数让它导出__wasm_call_ctors
。参考How can I specify linker flags/arguments in a build script?,创建.cargo/config
:
1 | # 注意`--export`和`__wasm_call_ctors`不能写在一行,因为它们必须作为两个参数分别传给rustc |
在得到wasm模块后手动调用这个函数即可:
1 | ... |
另一个错误是这样的:
1 | Uncaught (in promise) RuntimeError: memory access out of bounds |
看起来应该是Rust中申请内存时出错了,我猜测是因为同时存在两个内存分配器,Rust和C++中各有一个,它们都认为自己是独占堆空间的,所以可能会覆盖对方的元信息,导致出错。解决方法就是去掉其中一个,用另一个来代替它。比如去掉Rust中的,直接用malloc
这一套申请内存:
1 | use std::alloc::{GlobalAlloc, Layout}; |
去掉C++中的也可以,但是麻烦一些。首先build.rs
中不再链接libdlmalloc.a
,即去掉-l dlmalloc
,这样wasm文件会从env
中多import三个函数:malloc
,realloc
和free
。wasm模块中还export了__wbindgen_malloc
和__wbindgen_free
两个函数,这是Rust那边实现的,可以用它们来实现malloc
,realloc
和free
,但会有一点困难,因为__wbindgen_free
需要传指针和长度作参数,而free
只有一个指针参数,可能需要手动在内存中维护这种信息,当然也可以直接把。free
实现成空操作
到此所有问题都解决了。
]]>catch_unwind
从panic中恢复。这里记录一下我为了解决这个问题所做的尝试,虽然只有一种方法成功解决了问题,但是过程还是有点记录的价值。为了简单,我的编译器代码中对各种类型错误,比如找不到变量,操作数类型不对之类的,都直接panic
了,而不是用更加方便后续处理的Result
那一套来表示失败。这样做确实简单一些,而且一般编译器本来一次运行也只编译一个程序,panic
终止程序和返回Err
然后上层处理在效果上其实没什么区别,但是如果做一个网页展示,用户会经常改变输入,那失败一次直接挂了就不太好了。我也不愿意为这种事情修改代码,所以要尝试在调用端解决这个问题。
Rust中不鼓励从panic中恢复,但确实存在恢复的手段:catch_unwind。然而当我在wasm中尝试使用这个函数时,仍然不能从panic中恢复,体现在JS那一侧就是函数抛出了一个异常:RuntimeError: unreachable
,但是没法得到panic时的信息。
首先创建一个简单的复现环境:
1 | use wasm_bindgen::prelude::*; |
然后在JS那边调用它,结果会显示RuntimeError: unreachable
,没有任何关于这个字符串hello
的信息。debug build和release build结果差不多,前者会多一些栈帧信息,但是也没有关于hello
的信息。在实际场景中,panic时传入的字符串是描述编译错误类型的信息,希望能够在panic之后得到这个字符串并把它在浏览器中显示出来。
catch_unwind
尝试像平常一样用catch_unwind
来捕获这个panic:
1 |
|
catch_unwind
返回一个Result<T, Box<dyn Any + Send + 'static>>
,如果闭包中的操作发生了panic,就返回Err
,这个Any
中包含了panic的信息。虽然有点麻烦,但是确实可以把panic的字符串从Any
中提取出来,这里就不讨论了,只要能够得到这个Result
就算成功获取了我们需要的信息。
但是结果没有任何变化,work
函数仍然不能正常返回,JS那边还是显示RuntimeError: unreachable
。根本原因在于,catch_unwind
其实本来就不保证能从任意的panic中恢复,它要求panic必须是以stack unwind的形式实现的,但是实际上目前Rust的wasm后端,不管指定panic按unwind实现还是按abort实现,结果都是按abort实现的。我想找相关的代码来验证这一点,但是不知道应该在什么地方找,就放弃了。
用实验来验证比较容易,分别在Cargo.toml
中指定
1 | [profile.release] |
和
1 | [profile.release] |
在release build下生成的wasm文件是逐字节相同的。把profile.release
都改成profile.dev
,结果倒确实不是逐字节相同,但是我简单观察了一下也没有什么本质区别。
所以本质上catch_unwind
这一条路是走不通的,只要现在的wasm后端panic实现不改成unwind,无论做什么修补的工作都不可能让它成功捕获panic,从而得到想要的信息。
panic_handler
之前在一些介绍bare metal下的Rust编程的文章中见过panic_handler
这个东西,简单来说就是在no_std
模式下需要定义一个panic_handler
函数,类型是fn (&PanicInfo) -> !
,用来在panic时调用。
如果能够控制panic时的行为就能解决问题了,但是简单尝试一下之后就放弃了。因为不仅是no_std
下需要定义panic_handler
,而且也是只有在no_std
下才能定义panic_handler
,因为std
中已经定义了一个panic_handler
,自己再定义一个会报重复定义的错误。虽然我的程序稍微改改应该是可以在no_std
下运行的,只要有alloc
就行了,但是wasm_bindgen
目前还是不能在no_std
下运行:在Cargo.toml
中为它定义default-features = false
就可以让它不依赖std
,但是会有很多编译错误,有一个issue讨论这个问题,好像他们本来就没打算支持在no_std
下运行。
set_hook
set_hook函数可以定义一个钩子,在触发panic后,但在unwind或者abort前执行。
一般来说用它不能实现通用意义上的”从panic中恢复”,如果不借助什么魔法的话,不可能从这个钩子函数里将控制流转移到触发panic前定义的的一个位置(类似其他语言中catch的位置),只能在这个函数里做一点处理之后等着unwind或者abort。但是在这个实际问题中,它还是可以达到我期望的效果(获取错误信息,在浏览器中显示出来)的,有两个方法:
RuntimeError: unreachable
错误,然后直接忽略它即可wasm_bindgen::throw_str
,把错误信息字符串抛出去,在JS那边catch它,然后就可以随便操作了。这其实就算是上面说的”魔法”,因为它真的改变了控制流,可以不unwind或者abort,而是跳转到panic前定义的的一个位置,虽然这个位置只能在JS中,不能在Rust中两种方法在我的应用中没有本质区别,我也都尝试过了,但都失败了:第一次和第二次编译一个有错误的程序时都能得到错误信息,但是第三次及之后就不能了,如果第一种方法中不忽略catch到的错误,或者是用第二种方法,都会发现第三次及之后还是得到了RuntimeError: unreachable
。
研究之后发现标准库中执行钩子函数之前会先进行一些检查。在std::panicking::panic_count
模块中定义了一个全局的和一个thread local的计数器,用来表示当前正在发生的panic数量。发生panic时先增加计数器,如果panic被捕获,在后续的清理中会减少计数器,而如果没有被捕获,也就是我们这里的情形,计数器就不会减少。在一般的Rust程序中panic没有被捕获应该会导致整个程序结束,所以减不减少不会有任何区别,但是这里panic没有被捕获只会在JS那边抛出一个异常,不会导致网页崩溃之类的后果,之后还可以调用Rust中的函数,这样之前的计数器的值就留下来了。
在std::panicking::rust_panic_with_hook
函数中执行钩子函数之前会检查这个计数器,如果增加1之后大于2,也就是在第三次调用它时,就会直接abort。我写这篇文章的时候的代码可以参考https://github.com/rust-lang/rust/blob/4b65872d272875adb298b6dc12d5e4e79cf8e263/library/std/src/panicking.rs#L559。
std::panicking::panic_count
模块是private的,所以不能通过调用里面的函数来修改计数器。但是理论上可以找到这两个计数器mangle之后的符号,这样可以绕过private的限制。比如std::panicking::panic_count::GLOBAL_PANIC_COUNT
mangle之后的符号是_ZN3std9panicking11panic_count18GLOBAL_PANIC_COUNT17he5d2b5eed51f22a6E
,可以这样修改它:
1 | extern "C" { |
我在普通的Rust程序中试过了,这样确实可以成功修改GLOBAL_PANIC_COUNT
,但是在wasm中尝试时链接器报告说找不到这个符号。不知道是为什么,我猜测可能wasm中的全局变量不是这样实现的,可能没有符号这样的概念,如果有错麻烦大家指正。
其实还有一个问题。我们实际上不是要修改GLOBAL_PANIC_COUNT
,而是LOCAL_PANIC_COUNT
,但是它是一个thread local变量,看起来实现有点复杂,至少不是直接保存一个usize
,我没有找到它实际存储的地址的符号。
既然是想清零thread local的LOCAL_PANIC_COUNT
,有一个虽然看起来有点蠢,但是理论上还是可行的做法:每次计算都派生一个新的线程,每次新线程中它应该都是初始值0。我直接尝试了一下thread::spawn
,但是派生失败了,想一下也是很合理的,wasm中应该没法做到这种事情。
我看到wasm_bindgen
中已经有用rayon
多线程计算的例子了,本来以为现在wasm中已经支持派生线程了,但是点进去仔细看了一下,它还是基于JS中worker那一套实现的,不是基于thread::spawn
派生的线程。感觉像它一样实现太麻烦了,也不确定能不能达成我要的效果,就没有尝试了。
我的程序中是不需要保存任何全局信息的,理论上每次调用前都可以使用第一次调用前的那一份内存,都应该会产生正确的结果。不管这个计数器到底在内存中的哪里,反正总是在内存里面的,只要重置整个内存总是可以把它清零的。
如果是一般的程序,这个做法看起来没有什么可行性,但是wasm中有一个导出的memory
变量,可以在JS中操作它,这样做就是操作Rust的内存。new TypedArray(memory.buffer)
会创建一个引用memory的数组,修改它就是修改memory,new TypedArray(array)
会把array复制一份,它和原来的内存互不干扰。最后的实现是这样的:
1 | // 一开始执行一次 |
这次尝试最终成功了。
]]>在线演示网站:https://mashplant.online/lalr1。不过其实演示的内容与lalr1的使用没有什么关系,只是用图形或者文字展示一下各种分析表,状态机,大概可以用来算编译原理的作业答案吧。
lalr1的parser生成可以选择基于LALR1(1)文法或者LL(1)文法,鉴于我考虑到应该不会有人在可以使用LALR1(1)文法的情况下用LL(1)文法,这里不会介绍选择LL(1)文法时的用法。其实这个LL(1)文法的版本就不应该存在的,只是以前的decaf实验中有这么一个任务,我才做了这个支持,正常人怎么可能想用LL(1)文法呢。
lalr1生成parser的同时也生成了lexer,lexer内部的自动机是基于同样由我编写的re2dfa实现的。lalr1和re2dfa已经经受住了2019年的编译原理课的检验,不过这一套工具链毕竟不可能像flex,bison那样成熟,所以还是可能有一些不稳定因素,如果遇到了影响使用的bug,希望能够积极汇报。
lalr1有两套代码生成的方式,一种是利用Rust的过程宏,在Rust代码中嵌入产生式的信息,这一种显然只支持生成Rust代码;另一种是读取TOML配置文件并输出代码,这一种支持上面说的三种语言。下面两节分别介绍这两种方式,你只需要阅读你选择的方式对应的一节,之后的两节”解决冲突”和”字符集”是general的,两种方法中都存在相关的概念。
过程宏(Procedural Macros)是Rust的一种语法特性。简单来说,过程宏的编写者定义了一个特殊的函数,它在Rust的编译过程中接受Rust的语法树,在执行任意的变换后返回一个新的语法树。这里不必完全了解过程宏的所有形式,只需要了解lalr1这个库中定义的过程宏怎么使用即可。下面这些小节会分别介绍一些使用上需要注意的点,如果觉得理解有困难,可以结合最后一小节”一个完整的例子”一起阅读。
在你希望成为一个parser的struct的impl块上添加#[lalr1(Start)]
属性,其中Start
是一个非终结符,是parser希望规约出来的最终结果。另外一个必要的属性是#[lex = "TOML of lexer"]
,以TOML字符串的形式定义了词法分析器。此外还可以为impl块添加一些可选的属性,后面会详细描述。对于impl块中的每个函数,都使用Rust的正常函数的语法来编写语义动作,同时用函数级别的属性来描述产生式。
生成出来的Rust代码中不会保留这个impl块,也就是说这里面编写的东西都不会直接被编译。输出的结果包含两个enum,即TokenKind
和StackItem
,两个struct,即Token
和Lexer
,并为你希望成为parser的struct定义一个parse
函数。根据我的使用经验,现在的IDE或者编辑器不太可能把这些符号识别出来并且给予正常的语法提示,如果希望了解具体的API的话,可以使用后面会提到的#[expand]
,也可以使用rust doc
,它会在展开过程宏后再分析API。
#[lex = "TOML of lexer"]
的TOML字符串中应该包含priority
和lexical
两个字段,前者用于指定终结符的优先级和结合性,排在后面的优先级高(其实这与lexer没有任何关系,完全是parser的性质,不过在这里定义它比较方便);后者用于指定终结符的正则表达式,排在前面的优先级高。
每次生成token时,lexer从剩余的字符串的开头查找一个最长的匹配。例如在下面的例子中,字符串int1
中的int
部分匹配Int
的正则表达式,但是int1
匹配Id
的正则表达式,且更长,所以生成一个Id
token;字符串int
同时匹配Int
和Id
的正则表达式,但是Int
排在前面,优先级更高,所以生成一个Int
token。
一个简单的的例子如下:
1 |
这其中Add
,Mul
这些就是终结符的名字,在定义产生式的时候会用到,产生式中不支持直接用终结符的字符串形式来表示终结符,例如yacc/bison中可以写Expr: Expr '+' Expr
,lalr1中是不行的,只能写成Add
。
lexer中包含三个预定义的终结符:
_Eps
:表示解析出这个结果时,lexer不应该告诉parser找到了一个终结符,而是忽略它继续解析下去。这个例子里遇到多个连续地空白字符时返回_Eps
,parser就可以正确地忽略空白字符_Err
:表示无法识别的输入,一般情况下lexer在遇到无法识别的输入时会返回一个_Err
token,而且不会消耗输入字符串,也就是说之后无论调用它多少次都会返回_Err
token。如果像这个例子一样,主动识别.
,也就是遇到任意不符合上面的的模式的字符时返回_Err
,那么输入字符串会被消耗(因为匹配了.
),下一次调用就会跳过这个字符_Eof
:在字符串结束时自动生成一个提供给parser,告诉parser输入结束了生成出来的Lexer
是可以独立于Parser
使用的,下面的代码可以依次输出从一个字符串中解析出的全部token:
1 | let mut lexer = Lexer::new("your string here".as_bytes()); |
re2dfa支持一个正则表达式的子集,这里列举几个不符合正则标准的地方
{n}
,{m,n}
,^
,$
,但是{
,}
,^
,$
仍然需要用\
来转义,直接用会报错()
没有分组作用(当然,因为这里根本没有分组这个概念)\s
,\d
,\w
,但不支持\S
,\D
,\W
.
就是识别所有字符,而不是识别所有非换行符的字符[]
内不支持多字节字符其余功能的支持也不一定完整,如果遇到了什么不符合直觉的结果可以把lexer单独拿出来调试一下,如果的确是re2dfa没有支持的话,就暂且换一个更简单的方法来实现吧,毕竟理论上正则只需要拼接,|
和*
就可以实现所有功能了。
有一些正则表达式虽然合法,也可以构造出正常的自动机,但是这些自动机可能不适合用于lexer。这样的正则表达式有两种,一种是不接受任何字符串,一种是接受空串。之所以说它们不适合用于lexer,是因为在遇到一些corner case的时候比较难以定义它们的行为,例如:
_Eof
还是_Err
?_Eof
还是对应的token?出于这样的原因,lalr1(而不是re2dfa,因为从自动机的角度来说它们是完全合法的)禁止这样的正则表达式。
除了#[lalr1(Start)]
和#[lex = "TOML of lexer"]
这两个必要的属性外,还可以添加几个额外的属性,目前支持的有以下几个:
#[verbose = "output path"]
向指定路径输出一些用于调试的信息。
调试信息首先包括文法符号的编号及其对应的名字,产生式的编号及其对应的内容,剩余部分依据使用的文法而定。使用LALR(1)文法时为action表,出现冲突警告的时候可以利用这个文件来帮助查找语法中的问题。action表中包含每个节点包含的产生式及点的位置(不包含向前看符号),以及每个节点处遇到终结符时的移进/规约/接受动作。每个动作都会在后面加上一个表示冲突情况的符号,示例如下:
1 | A => Shift(1) (✓) |
其中(✓)
表示它最后被解析器采用,(-)
表示它被利用优先级和结合性消除了,(✗)
表示它被”强行”消除了,对应于一个冲突警告。详见后面的”解决冲突”一节。
#[log_token]
在生成的代码中,每当新解析出一个非_Eps
的终结符时,输出它的相关信息,也就是输出一个struct Token
,包括token类型,字符串片段,行号列号。
#[log_reduce]
在生成的代码中,每当执行一次规约时,输出产生式。
#[use_unsafe]
在生成的代码中使用一些unsafe
来减少运行时检查,以期提高性能。例如将不可达断言转化成不可达hint,取消下标越界检查等。
理论上无论你编写的parser是什么样的,无论输入的字符串是是什么,只要成功生成代码并且成功编译了,这些unsafe
都不会导致任何未定义行为(至少lalr1的目标是这个,至于是否真的达到了,目前暂且不能做出保证)。
#[expand]
输出生成的代码。之所以添加这个选项而不建议大家使用cargo expand
来查看生成的代码,是因为后者把所有宏都展开了,不利于阅读或者调试,而且后者要求编译过程至少成功进行到了某个阶段(具体哪个阶段我还不清楚)才会输出,这对帮助解决编译错误可能是没有帮助的。
lalr1生成的Rust代码不包含位置信息,所以parser中的编译错误和运行错误都无法得知具体在什么位置,而如果使用#[expand]
输出的代码来代替整个impl块,效果是完全等同的,这样可以方便调试或者看自己具体那里编译出错了。
#[show_fsm = "output path"]
和#[show_dfa = "output path"]
分别把parser的lr fsm和lexer的dfa的图形以dot文件的形式输出到对应路径中。其实是没啥用的一个功能,对于稍微有一点意义的语法,这两个自动机都太大了,甚至dot文件都不一定能顺利地渲染成图片,更不用说靠人眼从中提取什么有用的信息了。
在impl块内部将产生式和语义动作结合起来,以函数的形式编写代码,一个例子如下:
1 |
|
以下几点需要注意:
'p
是硬编码的,这意味着你把所有的'p
都换成'a
或者别的什么的,是不能work的。当然,理论上可以在过程宏中更加精细地提取代码中的这些信息,但是我想了一下,感觉收益明显小于付出,不多费事了Token
类型,但是这个检查完全是基于字符串比较的,并不会考虑任何Rust层面的语义信息(如类型别名),所以是有可能把两个相同的类型判定为不相同的。编写类型也可能带来很多冗余,但是我认为这是有必要的,这是为了最大程度的保证生成器生成出来的代码能够通过rustc编译,避免后续产生不必要的编译错误函数上还可以添加一个属性:#[prec = "Term"]
,这是指定这条产生式的优先级与终结符Term
相同。具体作用的会在”解决冲突”一节描述。
下面以一个解析四则运算表达式的parser为例子讲解:
1 | struct Parser; // 用户希望成为parser的struct |
跑一个实际的例子:
1 | assert_eq!(Parser.parse(&mut Lexer::new(b"1 - 2 * (3 + 4 * 5 / 6) + -7 * -9 % 10")), Ok(-8)); |
结果显然是正确的。后面的内容其实就不影响使用了,可以跳过。
利用#[expand]
,得到过程宏输出的代码如下(为了美观,代码经过了格式化):
1 | // 两个宏用来集中管理safe和unsafe模式下的下标访问和不可达 |
DFA的图形是这样的:
再看一个巨大的LR(1) FSM(美观起见,我把Add
等终结符换成了对应的符号,_Eof
替换成了#
):
值得注意的是这个LR(1) FSM是没有经过解决冲突的处理的,例如图中的这一处:
显然是有移进-规约冲突的。文本表示的verbose信息中体现了冲突的解决,这个片段对应于verbose.txt
中的:
1 | State 13: |
可见这里所有冲突选择都被优先级和结合性的约束给消除了。
如果你选择使用过程宏来生成代码,那么这一节可以完全跳过。
首先通过如下指令安装parser_gen
,这个二进制程序就是负责读入TOML文件,输出指定的目标语言:
1 | cargo install --git https://github.com/MashPlant/lalr1 --features="clap toml" |
直接运行这个程序就可以得到帮助信息,因此这里不再介绍怎么使用它。
输入的TOML文件中可以包含如下字段(可以参照https://github.com/MashPlant/lalr1/tree/master/parser-gen/examples文件夹下三个目标语言的TOML文件一起阅读):
include
:字符串,会被添加到输出的文件的开头位置priority
:与过程宏中”编写lexer”一节的priority
格式完全相同lexical
:与过程宏中”编写lexer”一节的lexical
格式完全相同parser_field
:字符串列表,每个字符串表示parser结构体的一个字段start
:字符串,表示初始非终结符production
:列表,每个元素表示一组左端项相同的产生式,用一个TOML中的map表示,结构如下:lhs
:字符串,左端项的名字ty
:字符串,左端项的类型rhs
:列表,表示一组产生式,每个产生式也用一个map表示,结构如下:rhs
:字符串列表,每个字符串是产生式右端的一项rhs_arg
:可选的列表,每个元素是一个长度为2的字符串列表,分别表示一个右端项在语法动作中的名字和类型。如果存在的话它会被用于类型检查,不存在的话这个检查就跳过了,生成的代码交给目标语言的编译器去检查。一般来说在TOML文件中描述这些类型信息太麻烦了,所以这个功能一般只用于过程宏模式下,这里直接不写这个字段就行了act
:字符串,是目标语言的一段代码,表示语法动作。这里可以使用产生式的右端项,如果没有在rhs_arg
中为它们起名字的话,Rust和C++中用_1
,_2
…等表示它们,Java中用$1
,$2
…等表示它们。Rust代码中这个语法动作最终应该是一个值(语句块的最后一个表达式如果没有分号的话就是整个语句块的值),C++代码中最终应该把结果赋值给__
,Java代码中最终应该把结果赋值给$.$
。这一段代码不是放在一个独立的函数里面的,而是都嵌在一个大函数里面,所以不要在这里写return之类可以改变控制流的语句prec
:可选的字符串,如果存在,表示一个终结符,本条产生式的优先级与这个终结符相同,详细功能见”解决冲突”一节parser_def
:可选的字符串,如果不存在,就会定义一个名称为Parser
的结构体,并为它定义parse
函数;如果存在,就会直接为名称为它的结构体定义parse
函数。目标语言为Java时这个字段会被忽略,总是会定义一个名为Parser
的类。生成的代码的基本功能和实现方式基本上都和基于过程宏的版本是一样的,所以没有必要重复介绍了。
我找了很多文档,也用yacc/bison做了很多实验,但仍然没有总结出一个完整的解决和汇报冲突的策略。既然这样,那我就不尝试实现它的规范了,lalr1以我的规范为准,如果和yacc/bison的表现不一致,那就不一致吧。(当然,如果它的表现和我声明的规范矛盾了,那肯定还是一个bug)
首先定义产生式的优先级(不定义产生式的结合性,因为没有意义且不会用到)。没有#[prec(Term)]
时,产生式的优先级与右端最后一个终结符的优先级相同,如果右端没有终结符或者右端最后一个终结符没有定义优先级,那么这条产生式就没有优先级;有#[prec(Term)]
时,产生式的优先级与与终结符Term
相同,同样如果Term
没有优先级,那么这条产生式就没有优先级。
先假设只有两种冲突选择:
以上就是解决冲突的全部内容了吗?这的确是文档里能找到的全部内容了,但我认为事情还没说完:在一个状态转移上,可行的选择可能大于两个。这一定是有大于两个规约选择,或者有一个移进选择和大于一个规约选择。
这之所以会成为一个问题,是因为没办法通过直接推广上面的两两比较来选择出一个”最好”的选择。在集合{ 移进,规约1,规约2,…,规约n }上如果定义x ≤ y ⇔ x和y冲突的时候选y,那么这个关系并不是一个合法的偏序关系,例子如下:
1 | 规约x:无优先级,出现在第2位 |
那么满足x ≤ y和y ≤ z,但是不满足x ≤ z。
即使先不考虑所有的需要”汇报一个冲突警告”的情形,也就是说产生式的优先级和终结符的优先级和结合性都存在且产生式的优先级不会相等,这时这个关系的确是一个偏序关系。然而如果需要处理结合性为不结合的终结符,仍然不能做出非常令人信服的选择(因为这个偏序集仍然不是全序的),例如:
1 | 规约x: 优先级1 |
有两种理解是合理的:一是规约y和移进z”抵消”了,所以选择规约x;二是规约x因为优先级不如规约y而不能选择,规约y和规约z因为终结符不结合也不能选择,所以没有选择。
我没有查到yacc/bison是怎么处理这些情况的,试图通过实验来归纳的时候也遇到了很多无法理解的问题,没有得出有用的结论。简单起见,规定如果有大于两个冲突选择,lalr1将拒绝生成代码。它会把冲突情况做出一定程度的汇报,然后失败退出。
上面一直在提”字符”的概念,这其实是一个非常模糊的表述,为了严谨起见有必要说明清楚。不过实际上我相信实际用lalr1编写的parser基本都是用来识别纯ASCII码的源程序的,所以应该是不会遇到字符编码相关的问题的。
re2dfa是完全基于字节的,也就是说从输入的正则表达式,到内部的处理过程,到最终生成的自动机,都假定了处理的字符集一定是在0, 1, ..., 255
这个范围内的。最终生成的自动机对各种编码完全没有感知,只是逐字节读入数据并执行状态转移。所有的目标语言都是这样的:Rust中逐个u8
地读入,C++中逐个char
地读入,Java中逐个byte
地读入。
不过这并不意味着生成的lexer只能识别单字节字符组成的字符串,因为即使输入的正则表达式中包含了需要多个匹配字节才能识别的字符,这对自动机来说无非就是多执行几次状态转移而已,并没有什么困难的(当然也有困难的情形,如前面所说[]
内不支持多字节字符,就是因为如果要实现的话会增加不少复杂性,考虑[^a]
和[a-b]
的情况,其中a
和b
是多字节字符)。例如一个UTF-8编码的正则(测试)*
的自动机如下:
这其中的230 181 139 232 175 149
就对应于测试
的每个字节。
在现在这样的实现下,可以很容易地精确描述出正则中的一些特殊字符的具体含义:
.
:等价于[\x00-\xFF]
(大家可能不熟悉\xHH
的写法,这是一个值为16进制数HH
的字节)\s
:等价于[\n\t\r ]
\d
:等价于[0-9]
\w
:等价于[0-9a-zA-Z_]
虽然这看起来没什么特别的,但是在对字符编码有感知的正则表达式实现中并不是这样的,例如Rust的正则库regex的规范中规定:
\w
,\d
and\s
are Unicode aware. For example,\s
will match all forms of whitespace categorized by Unicode.
举例来说,它的\s
可以匹配中文全角空格,而re2dfa中的\s
就不能。
我自己尝试实现了一下Earley算法,代码:https://github.com/MashPlant/earley。前端演示:https://mashplant.online/earley/
算法的基本信息在wiki上面都有,我这里就简单复述一下。
Earley算法可以解析任何CFG,就像我们自动机课上学的CYK算法一样。Earley parser对于确定性不同的CFG时间复杂度不同,一般情况下是输入长度的立方(这与CYK算法是一致的),对于无歧义的CFG是平方,对于确定性的(即是某终态接受DPDA的语言,也即是LR(n)语言)CFG是线性。
wiki上面介绍了Earley recognizer的实现,recognizer和parser的区别在于前者只是判断一个输入串是否属于给定的语言,而后者还要解析出输入串的语法树。有一句话让我很不满意:
The recogniser can be easily modified to create a parse tree as it recognises, and in that way can be turned into a parser.
实际上从我的实现来看,把recognizer拓展成parser占据了绝大多数时间和代码量。Jay Earley最初的论文An Efficient Context-Free Parsing Algorithm(这个太长了,我没读过)中提出的把recognizer拓展成parser的方法被指出是有错误的,现在比较正统的方法应该是论文SPPF-Style Parsing From Earley Recognisers中提出的,可惜的是我读了一会,感觉读不懂。
最终我的实现基本上都是参照于http://loup-vaillant.fr/tutorials/earley-parsing/。感觉这个人的文章写的相当不错,能看出他理解的很深刻,风格也算清晰易懂,应该是比我写的好多了,推荐大家去读一下。
首先定义Earley item:一个LR(0) item和一个整数的元组,当然也可以看成一个产生式,两个整数的三元组。其中的LR(0) item与LR算法中的意义一致,产生式表示正在解析的产生式,点的位置表示左边的串已经完成解析;还有一个整数,它表示这个产生式是从输入串的这个下标处开始解析的。一个Earley item表示如下:
1 | (X -> α • β, start) |
设输入串的下标从0开始,输入串的长度是N
,为[0, N]
每个位置维护一个Earley item的集合,用S[i]
表示。S[i]
的含义是完成读入了输入串的[0, i)
后发现的所有Earley items。S[i]
中一个上面那样的Earley item表示的意思就是,正在尝试把输入串的[start, i)
这一段解析成X
,已经解析了其中的α
部分。
算法的核心就是执行一个类似DP的过程,这个伪代码长的有点像Rust,具体语义不难想象:
1 | S = [{}; N + 1] |
最终如果S[N]
中存在(Start -> γ •, 0)
的话,就意味着整个输入串可以解析成γ
,也就可以解析成Start
,这个串被接受。反之则不接受。
看着这个代码,如果你熟悉Rust的话,肯定能反应过来一个问题:这样会出现迭代的过程中修改集合的操作,在Rust里肯定是不能通过编译的。Rust只是把这个问题暴露出来了,用别的语言的话也会遇到逻辑上的问题,例如我在执行S[i].add
后,这个新加入的项后面迭代的时候会不会遇到?还有可能新加入的项满足之前的某个规则(注意complete规则中i
可能等于x
,所以有可能先执行了complete规则,之后才把一个这样的项加入S[i]
),这样要不要重新考虑一遍之前执行的规则?
对于第一个问题,答案是for item in S[i]
这个for
会考虑后续加入的项,for (B -> α • A β, y) in S[x]
这个for
不考虑后续加入的项。迭代的过程用下标而非迭代器来写大概是这样的:
1 | for (j = 0; j < S[i].len(); ++j) { // 每次len()都要重新读取 |
对于第二个问题,答案是原始的Earley算法不考虑,同时这也引入了下面的问题。
原始的Earley算法在处理存在可空的非终结符的语言时,可能会得到错误的结果。例如语言:
1 | S -> A A |
设开始符号是S
,显然空串是这个语言的串。但是运行一下上面的算法,结果是:
1 | =====S[0]===== |
到此为止了,S[0]
中没有(S -> A A •, 0)
,因此按照上面的算法,空串不被接受。其实这个时候如果再对(A -> •, 0)
执行一次complete规则,因为(S -> A • A, 0)
存在,所以会把(S -> A A •, 0)
加入S[0]
,这样就对了,但是原始的Earley算法中不会再考虑一次(A -> •, 0)
。你可能会问是不是for (B -> α • A β, y) in S[x]
这个for
考虑后续加入的项就能解决问题呢?对上面这个语言确实是的,但是对这个语言,还是不行,所以这不是本质问题:
1 | S -> A B A |
考虑一下,发生这种情况的根本原因是什么?是因为complete规则中i = x
,所以才需要纠结后续给S[i]
新加入的项会不会影响到S[x]
。什么情况下i = x
?从x到i成功解析了A,而i = x
,这意味着A
是一个可空的非终结符。
论文Practical Earley Parsing中给出了一个解决方案:在predicate规则中加入一个操作:
1 | ... |
至于怎么计算每个非终结符是否可空,如果不追求效率的话就是很基本的自动机/编译原理的知识,相信大家都会。
上面的语言中,S
和A
都是可空的,运行一下修改后的Earley算法看看:
1 | =====S[0]===== |
成功接受。
SPPF-Style Parsing From Earley Recognisers给出的方法是能够保证立方的复杂度的,可惜我看不太懂。http://loup-vaillant.fr/tutorials/earley-parsing/parser给出的方法很好理解,也比较好实现,但是他没有证明复杂度,我也没有能力和兴趣去证了。
基本思路就是从recognizer生成的表格中通过搜索得到树。首先把表格做一个类似转置的操作,方便后续分析:对于S[i]
中的(A -> γ •, x)
(只考虑点在最后的项,表示解析完成了),在S'[x]
中加入(A -> γ, i)
(点可以忽略了,因为只加入了点在最后的项),两者都是表示从x到i解析出γ
。
已知S'[0]
中存在(Start -> γ, N)
,对于γ
中的每一项,从第一项开始,它必须是从0开始解析的,看它是终结符还是非终结符,如果是终结符就把它和输入串中的对应位置进行匹配,如果不相等的话意味着这一支搜索失败了(第一项是不可能失败的,后面的项就有可能失败了),相等的话就从1开始解析γ
的第二项;如果是非终结符A
,对于每个S'[0]
的(A -> α, x)
,分出一支进行搜索,因为它终止于x,所以之后就应该从x开始解析γ
的第二项。
如此这样搜索下去,如果没有失败地考虑了γ
的每一项,而且最终的结束位置等于N
,这就是一支成功的搜索,表示成功地为S'[0]
中的(Start -> γ, N)
的每个孩子找到了一个产生它的项,接下来再继续对这些项进行类似的搜索即可,这个过程没有必要是递归的,可以把他们保存到队列里面,做广搜之类的,不然递归套递归可能有点难以理解。
这些在上面的文章里没有,是我自己想到的。构思好了搜索的逻辑之后我很正常地想到搜索的过程中是不是可能出现相同的节点被多次搜索,比如:
1 | S -> A B |
按照上面描述的搜索,第一支是先搜A -> A1
,再搜右端为B
且从1开始解析的,第二支是先搜A -> A2
,再搜右端为B
且从1开始解析的,后面搜索的目标是一样的,也必然会得到相同的搜索结果,如果重复做一遍的话是一种浪费。
这其实就是朴素的SPPF(Shared Packed Parse Forest)的想法,它是用来高效表示有歧义的语法产生的多个语法树的数据结构。我没有找到它的官方定义或者是最先提出它的论文,所以我也不能确定我对它的理解是完全正确的,如果有错误的话麻烦大家及时指出。
SPPF是一个有向,可能有环的图,每个节点包含的信息有一个产生式和一段输入串,表示这段输入串可以按照这个产生式解析。每个节点连出一组边,每个边表示一组得到产生式右端式子的每一项的节点集。用我的程序输出上面的语法解析”a b”得到的SPPF是(我为了让它好看一点,做了一点修改),一个圆圈连出的边就是这一组节点集:
对上面描述的搜索算法做一点修改就可以生成SPPF,而不是一组树。对一个右端式子进行搜索,生成了一组合法的生成右端式子的组合时,将其中每个产生式和范围的元组加入一个集合中,得到一个id之类的东西保存起来,这样就把边连起来了,如果集合中已经存在这个产生式+范围的话就意味着出现了节点的重用。一次搜索结束后,继续对集合中未搜索的元组进行搜索(就是上面提到的广搜)。
除了节约空间之外,SPPF还带来了一个好处,因为它可以是有环的(这一定是因为存在可空的非终结符),所以它可以表示无限多可能的语法树。例如这个语法:
1 | A -> A |
用它解析空串生成的SPPF是:
到这里基本就跟Earley算法没有关系了,不过也算是parser的一部分。这个也花了我挺长时间的,看起来是一个比较正常的需求,但是找了半天也没有发现已经存在的算法,只能自己想了。其实也不算太复杂,但是毕竟我水平有限,还是想了一段时间才想出来。
首先想一个深搜的算法,搜索所有的树。这里有一个难点在于,每个节点往下可以有多个选择,每个选择中还有多个待搜索的节点,这就和简单的深搜不太一样了。主要还是要搞清楚访问节点的顺序是什么。以倒数第二张图为例,从S -> A B
开始,首先选择最左边的圆圈(圆圈并不是具体的节点,而是表示一组节点),然后往下走,先看A -> A1
,需要搜索它的全部可能,比较幸运的是只有一个。继续往下走,到了a
的时候停下,这个时候走完了这个分支,但是仍然没有为S -> A B
构造一棵具体的树,因为B -> B1
那一边还没有探索,所以需要能够从a
这里跳到B -> B1
继续搜索。
这个例子比较简单,但是也能看出来一点思路了:除了深搜的调用栈之外,还要再维护一个栈,每次选择一个产生式右端式子的一种可能时,只进入右端式子的第一项,而把剩余部分的信息保存到另一个栈中。当搜索进入一个叶子节点时,从这个栈中弹出一项,对它继续搜索;如果此时栈为空,即证明整个搜索已经完成。
然后把深搜的版本改造成一个基于栈+状态机的版本,这并不是为了性能或者防止爆栈,而是希望能给用户提供一个更便捷的接口,能够像访问迭代器一样访问所有可能的树。而且考虑到树的总数可能是无限的,这个转化就更重要了,理论上就不能用深搜构造一个容器来保存所有的树再把容器返回给用户。其实我之前还想过使用Rust的Generator,另一篇文章试用Rust的Generator其实就是为了这个写的,不过写完之后感觉现在的Generator还是不够成熟优雅,并不是很想用,还是自己转化成基于栈的版本吧。
这个其实就是机械的工作了,我一直都知道这是可行的,不过也没有尝试过,这次终于手动操作了一回,完成了之后还优化了好久才达到自己比较满意的样子。现在用户可以像这样生成树了:
1 | let mut it = sppf.iter(); |
说点关于Rust的题外话。你可能会好奇我为什么不用Iterator::enumerate
来实现类似的功能,其实是是因为it
并没有实现Iterator
。Iter::next
的定义是:
1 | pub struct Iter<'a> { |
比较遗憾的是这样的next
不符合Iterator::next
的要求,要让现在的Iter
实现Iterator
是不可能的,读者可以自己思考为什么。
Rust中的Generator这一套很久之前就提出来了,在issue#43122里一直从2017年讨论到现在,还是没有稳定,我也没见到什么知名的项目用过它,如果是我见识太短浅了麻烦提醒我。好像这个东西就一直游离在语言的边缘,看上去有点用,但是一直没有人用。我不太了解历史,但是总觉得async
那一套出现的比它还晚,成熟的比它还早,如果我错了的话也麻烦提醒一下。
Generator
trait类比async
的核心是Future
trait,Generator
的核心是Generator
trait。把这两个trait的定义列出来比较一下:
1 | pub trait Future { |
其实是相当类似的。简单描述一下相同点和不同点:
self: Pin<&mut Self>
为参数,因为它们内部都可能保存了指向自身的指针,只有这个数据结构被Pin
住的情况下操作它才是安全的。Future
只有一个类型参数Output
,表示最终返回的结果,Poll
其实就相当于Option
;Generator
有两个类型参数Yield
和Return
,可以在返回零次或多次Yield
后最终返回Return
,GeneratorState
其实就相当于Result
。Future
的poll
中有一个额外参数cx
,一般是用来注册回调的;Generator
的resume
中有一个额外参数arg
,也就是每次进入的时候都可以传一个参数,但是我个人觉得这和我印象中的生成器并不是很匹配,生成器不应该是给出一个初始参数构造好之后就像一个迭代器一样不断生成值吗?每次生成值的时候传入新的参数,这是一个什么样的语义呢?什么情况下能用得到呢?我现在也还不理解,希望得到解答。unstable-book中有一章讲Generator
,我稍微简化了一下它的例子:
1 |
|
就像一般不会手动实现Future
trait,而是用async fn
或者async
块来得到实现了Future
trait的匿名类型一样,一般也不会手动实现Generator
trait。上面的代码中g
的值是一个generator literal
,形式是一个闭包,里面可以用yield
关键字来yield一个值。g
的类型就是一个实现了Generator<(), Yield=i32, Return=&'static str>
的匿名类型。
为了调用g
,需要先用Pin
把它包裹起来再调用resume
,这是Generator
trait的要求。每次调用resume
,就从上次停下来的地方开始(第一次执行时就从开头开始),执行到yield
或者return
或者末尾,从而yield或者return一个值。这还是很符合直觉的,其他语言中如果有类似的feature的话,语义差不多也是这样的。
需要注意的是,这里用了闭包的形式来定义Generator
,这并不是为了简化代码而不用函数定义,事实上当前的编译器中不支持用函数定义Generator
。具体细节请看error#E0627。而Rust中的闭包想要引用自身相当麻烦(确实是可行的,但是需要一堆丑陋的boilerplate),所以目前应该是不太可能像其他语言(比如Python)一样很愉快地写递归的Generator
。
unstable-book中说:
At this time the main intended use case of generators is an implementation primitive for async/await syntax, but generators will likely be extended to ergonomic implementations of iterators and other primitives in the future.
这里面说了两点,第一是作为实现async
那一套的原语,我理解可能是编译器先把async
那一套desugar成yield
那一套,这样后续步骤就只用处理yield
,毕竟这两种东西的语义还是有很多近似的地方,这样做是可以理解的,不过跟我们用户好像没什么关系。
第二是用来实现迭代器之类的,这个我觉得是一个很符合实际需求的用途。比如我可能有一个递归函数,里面产生一些值,希望能把值的序列以迭代器的形式返回给用户,在当前的稳定Rust
中我能想到的唯一的方法就是自己手动维护栈,实现状态机。如果能够在产生值的地方yield
,然后整个函数自动成为一个Generator
,那就方便多了。Generator
和Iterator
也很类似,前者一直返回Yield
直到Return
,后者一直返回Some(Item)
直到None
,所以用户很容易就可以把这个Generator
当成迭代器用。
但是现在的Generator
是不能直接这样写的,上面也说了,当前的Rust
没有提供直接在Generator
中递归的手段。
上面说了不能直接写,但是经过一些不算太复杂的改写,还是可以用同样结构的代码来实现这个需求的。
首先造个简单的轮子,把Generator<(), Yield>
当Iterator<Item=Yield>
用,这里Generator
的resume
的参数(默认)是单元类型,和我上面说的一样,这才是比较符合直觉的,后续执行的时候应该不用再提供信息;resume
的返回值直接丢弃,对应于next
返回None
。
1 |
|
先看一个Python版本的:
1 | class Tree: |
Python大家应该都会,没有什么可说的。唯一可能不太常用的是yield from
,请看pep-380。
先在Rust里造一棵一样的树:
1 | struct Tree(u64, Option<Box<Tree>>, Option<Box<Tree>>); |
我希望不用写闭包引用自身的那一坨代码(其实我也没有试过这个方法是否能用于Generator
),所以还是需要一个函数,这个函数自身不可能是Generator
,所以只能让它返回一个Generator
。先写个大概的框架(相信大家能够理解'a
在这里的意义):
1 | impl Tree { |
之前说希望能返回给用户一个迭代器用,而不是生成器,所以再改改,用上之前造的Iter
:
1 | fn inorder<'a>(&'a self) -> impl Iterator<Item=u64> + 'a { |
问号的地方,就是希望执行Python中的yield from
的操作,也就是从一个生成器(在我们这里是迭代器)中生成所有元素,Rust里没有yield from
这个集成的操作,只能自己手动写这个逻辑:
1 | fn inorder<'a>(&'a self) -> impl Iterator<Item=u64> + 'a { |
这时编译会报错,大概长这个样子:
1 | error[E0720]: cannot resolve opaque type |
虽然不知道具体原因,但是可以猜测是因为递归调用的返回类型与函数的返回类型一样,编译器类型推断的时候出现了循环。这种情况的解决方案一般是用Box<dyn trait>
套一层来打破循环:
1 | fn inorder<'a>(&'a self) -> impl Iterator<Item=u64> + 'a { |
到这里就已经完成了,加点输出:
1 | fn main() { |
可以再定义一个宏来提取一下这个手动的yield from
的操作:
1 | macro_rules! yield_from { |
这个例子和上面那个没有什么本质区别,可以跳过。
我记得我在初学Python的时候就接触过一个经典的例子:展开一个不规则的列表的列表,比如把[[[1, 2, 3], [4, 5]], 6]
展开成[1, 2, 3, 4, 5, 6]
:
1 | def flatten(l): |
用Rust来写一下:
1 | enum Elem { |
需要声明的是,上面这些转化并没有把一个依赖调用栈的递归函数的变成一个依赖堆上的栈进行状态转移的非递归的数,所以该爆栈的时候还是会爆栈的。这在Python里也是一样的,下面这两个例子都会爆栈,如果你的环境下没有爆的话,要么是数字还不够大,要么是编译器做了优化,本质逻辑上还是依赖于一个调用栈的。
1 | def range(n): |
1 | fn range(n: u64) -> impl Iterator<Item=u64> { |
纯粹无聊,想测试一下几种方法来实现递归的性能怎么样。只是随便测一下,满足我自己的好奇心,并不保证什么严谨性。这里用树的中序遍历来测试,求所有节点的和,实现递归的方法包括直接递归,用Generator
实现递归,用标准的栈实现中序遍历的算法实现递归,用栈+状态机实现递归。代码如下:
1 | impl Tree { |
树就随机插入数据来构建,这个代码就太平凡了,不展示了。随机插入1000000个数,求和20次取平均时间,结果如下:
inorder_rec | inorder_gen | inorder_stk | inorder_stk1 |
---|---|---|---|
36.85ms | 212.3ms | 36.45ms | 44.55ms |
可见现在的Generator
的性能还是相当一般。标准的栈实现中序遍历的算法的性能和直接递归非常接近,甚至略优一点,不过对于现实中的问题不总是有这种优美的解法,但是都可以改写成基于栈+状态机的版本,这个性能虽然差一些,但也可以接受,我个人相比于用Generator
更推荐它。
“因循苟且,逸豫而无为,可以侥幸一时,而不可以旷日持久。”
从寒假开始讲起吧。2020.1.20坐火车从北京回武汉,当天注意到新闻里关于疫情的报道渐渐变多,但是心里还是觉得这应该不会太严重,甚至还想着,”我全程戴着口罩坐火车回来,这应该就是对我的生活的最大的影响了吧”。事实证明后来的影响大到了无可估量的地步。
1.21的时候相关的新闻就开始爆发了,而且向着越来越严重的方向发展,后来我想,如果我只是计划晚一天回去,也许就不会回去了。我当然觉得这是一件很可惜的事情,因为自己在家里一直在划水,想着如果在学校的话肯定会好一些,至少不至于用性能这么差的电脑。不过我估计,假设我真的没有回去,那我可能也会觉得可惜,想着如果我在家的话会怎么样。说到底是我自己的问题,跟在哪里也许没有什么关系。
从回家到开始上托福课,再从托福课结束到开学,两段时间里几乎什么都没有做,现在唯一还记得的就是写了一个Haskell版的Decaf编译器(当然,上托福课的时候也什么都没有做,所以这就是整个寒假我做的唯一的事了)。只写到PA3,也没有仔细比较过结果,只是测了几个例子。Haskell真的太难了,每写一步都要考虑很久,而且因为惰性求值,我经常在尝试一些看起来完全等价的写法,有时候会卡死,有时候就不会,说到底我根本没有理解其中的原理。写到PA3已经花费了我很多时间,我完全无法设想后面的PA4/5能怎么写了。
我自己尝试过学Haskell很多次了,这也是一次尝试,虽然这次有充分的时间可以投入,但是还是感觉太难了,学不懂。从那到现在,我也没有再碰过Haskell,感觉这和我日常的编程实在差的太远了,这样的话估计当时学的东西到现在也全都忘记了。
我是对软件分析与验证这个方向挺感兴趣的,但是连Haskell也学不会,这不是相当于门槛也没有跨进去吗,那我还配感兴趣吗?
上学期得知很多学校要求托福考试时间是在入学的两年以内,而不是申请的两年以内,所以之前的那次托福很有可能等于白考了。这在上个学期让我心情低落了相当长一段时间。本来是打算这个寒假报个托福班,再学一次再考一次的,但是遇到了疫情,考试取消了,课程也改成线上的了。这样其实基本不太可能认真的投入了,感觉每天浑浑噩噩的,就这么混过去了,口语自己几乎根本没有练,听力和作文也还是很不熟练。当然,后续也一直也没有考试的机会,即使当时真的掌握的更好,对之后的考试其实也是不会有任何帮助的,我的记忆都是相当短期的。
我想起来去年的这个时候,自己一个人住在寝室里,那时又回想起高中有段时间一个人在寝室里准备化学竞赛,感觉情景非常类似。十几天里,每天早上六点多起来跑个步,吃一点早餐后骑车五公里去上托福,上午上课,下午做题和记单词,结束后再骑回来,晚上在寝室里对着听力和口语发呆,感觉像是不可逾越的困难一样。考试的前一天晚上绕着操场走了几圈,当时心里想的是,我这学期应该选OS,不能让别人对我失望;同时这学期也是学GRE的最好的时间了,也应该报个班。那时我真的感觉自己是在为未来而努力,但后来证明这些都是徒劳,做或者不做对未来没有一点的影响,就像我为之努力过的很多其他事情一样。
事情很少,我自己找了几本书看。有些看得进去,比如TAPL,有些看不进去,比如SSA Book和Software Foundation。但是不管看不看得进去,现在里面的内容我都已经不怎么记得了。我自己当时心里应该也知道这一点,只是不愿意承认:我现在做的事情,没有意义。
chyyuu每周有一个交流时间,参加了几次,每次都只能说没有什么成果。我在忙什么呢?作业吗,看书吗?其实根本没有什么忙的,就是什么都不想干,最好能找点什么事情做来打发时间,实在没有的话就玩玩手机也是差不多的,反正结果也没有什么区别。这个交流参加了几次之后就没有再参加了。
我大概不能算一个合格的大学生,至少肯定不是一个合格的清华学生,我应该还是一个合格的高中生吧,只会做题的那种,真是对不起我的母校。花时间在考试和作业上,虽然心中可能会有些厌烦,但是心里也很充实,感觉这就是我应该做的事情;要是让我自己去探索点什么,没有立刻的反馈,也没有随时的指导,那我只能说,我目前还没有成功过一次。
如果我想要改变的话,大概也只可能改变自己对这件事情的心态,我想是不太可能改变这件事情本身了。现在我基本上是抱着这样的心态的,我没有必要因为自己会失败而拒绝什么可能的尝试,但是我也不太可能主动去争取,最后做成什么样子就随它去吧。
工作量真的很小,只有至多两次编程作业(我们还只有一次),两三百行的小玩具级别的。还有一个非常简单的考试,就是一些考基本概念的选择题,和一个找代码缺陷的大题。这种找代码缺陷的题老师上课也经常讲。
虽然考试很简单,但是我就是必须要很认真地去准备,ppt看了有三遍,其实可能一遍都不用看我也会做,我还是这么在乎分数,这是没有办法的事情,虽然我现在心里也渐渐承认,提高这一点GPA对未来可能没有什么帮助了,但是分数早就成为了我的信仰,是我怎么样也不会放弃的。
前半段是一个研究生来讲一些基本的存储技术,比如SSD什么的,当时听的感觉还有点慌,感觉记不住,但是一看没有考试就不担心了。后半段是老师来讲一些应用,基本没怎么听。
有两次大作业,第一次是写一个KV引擎,代码在https://github.com/MashPlant/undergraduate_projects/tree/master/存储技术基础/kv。我写的挺简单的,基本就是一个内存里的map,只是保存到磁盘了而已。它有一些原子性,持久性的要求,我感觉如果要满足这些要求的话也没有什么花样可以玩。第二次是用FUSE自己随便写点什么,是组队的,不过我自己做了。我没有什么创意,看到指导里说可以写一个网络学堂映射的文件系统,那我就写了。先是照着Harry Chen的thu-learn-lib自己造了一个Rust版的thu-learn-helper,感觉写的还是很舒服的。然后基于fuse-rs造了一个网络学堂映射的文件系统,代码在https://github.com/MashPlant/thu-learn-fuse,这个库感觉想法还可以,用起来也还比较舒适,但是好久没维护了,不知道是不是凉了。
还有一个组队论文阅读的作业,每个人读几篇论文,一起写个报告。我基本没有怎么读过论文,感觉我的阅读能力还是不行,精神不好的时候经常就往谷歌翻译里一拖,看那些磕磕绊绊的中文也比这种状态下看英文快。可能我真就只擅长考试吧,或者更严谨一点说是我的实际能力没有达到我的考试成绩反映出来的能力。
大作业和论文阅读都有一个可选的展示环节,我都参加了,不过看别人做的东西,还是感觉自己太浅薄,重复了那么多遍自己熟悉的东西,最终也不能说有什么收获。
去年选了另一个老师的系统结构,然后退了,这在去年的总结里已经说过了。不知道他今年的教学效果有没有变好一些。
今年换了个老师,感觉他讲课诚恳多了,至少不会不时蹦出来一句”我当年”怎么样的。不过这课本身死记硬背的东西太多,而且至少我是实在看不出来有什么应用,老师讲得好一些应该也没法让学生很投入的听。他说这像高中的化学,其实我看还是像生物更多一点。
小作业只有四次,好像都是前半学期布置的,后面就没有布置过了。大作业一个是Cache模拟器,一个是Tomasulo模拟器。听说另一个老师的第二个大作业改成了Meltdown和Spectre漏洞的利用,感觉好像挺有意思的,没想到他居然能做这种创新,去年上了他的课我还以为这两个模拟器的作业准备用一辈子呢。去年我退课的时候做了第一个大作业,当时感觉做的挺困难的,今年就做的轻松多了,有可能是我的能力提升了,也有可能是网上的资源变多了,也有可能是作业本身描述和指导的更清楚了。
两个大作业代码都在https://github.com/MashPlant/undergraduate_projects/tree/master/计算机系统结构。虽然我上交的Tomasulo的代码中有一个会影响结果的bug(github上的已经修复了),但是平时作业得分还是满分。考完了之后有空我还造了一个在线版的:https://mashplant.online/tomasulo,去年我感觉造GUI是非常困难的工作,当然其实现在本应也是很困难,因为我并不会前端,前端是抄的,不过有了这些资源的情况下做起来好像也不是很难了。
考试是开卷的,估计是想着如果要求闭卷,可能最后就成了一部分人闭卷一部分人开卷。虽然我自己一直认为自己记忆力还行,之前的考试也基本证明了,但是记这种东西的过程还是很痛苦的,能开卷当然最好。虽然是开卷,但考前我还是把ppt仔细看了很多遍,毕竟那么多复习的时间,实在也是没有什么别的事可以做了。
考试的时候,大多数题目都是很熟悉的,不看ppt也能直接写,少部分考到了一些很奇怪的知识点,比如TPU的脉动执行,这个点我考前看到最后一章的ppt,看着这么模糊的图,想着它是绝对不可能考的,就没有细看,再说我也是认真听了课的,这应该是真的没有讲。我就在考场上对着ppt现场拟合脉动执行的算法,感觉像是我在机器学习一样。最后还是看出来了,只是不知道是不是对的。
这个是软件学院的课,我对这个方向感兴趣,所以选了。
这门课前半段讲一些基础的知识,命题逻辑,一阶逻辑,一阶理论什么的,后半段用一个示例语言讲程序语义。前半段感觉还行,越听到后面感觉越虚,感觉都是一些无法应用的算法,至少只用课上讲的内容肯定是做不出什么东西,虽然这肯定不只是学校对我们的要求。而且很可惜的是今年因为形势特殊,为了给我们减轻负担所以取消了大作业,大作业的主题应该是写一些自动分析的工具之类的,本来我是很感兴趣,很想写一下的。
老师讲课相当仔细,但是也有点太仔细了,可能会花很多时间去解释一个我觉得很显然的例子,再加上是网课,走神是很正常的。平时书面作业大概三周左右一次,编程作业只有两次,PA1是写一个SAT Solver,PA2是写一个经Dafny验证的二叉堆 + 堆排序。不过我不明白为什么PA2单独算一次编程作业,因为书面作业中也有一次要我们写一个Dafny程序。好像我的PA2扣了几分?评分是6分,我也不知道满分多少,估计是10分吧。是学期结束后才出的分,出分时也没通知,也没有给argue机会。这个东西难道不应该是能够跑过就是满分吗?不然自动化验证的意义何在呢?
更新:我发邮件问了助教6分的原因,助教说在他用的Dafny版本下出现了四个error,一个error扣1分,所以就是6分。他的版本是2.3.0,我的版本是1.9.7,这就离谱了,居然会有旧版本能够证明,而新版本不能证明的情况,这要么是新版本证明能力下降了,要么是旧版本有bug。这个评分标准也离谱,四个error可能只是少了一两句话的结果,这种自动化验证的程序出现这种结果是很正常的,居然真就总评扣四分。
助教说习题课里说了要尽量用新版本的Dafny,两个问题,第一是习题课不是必上的,我很顺利地做完了实验为什么要上?第二是什么叫尽量新版本?按照网上的教程,这就是我安装的最新版本,我没有渠道可以得知还存在更新的版本。再说了,评测用的就是最新的版本吗,那要是我用的版本比他还新,然后也是我这里通过他那里error怎么办?毕竟新版本证明能力上升了才是正常的。很明显这个作业就不应该这么评测,肯定是要写报告,讲自己的思路,贴运行结果,这样才合理。如果一定要根据error数评分的话,一定要在实验指导里指明评测用的版本,而不是在习题课里来一句”尽量新”。
最后结果是A-,我是相当不满意了,不过也还是4.0,而且批阅时间和出成绩的时间都是很久以前了,我就不争了。
考试也是开卷的,和系统结构同一天考。我从第十六周一开始复习到第十七周周三考试,就一直看这两门课,感觉复习的要吐了。这个考试比系统结构的简单一些,可以说全都是不用看ppt就能做。比较不舒服的一点是考前我把修正带用完了,修正带我已经用了这么多年,估计只要我还写一天字,就会用一天修正带。考试的时候一写错心里就很烦,如果有修正带的话肯定可以直接改掉,但是没有,我就在纠结我是全部划掉,还是尝试改一下,纠结的时候思路就断了。这让我想到了高三的九月调考,一样是很简单的卷子,考场上不在状态,那一次结果也是不怎么样。我觉得这考试真没什么题可以错了,我猜可能就是PA2扣了4分,其他作业加起来扣1分多一点,考试最多错了一点点吧,这样就A-了。
这个学期开学的时候我在看Software Foundation,讲Coq的。前面也说了,我看不进去,很多地方缺少解释,虽然我主要看的是中文版,但是应该也不是翻译的问题,毕竟我也可以看英文版的对应位置,我觉得就是前后逻辑接不上。这东西看起来是真的很神奇,也确实有它的应用价值,但我还是感觉离我平时的编程太遥远了。一学期的课上完了,感觉也没有什么进步,这本书也还是看不进去。
选的时候只是为了填一下专业限选课的学分,上完之后就感觉真不该选,成绩出来之后更加后悔。我对沾点机器学习的东西基本就一点兴趣都没有。老师ppt念的确实没什么问题,但是我实在是听不进去。一般下午第二节课的时间我不是很困的,但是听这个课好几次都睡着了。
小作业就是平时讲的一些内容,要你手动跑一下数据挖掘的算法,大约一个章节一次,感觉覆盖的知识点并不是很全面。我感觉小作业似乎有点批的过于严格了,一道要求写过程的计算大题的两个答案我不小心写反了,然后就把分全扣了。还有就是第一次作业的一道选择题就在总评中占两分,我错了,但是这个真的就只能抱怨,没有什么可argue的了,毕竟评分标准是清晰的,对所有人都是一样的,只能怪自己运气不好。
大作业是论文作者消歧,就是给出很多论文的信息,把多篇作者名字相同但实际作者不同的论文聚类成不同作者的论文,三个人组队做的。这个是一个竞赛,在https://www.biendata.xyz/competition/chaindream_nd_task1,这个竞赛去年也有,所以我们就尝试改了一下公开的去年前几名的代码。有一个第五名的,我魔改了半天,越改F1越低。还有一个第二名的,一个队友先搭好,效果还行,在排行榜前面待了一段时间,但是后来就被别人挤到很后面去了,我们怎么改F1也没有超过最初的结果。Python实在是跑的太慢了,而且每天也只有三次提交机会,所以实在是没什么动力去卷了。
一开始的成绩是85分,我感觉太低了,去argue了一下。一个是上面我说的作业那个问题,助教给加了一分,还有一个是我说大作业主要是由我和另一个组员完成的,还有一个人在划水,我觉得如果分数都一样的话并不公平,所以申请加分,助教也给加了一分。除此之外就没什么可加的了,所以最后87分,好像绩点也没什么变化。喜提大三一年唯一一个算学分且不是4.0的课。
非常幸运地又选到手球了,这已经是我第三次选手球了。手球的网课也不出我的意料,比其他的体育网课要水得多。前大半个学期都是老师在微信群里喊一句上课,然后大家答到,接着讲几句手球相关的知识,一节课就这样结束了。到了后面改用腾讯会议,基本上也就是答个到,简单说十分钟就下课了,即使当时不在,过一会在群里解释一下也没有问题。考试就是几个手球动作,老师用视频发给我们,要我们自己做了录成视频发给他。
在一些群里看到有的低年级的体育老师一直都是强制要求视频签到,还有人因为自己错过了一次签到要扣分而想退课,不知道是只有我们这么水,还是大三的普遍会水一些。
]]>最近学了一下Rust的WASM工具链,感觉用这一套来写这种给大家分享的算法模拟器是相当合适的,因为可以用Rust写,写的很舒服,而且可以在浏览器上访问,分享起来也很方便。
这里特别感谢jiegec,他的代码和指导给了我很大的帮助。同时也特别感谢n+e,因为我的页面是抄他的,其实我根本不会写前端。
代码在这:https://github.com/MashPlant/tomasulo/。其实这个仓库是我寒假的时候建的,当时我以为这学期的系统结构的要求没有变,还是做一个带GUI的Tomasulo模拟器。去年这就是我退课的直接原因(当然,不是根本原因),当时是有点想用Rust写,但是不敢,把一个大作业交给一个我还不是很熟悉的语言,这听起来风险就很大。
到寒假我的Rust水平已经有了一定的提升,我做了一个带TUI(Terminal User Interface)的Tomasulo模拟器,感觉还是挺有特色的,用的是Rust的cursive那一套工具链。用起来其实不算顺手,很多常见的需求还得自己造轮子,不知道现在有没有好一些。代码在https://github.com/MashPlant/tomasulo/tree/cursive里。寒假里我粗略的写完之后没有做任何的测试(因为也没有测试数据),这里面一定存在一些逻辑错误,到开学又听说今年不用做GUI了,而且要求要用C/C++写,所以就是说我白做了,这些错误也懒得改了。
学期当中用C++做的时候从Rust这边翻译了很多代码过去,也经过了很多的修改,现在又要写Rust的版本,所以我又把它翻译回来。顺便说一句,我在翻译的时候发现我提交的C++代码有一处明显的逻辑错误,在给保留站分配功能部件的地方,也确实会导致两个测例输出的log不一样,但是我的这项作业还是满分。所以说其实没有太大必要担心正确性,助教不是那么死板的,当然我估计这种话给正在做的人说,他肯定是不愿意相信的。
前端是复制n+e的,这个我是真学不会,也没有什么地方系统的讲这个,其实我也没有什么学习热情。有一说一,n+e的后端的Python写的确实是不怎么样,当然他自己肯定写起来很快,我也相信他维护起来没有困难,只是苦了那些要看他的代码的学弟学妹了。
n+e:
点击多步运行,输入100000(十万),来个死循环,大概1.8s这样能执行完。相比 https://tomasulo.harrychen.xyz/ ,同样程序他运行10000(一万)次就得花2-3s,我的效率是他的十倍左右。
(谁说python代码慢?)
附:死循环代码如下:
LD F1 0x0
MUL F3 F1 F1
JUMP 0x0 F3 0xFFFFFFFE
真的吗?我不信,TS/JS那一套的性能应该是相当不错的,毕竟都是钱砸出来的,怎么说也不可能比不过Python,我估计可能是实际做的事情不一样,比如Harry Chen的网络访问多一些,或者cycle之间有等待之类的。
我写的这个没有任何网络访问,都是在使用者的浏览器里算的,所以具体速度跟浏览器关系很大。这段代码输入前要稍作修改,因为这两年的语法并不一样。多步执行的输入框里输个10000000,console里执行:
1 | beg = new Date(); window.multi_step(); end = new Date(); end - beg |
结果在500ms左右,这个算是相当可以的速度了,我写的C++也只是这个数量级的,截一下我的报告:
实测了一下,跑这段代码10000000个cycle,我写的C++也要约300ms,WASM这一套的性能真的是相当不错了。
]]>这是这学期唯一一个编程课,我当时还算是很享受编程的(到现在时间长了,也许编程仍然能给我带来快乐,但是疲劳肯定是越来越多了),所以算是上的很认真。
我选的是yhl的课,选之前听说很有特色,比如是英文的ppt+大部分英文的讲课之类的。其实到了他自己讲不清楚的时候他自然会用中文的,感觉就很滑稽,不过对我们听课也没什么影响。
平时的作业基本就是一些编程题目,有一个互评的环节,把自己的作业发给别人,别人填一张评价表,然后自己根据评价来改。开始前几次还是有点意思的,不过次数多了就感觉是走过场,有点无聊了。
这门课有一个课后报告的环节,就是一个学期中轮流每个人准备一次报告在课上讲,主题没有什么限制,就是分享一下自己的了解的知识。我那一场时间很早,当时好像还在学构造函数什么的,但是我准备的主题是模板元编程,这就很突兀了。当时我花了一个周末准备,写了一点代码,比如一个编译期的Treap之类的。虽然在台上讲的时候装的好像一副举重若轻的样子,像是在炫耀自己的成果一样,但是其实我心里也知道这完全就是屠龙之技,真没什么用的,看别人讲比如编译工具链之类的,我感觉比我的还更有意义一些。
有两个大作业,分别是个人的和组队的。个人的我选了写一个带语音输入的文本编辑器,用的是讯飞的语音输入API,用Qt做GUI。在做的时候感觉在Windows上写代码实在是太难受了,几乎完全没有可能成功编译网上的代码,VS这一套工具链实在是不知道怎么用,而且本来很多网上的代码就是只能在Linux上跑的。再加上这个Surface只有128G的硬盘,Windows下空间快不够用了,等等许多原因,我换成Linux了。但是直到现在,我对Linux的使用水平也只限于把它当做一个写代码更方便的Windows。
组队的大作业,因为不想组队,我就申请自己做了。我很不喜欢组队,到现在也是这样的,能不组就尽量不组。大部分情况下,只有一门课强制要求,或者我对这门课不感兴趣,我才会主动去组队。我选的是实现姚期智的一篇关于欧式空间中的MST的计算的论文,只要求实现二维的情形。在经过了很多优化之后,这个算法的速度确实还可以,其他常见的算MST的各种方法都比不过,但是比用Delaunay三角剖分做的还是慢太多了。当然这个我并不会写,我是抄n+e的。最后我的代码里全是模板套模板,一个虚函数也没有,现在我的代码风格还是这样的,几乎从来不会主动用virtual
或者Rust的dyn trait
。
考试是机试做题,听说别的OOP班都是笔试,这可太幸福了。题有点多,有很少的一点点算法(最长上升子序列级别的),主要还是考察OOP的知识。我是顺利做完了,不过还是有不少人没做完,或者因为不知道概念和语法而做不出来一道题。
我还是选了晏平的微积分,上学期就觉得听的很舒服。
晏平的作业是真的多,反正是比我的舍友上的微积分作业多多了,但是我当时一直把这当做很好的复习巩固的机会,要是让现在的我去选,那肯定是不会选晏平了,当然也不可能达到这样的分数了。不仅作业多,答案也有很多错漏,当时多亏了有fzt做的答案勘误。我觉得做完之后对一遍答案,然后自己尝试改一下错题,这个收获非常大。而且如果实在不会的话确实是需要答案帮助一下的,自己已经有了充分的思考,再继续耗下去确实是浪费时间了,看答案也不会削弱练习的效果。
这门课上习题课的助教是所有数学课的助教里我现在还能想起来的唯一一个了,因为他非常有特色吧,感觉像是国防生之类的,很高很瘦,穿着一双胶鞋。我们每次都在习题课上交作业和拿作业,这门课每次发下来批改后的作业之后允许我们下周改正再交过来,这样不算扣分,我很喜欢这个政策。
我记得有一次习题课前我生病了,下午为了缓解难受准备睡一会,但是醒来习题课已经开始了,舍友也没有叫我。我在床上躺着想了一会,还是要去,因为习题课上要交作业,我怕迟交会扣分。到了教室交完作业之后只能趴在桌子上,感觉教室的光线特别刺眼,眼睛完全不敢睁开,要用手臂重重地压住眼睛才会舒服一点。现在回想起来感觉和我之后顶着病和寒风去上大物和考软工非常像,不知道这样的坚持是有什么意义,明明不去才是最优的选择。
尽管作业很多,但是这学期课外用来学微积分的时间还是比大一上少多了,因为几乎只有做作业的时间了。那本数学分析我偶尔会看,考前看的还是很多,感觉这个复习效果还是相当好的。
期中99,期末100。期中其实算比较简单,因为班上有好几个100,其实我也不知道到底是哪里扣了一分。期末就相当难了,我自己做的时候感觉计算量是非常大的,让我找到了高中考试的感觉了,确切地说是第一次八校联考的最后一道填空题,算一个几何体的表面积还是体积来着,一遍算不对算两遍,两遍算不对算三遍,算到算对为止。证明题就没什么技术含量了,多看看数学分析会发现微积分的证明题一眼就看透了。我还记得晏平在监考的时候逛到我这边来,看到我做完了,问了一句你做完了?我说是的,但是我还在检查。
选的是姚国武的线代。线代2说实话真的很简单,就是一些算法,没什么证明技巧的要求,而且老师也不会故意刁难出一些很难算的数字,最后都是那种你算出来就知道自己做对了的题目。
不过姚国武还是有他自己的风格的。虽然最后考试确实很简单,但是作业里还是可以尽量往难了出。之前也提到了,实在不会做我就上网搜了,基本上都是能够搜到的,因为一般都是一些高等代数的习题里面的。
期中期末都是满分,不过没有得A+,估计也是因为满分的人不少吧。
选的是cy的课,挺后悔没选另一个老师,因为后来听说另一个老师口碑还可以。
他上课主要的时间是在纯念ppt,很催眠的那种念。还有一小部分时间用来鼓励我们要去创新,听得我相当恶心,假如我们这一群人里真的有人能做出什么创新(当然也确实有,可惜不会是我),那也绝对不会因为是听了他这种质量的课,或者是受到了他的启发。
其实上课部分倒还好,后来就会知道这大概就是计算机系老师的及格水平了,他的其他操作才是更恶心的。也许相当一部分人会喜欢吧,我不知道,但是这是我自己的总结,我想说什么就说什么,我就是觉得恶心。例如要学生做什么技术报告之类的,通过这些报告我什么也没有学到,但是我感受到了很大的压力,好像别人已经融会贯通,站在高处俯瞰了,我还纠缠在这些图论的题目中,因为图论对我来说真的很难。还有期末考试让学生出题,这要是放在高年级的专业课上,人都能给他喷没了,这就是直接鼓励恶性竞争。某些人把自己在别的课上做的题目出出来,自己当然是会做,和他水平差不多的人多半也会,我就想破头也不可能做出来了。哦,那怎么不让我来出微积分的题目啊,我就不信这群计算机系的人还能人均精通数学分析了。
结合其他课一看,感觉我这个人心理有点问题,我卷可以,别人卷就不行,我就感觉很难受,压力很大。这人怎么这么贱啊。
还有这课的时间也不好,是在上完两节微积分后面连上三节。本来人就很累了,上到后面还很饿,根本听不进去。
A-是我的数学课的最低分(如果离散数学算数学课的话),仅有一个数值分析与之并列(如果数值分析也算数学课的话)。
这节课应该是经常被推荐给大一下的学生提前选的,因为没有什么前置要求,对图论其实没有什么要求,总不能只要有张图就说前置要求图论吧。
课的主要内容就是正则<->DFA<->NFA,CFG<->PDA,图灵机这一套,相关的算法比较多,大部分还都是很合理,不难记的,不过越到后面关于可判定性的结论越多,这就只能死记硬背了。我记得前几周上CFG的时候有几个CFG的构造的题目是真的做不出来,感觉就是纯考智力,这我就不擅长了,后面考记忆的东西慢慢变多了,我就掌握的更好了。
很多人可能觉得这些算法,没什么用,包括编译原理的很多算法,但是我写lalr1和re2dfa的时候其实都用到了,比如正则转NFA,NFA转DFA,DFA最小化,还有龙书上介绍的一个快速生成LALR(1)转移表的算法(编译课上没讲)。事实证明这些算法的效率都是相当高的,尽管看起来有很多迭代之类的东西。
期中考试是开卷的,但是其实题量很大,而且需要记的东西不多,开卷没有什么意义。期末考试是闭卷的,反而它更应该开卷。期末我记得考了一个构造PDA,考场上自以为做出来了,其实做的是错的,出来听别人一说,答案是真的精妙,我绝对想不到这么做,错的不冤。期末有一个附加题,证明一个图灵机相关的理论什么的,我也是自以为证出来了,不过看结果应该是没有。
我期中期末都是接近90的样子,得个A-应该算是调分力度非常弱了吧,可能他们的算法对于分数比较高的人就是调的很少。
我们的老师周树云好像是那一年新来的,感觉讲课软绵绵的,语调也没什么变化,听不出来强调的地方。听说她后来还上了朗读者,应该是科研成果比较突出吧,不过上课水平确实也就一般。
期中考试感觉有点难度,但是做的还可以。她还给我们班排了个名,我是排第五,当时自我感觉很不错。
后来学到相对论的时候感觉听不太懂,上了一次习题课之后感觉好了一些。
期末主要是热学,公式很多但是不是很难,我留了很多时间来复习,我自己也感觉复习的有点随意,但是我相信我还是都记牢了的,没想到翻车了。考试是下午两点半,我当时准备上床休息一会,但是闹钟没有响,或者是我没听到。我醒来时大概两点二十多了,同样是这门考试的舍友已经走了,没有叫我。我当时就骑车飞奔到考场,幸运的是到的时候他们还在发卷子。做的时候就感觉心里很浮躁,但是题目看着都很简单,比期中简单多了,我也不知道我能错在哪里,最后结果就是只有B+。
我到底是为什么想不开要选两门英语课啊。这两门课是这个学期主要的压力来源。
文献综述其实还好,老师老一些,要求也都比较佛系,但是因为我没有太多时间可以花在它上面,所以小测和考试基本都是没有准备的。如果把它放到别的学期,成绩应该会好一些。
研究论文是补选上的,上第一次课的时候退课就已经截止了,否则我肯定会把它退了。这门课上课是讲课本上的论文,每节课小测考单词。作业有两个阅读笔记,就是总结课本上的论文。不过主要任务还是一个组队项目,自选主题写一篇论文。也不是简单的写,还有很多额外的环节。论文分成两个部分,一部分是摘要和文献综述那些,一部分是方法和结果那些,四个人的组要再分成两组,每组中各自写第一部分,相互评改,然后大组里交流修改一下,第二部分也是一样。实际情况是很多时候我都不知道其他人在干什么,他们也没有问我在干什么。反正就是非常复杂混乱,我都没办法完全回忆起来具体做了什么,我能确定的是最后每个人应该要有独立属于自己的一篇论文,小组只是调查和讨论的单位。写完后每个人还要自己找别的组的一个人相互评改,最后还有一个pre。反正就是每个环节都能多出一些任务来。
很多个晚上我都用来写这个论文,绞尽脑汁要想一个不同于之前的表述,增加语言的多样性。我是真的不理解为什么一个研究论文要有语言的多样性,难道不是一直用同一个词读起来更舒服吗?还有,我后面上这个课经常会问自己,我没上的第一节课到底错过了多少东西,为什么会有那么多我没听说过的名词?还是说她假定我们都会,或者假定我们都能找到解释?
我没有说这门课不好,但是确实很不适合我这种不愿意在英语课上付出太多时间的人。
选了李蕉的课,听说这个老师很受欢迎,看人数确实如此,而且上到后面人也确实没怎么变少,这还是很不容易的。当然这种课我从来不会认真听,所以她讲的好不好我也不知道。
整个学期基本上只有一个项目要做,就是组队读一本书,有各种围绕它的讨论,展示,读书笔记等等。我们组选的是《历史三调》,这个其实我早就全忘了,书名还是在网络学堂里找出来的。每次讨论,尽管我为了不那么尴尬肯定还是会提前做一点准备,不过结果往往还是很尴尬,对同一个问题别人可以说的相当充实,结合书中的内容具体分析,我就只能随便讲一些假大空的东西。最后的展示主要是队长在carry,其他队员也有贡献,反正我是完全没有参与。
这样居然都能得个A-,进个好队也太重要了吧。
这好像是一个文素,当时应该也是因为文素而选它的。这门课就是教炼丹,很基础的那种,选课的时候我还不知道这不是我感兴趣的方向,不过上着上着我就确定了。有几次作业,都是用TensorFlow训练神经网络。我当时很期待自己能学会RNN到底是什么原理,可惜到现在我也还是不懂,不过现在已经不期待了。
大作业是组队的,这应该算既强制要求,我也不感兴趣的课。和两个舍友组队了,结果代码还是几乎都是我写的,每次都是这样我怎么可能喜欢组队啊。我们做的是一个下棋的AI的框架,目标是比较简单的那几种棋都能下,因为神经网络和MCTS本来也对棋的规则不感兴趣,所以只要不是太难的应该都能下好。初始的代码是从Github上找的,我魔改了很多,最后效果算一般吧,下个四子棋能和我这种只知道规则的人五五开(在我心中这已经可以算一般了,不是很差了)。
应该是文核吧,如果我没记错的话。课还算有点意思,就是每次请一个老师来讲一段科学史,有签到。工作量应该算相当小,没有展示,只要最后写一个小论文,和回答一些题目就可以了,这工作量小的我都怀疑它不是文核。
老师是杜超,跟上学期一样,这个应该是巧合,不是我选的。这学期体育是教清华拳,我记忆中不算难。这学期要测引体向上,这其实才是体育的主题。很遗憾,到最后我也一个都没有做起来。
一开始我肯定是一个都做不起来,我有一点想练的欲望,但是因为我真的没什么力气,在那里练习看起来实在是很滑稽,反正我自己是这么想的。这种心理上的障碍本来都是自己给自己的,如果我不在乎的话,哪怕别人真的觉得我很滑稽又怎么样呢,还不是一样可以练。但是很不幸,我确实很在乎,这个也没法改,天生在性格里面的。还有一个原因是我完全感觉不到自己有任何的进步,练习跑步我可以坚持,因为确实在变快,体重也有在降低,这个不给我一点反馈,我是真的没有动力坚持下去。
测试之前我是真的怕她把我给挂了,不过看起来杜超人还是很好的,没有太为难我。
这学期选的是杨晓京的复变,之前已经说过了,我觉得姚国武的复变更适合我,当然这个时候的我肯定不知道。
杨晓京名气很大,给分非常友好,题目都是原题,而且还可以做一些题目来代替考试,怎么看都应该是他更好一些。但是就是有一个问题,他讲课讲的不行,从我的角度来看,他没有讲清楚。作业选的都是一些书上的题目,感觉练习量也不够,那些他没讲清楚的东西做完作业也还是不清楚。
中间尝试了一下做他布置的题目,有一个关于zeta函数的,这个我记得我高中有段时间就对这很感兴趣,虽然理论基础完全没有,但是Fourier级数的公式还是记得住,自己算了几个zeta(2),zeta(4)什么的,当时感觉自己好厉害。他布置的题目具体是什么已经不记得了,只记得也可以用Fourier级数来辅助证明,但是还有很多其他问题,当时我自己找了很多资料才证出来。后来有天上课他说:我看有人(不是我)交上来的证明是用Fourier级数,你们不应该会Fourier级数,所以这个证明肯定不是你自己写的,这题的证明里不允许用Fourier级数。我直接???
期中的时候感觉课太多,没办法都学好,再加上这些烦心事,就退了。
全忘记了,没什么可说的。
]]>这是我这个学期投入最多的课。我对编译原理的兴趣应该来源于上学期选的自动机,当时学到CFG我就觉得,以这种东西为基础构建起来的parser一定是非常有意思的,上个学期尝试了好多次想自己手写编译器,基本上都没有任何成果,纯粹浪费时间,所以准备下个学期一定要选编译原理。
上起课来感觉就那样,感觉知识很多,勉强能记住吧。Decaf实验在当时还是很简单的,实现var
,foreach
之类的,比现在的可是容易太多了。最难的应该还是PA1-B,LL语法确实写起来很不自然。
当时有一个学术新星项目,我就选了chyyuu作为导师。当时我并不知道这个项目有啥用,其实到现在我也不知道。他很推崇Rust,所以我就去学了,其实在此之前我也听说过Rust,所以还是很感兴趣的。
为了更好的学习Rust我就准备用它写一个Decaf,也是因为实在不喜欢现在的Java版本。其实当时我的Rust水平真的是相当一般,很多地方不知道怎么写,想要快点写出东西来,管不了那么多就直接上unsafe
。而且当时Rust的工具链其实也不太行,我为了找一个parser generator就花了很长时间。后面框架搭起来了之后就还好了,基本可以随便写了,不过代码还是很丑。当时纯粹是出于兴趣写的这个,后来听说还可以申请把它当做拓展试验,感觉很幸运。
后来还在这个编译器上做了一些拓展,学了一下JVM Bytecode,LLVM,做了到这两个平台的codegen。我记得我很多次在答疑坊写这个编译器,是真的能感觉到很开心。不过最后答辩的时候感觉自己做的东西还是没有什么实质上的水平,就是堆砌代码而已,学术性上可能还不如选课的那些高中生。这个想法一直伴随着我到现在,我到现在也觉得我只是把自己熟悉的东西重复了太多遍,并没有什么真正的有水平的东西。
在考试周开始之前我生病了,时间刚好贯穿了所有考试,第一门软工课上考试的时候最严重,后来考试周里过了一周多也没完全好,感觉就是考完编译原理之后才终于好了:
最后一门编译原理考完的时候,我也差不多全好了,我骑车离开五教,嘴里长啸了一声,似乎是对这些天的这些压抑的排解。
(到底有多压抑,可以去看软件工程那一段)
也许有不少人对我在数据结构OJ上的排名还有一点印象吧。我那时真是太认真了,为了把一个红黑树,B树,排序,或者IO写快一点,愿意花上一天的时间,写出好几个不同版本的代码比较速度。现在想来做这么多有什么用呢,就像我为了微积分做了那么多题目,考试也考的那么好,过了一年还不是忘的一干二净。多做微积分题目至少还对分数有好处,但是爬到排行榜的前面又不会给我加分,可能纯粹就是为了给别人炫耀,满足一下我的虚荣心吧。多么无聊又卑微的人才会这样啊!
我还记得期末考试最后的那个好像是跟斐波那契数列有关系的证明题,我在考场上想了半天也没有证出来。出考场后还意识到自己前面一道关于B树的简答题写错了,以及判断题有很多都不确定,所以当时其实是很有点担心我的成绩的——要是我花了那么大力气冲榜,最后一看成绩不怎么样,那该多滑稽啊。幸运的是最后还是得了个A,这个算是比较满意的结果了,但是你要问我有什么收获吗?我觉得没有,我只是一直在重复同样的技巧,一直在做没有什么意义的工作,稍微有意义一点的,比如那个证明题,我就不会。
我不是搞OI的,到现在那些他们随手就能写的算法我还是不会。这不仅仅是熟练度的问题,他们那样的思维我到现在也没学到点皮毛。不会真有人因为数据结构的排行榜而觉得我很厉害吧,不会吧?
选课的时候觉得这个大三上的课应该没有什么前置要求,所以就选了。上第一节课的时候感觉有点慌,似乎很多常见的概念我都不懂,比如CI什么的。但是我觉得如果拖到大三上我应该也不会有什么进步,而且其它课的工作量那么大,所以最好不要退。
大作业分组的时候我看有一个Android上的会议笔记项目,看起来不是很难,反正我也会写Android程序,所以就选这个吧,拉了几个同样大二的人一起做。应该是十一的时候我开始做,找了一个别的项目移植了很多代码过来,基本搭出来一个完整的框架来之后才第一次push。后来有很多组队项目我也是这样的,我总是觉得这种东西交给我的组员我不能放心,所以希望都自己做完。
后续的工作分配其实还算均匀,除了gwy因为Java水平不行划了很多水之外其他人都算做了很多工作。现在已经记不清当时开发的过程了,只有为数不多的一些片段给我留下了深刻的印象,比如每次组会接物理实验接电子学实验的下午,比如最后为了代码覆盖率能够上去到处写测试,比如为了他们的系统开心每次隔半个小时commit一次。
这门课的上课内容相当无聊,大概是讲了一些设计和测试上的原则吧,真的是一点也听不进去,最后又是闭卷考试,这就很难受。每次上课都是一群大二的坐在那里写代码,而大三的根本不会来。
最后一周课上考试,比我想象的还要更“难受”一些,因为我生病了,这里直接贴我日记里的内容:
第一门期末考试是软工,那可能是最严重的几天,早上四五点钟醒来,还拉肚子了,坐在桌前,没几分钟就要灌一口热水让喉咙的疼痛不那么尖锐,让干枯的嘴唇稍微湿润一点。我无法想象我是怎么从寝室骑车去上大物的了,其实我自己也不是很理解为什么一定要去上课,大概是我害怕这最后一节课讲的知识考试会考吧。到了教室时已经迟到了十多分钟,我还是顶着目光直接走到了最前排,我平时坐的地方。记笔记的手都是飘着的,盲目地在纸上涂着,重复着ppt上的文字。我带了一杯热水来喝,喝到一半就慢慢冷却,再喝下去每一口都只能带来痛苦了。就这样上完了两节课,去了考软工的教室。考试开始,手指无力但坚定地写着,写的那些话几乎没有经过什么思考。写到最后一题,第一次在考试中感到了这种无力,我知道自己应该些什么,但是居然感觉没有力气把这么多字写下来。整场考试不敢咽口水,否则必然又伴随着嗓子的剧痛。就这样过去了两小时,别人如释重负一样去收拾书包的时候,我几乎都没办法站起来了,在原地坐了几分钟,才勉强能扶着桌子走到自己的书包前,装好文具,背上,一个人骑车回了寝室。后来结果怎么样?3.3,我没有什么可以评价的,大概这是我应得的吧。
前半段讲的电路基本上还是可以理解的,到了模电之后确实就像雷老师说的那样“阶跃”了。当时是感觉很难受的,课上怎么样也听不懂,来上课的人也渐渐变少,我不想去但是又不敢不去。当时真的是很怨恨这个老师,他根本不关心我们能不能听懂,反正就自顾自地讲,而且除了上课他其他方面的风评也是相当差的。
现在来看我觉得他好像也没什么太大问题,他确实教学态度相当消极,但是计算机系很多别的老师不也差不多吗?只是他讲的内容尤其晦涩,尤其没有用,所以我们会讨厌他吧。
期中考试几乎全是原题,倒是有一道很难的填空题不是,应该是算一个六边形拼接的无限平面上两点间的电阻吧。我当时是完全不会做,尝试了半天也没有结果,交卷前随便猜了一个系数,应该是2吧,居然真的对了。期中拿到100分。
期末复习的时候看着ppt,感觉都是天书,根本看不进去,感觉像是在训练神经网络一样,虽然看不懂,反正就往里灌,尽量记下来。期末允许带一张A4纸,我就抄了很多知识点,也抄了一些原题。上考场一看,小题几乎一道原题都没有,做的有点崩溃了,判断题我根据我的知识觉得基本都是对的,不是因为没法写出错误理由才填对。大题倒是有原题,就往上抄了。后来出分数一看,60,我当时几乎以为是改错了,听说判断题的答案几乎都是错之后就知道肯定不是改错了。后来到处一看,好像60分算相当高的分数了,那我就心满意足了,不再问了。
这个成绩是在我在武汉到处逛的时候出来的,根据我的日记应该是2019.2.3。我当时还是蛮庆幸的,就这么一点东西也没学会,居然也能拿个A-。
上学期选了杨晓京的复变,然后退了,这学期再选了姚国武的,这样的人应该不多吧。
我上姚国武的课感觉就很舒服,我就喜欢这样的数学课,按部就班地讲,不要突然跳出来一些奇怪的概念,该证明的地方就要好好证明。我还能大概记起来,应该是柯西公式吧(记错了也是有可能的),姚国武按照书上的方法证明完后补充了一句这个证明是不完全的,还有一个假定没有解决,要真正证明超出了课程要求,杨晓京的课上绝对没有这个过程。
作业中还是有一些难题的,不过也不能算很难,大多数我都能在半小时内做出来,实在做不出来的我就跟上学期上姚国武的线代一样,上网搜索,一般也都能找到解答。考试题目没有作业难,最后一题好像是证明一个级数的收敛性吧,我稍微卡了一下,但是其实是很简单的题目,冷静下来稍微想一下就有了。
我上了姚国武的线代2和复变,感觉听的都非常舒服。后来有一次我还在操作系统的课上跟他打了个招呼,因为他上节课在这个教室上。
选了梁恒的概统,感觉他上课有点太浮夸了,虽然确实有很多有意思的地方,不过听多了也会有点不自在。不过其实瑕不掩瑜,我感觉他的概统上的还是很不错的。
上课的时间是下午第一节,其实当时我还没有意识到这个问题:我渐渐地需要睡午觉了。从初中高中完全没有一点睡意,希望拿整个中午来做作业,下午还是充满精神,经过了大一之后,不午睡来到一点半这个时间,我已经明显地有些困了。幸好我上数学课一直是态度很认真的,一直在记笔记所以也不会太走神。但是从这学期以后,我几乎不能在这个时间点选课了,到这个时间点人就完全没有精神了,听我们那些计算机系的老师讲课更加催眠。可能这就是人衰老的表现吧,没有办法可以与之抗衡的。
我记得学期中有段时间我感觉很慌,因为当时搞了一个网上的小测,感觉很多东西都不会,至少是没法熟练地答出来。不过后来证明没有什么担心的必要,毕竟没有经过复习,做不好是正常的。其实期末还是有几道难题的,我记忆中有道大题考了一个不是很熟悉的分布的均值,填空题也有点难度,感觉比样题难不少。虽然最后都是顺利解决了,但是考场上还是有点紧张的。
我已经忘记老师叫什么了,去搜了一下才想起来是叫李桂琴。我感觉她上课应该也就是念ppt,但是不知道为什么,总是给我一种非常可靠的感觉,可能是声音比较慈祥吧,虽然她并不算老教师,这个词可能不太合适,反正就是她讲课听起来很舒服。
期中考试和期末考试都非常简单,当然这个简单是基于我充分的复习的基础之上的。我还能隐约回忆起来期末最后一题是跟光栅有关,如果能记得一个ppt上的不是很常用的公式的话就可以直接写,至于不记得公式的人有没有做出来那我就不知道了。
这门课本身没有什么问题,但是它对我的心理其实造成了很大的影响。不怕你们笑话,我高考完了之后心中想着一句话,叫“希望能更好地理解这个世界”,我也不知道我当时是怎么想的,居然在预习物理,看电磁场,转动惯量什么的,现在看来好像完全不能理解,明明至少是大一下才会有物理课。可能我当时心里觉得既然后面有物理课,这么安排肯定是有道理的,所以物理应该还会在我未来的人生中占有一定的地位,而且我高中对物理一直是很感兴趣的。这门物理课结束之后,就正式证明了我这种幼稚的想法是错误的,物理对计算机系人,没有意义;也证明了我高中投入了很多精力的理综,对未来的影响真的只有高考的那一刻,我以前还曾经欺骗自己这些知识至少还会有点用处。
我们这个马原老师龙治铭是今年新来的,相当有意思的一个老师,虽然我上课没怎么认真听,现在也什么都不记得了,但是当时应该还是能感觉到很有意思的。
任务相当轻,就只有一次pre加一次读书笔记。pre基本靠别人带了,我是前一晚才知道讲的内容的,随便准备一下第二天就随便说了,也没有什么不好的后果。读书笔记应该是选一个国家领导人的书,我就重复摘抄+评论,也没有什么自己的观点,大概唯一的优点是写的比较长吧。
最后得了个A-是真的很满意了,我估计就是读书笔记得分比较高。
这是我选的最后一门英语课了,因为上学期作死选了两门,所以上完这节课就够8学分了。
影视欣赏确实比其他英语课水一些,每节课基本就看电影加讨论,然后我就坐在最后面,我不举手的话老师也不会点我,反正我也不期望在英语课上拿到太高的分数。
大概有三次需要上台的地方吧,一次是电影配音,两个人对话,语速很快,我本来是记得很熟的,但是在快到末尾的地方的一个停顿之后就忘记开始了,挺尴尬的。一次是组队讲对电影的感想吧,我就做了个ppt,没有上台。一次是电影表演,相当于配音的基础上加上动作,感觉有点尴尬,不过这次没有忘词,就是不知道我的语音语调别人听起来会不会很难受。
最后结果B+,完全可以接受。
历史体育最高分!写这篇总结的时候是大三下快结束的时候,尚未得知本学期的体育分数,或许有更高的机会,不过本学期体育本来也不算绩点,所以B-已经确定是我上的所有算绩点的体育的最高分了。
手球确实是水课,我就不以小人之心度君子之腹了,也许其他同学可能会喜欢更有挑战性的体育课,反正我就是喜欢水的。老师非常和蔼慈祥,要求很宽松,给分也非常高。
其实就在这个学期刚开始的时候,我第一次,也应该是到目前为止唯一一次,在紫荆操场上跑三千米跑进了14分40秒,也就是及格线,这是我暑假一直在锻炼的结果。但是开学之后锻炼就渐渐减少了,这跟体育课太水并没有任何关系,我自己坚持不了而已,所以速度又慢下去了。
前半段是一些简单的高中水平的电路,后半段做模拟电路,跟电子学基础差不多,只是进度比它慢一些。我感觉还是体会到一点乐趣的。最后分数比较低,我一直觉得我的搭档gwy在拖我的后退。
最后要搭一个电路作为期末考试,大概就是用模拟电路解一个微分方程,虽然按道理是一组一起做,其实电路完全是我搭的,gwy测数据的时候还没测全,不知道有没有影响到分数。当时觉得这个电路好复杂,后来上了数字逻辑实验才知道什么叫复杂。附图一张:
忘记的差不多了,唯一有点印象的可能就是到最后我也不确定有没有调好的分光计了。我能把分光计调好又能证明什么呢?证明我是适合做系统?还是适合做AI?适合出国?还是适合保研?或者说是证明我适合转系物理?
重申一遍,这种课就是纯恶心人的。
全忘记了,没什么可说的。
]]>代码在这:https://github.com/MashPlant/computational_graphics_2019。
本来选课的时候只选了高性能,没有选图形学的,但是寒假(也有可能是在期末复习?记不清了)在网上冲浪的时候偶然看到了smallpt,感觉这东西好有意思,所以准备把图形学也选了。寒假的时候就开始写大作业了,用Rust写的,不过后来证明其实这个时候写的东西对后来真正用到的代码并没有太大贡献。这个时候是用我的破Surface跑的,4G内存 + 2C4T的CPU,我也是佩服自己等结果的耐心。
刚开学的时候配了一台台式机,R7 2700 + GTX 1060,主要就是为了做图形学和人智导。用这个确实快多了,但是也想跑出更大更精细的图,所以还是要花很多时间。
有一次我突发奇想,既然我的程序开始之后场景里有什么实体就不会再变了,那我可以直接生成代码,直接把每个实体的行为写死在代码里,这不是肯定快多了?后面就按照这个方向走了,这样就自由很多了,我还自学了一下CUDA,可以生成用GPU渲染的代码,跑简单的场景GPU是快太多了,但是越复杂就越没有什么优势了,应该还是我写的不行。这样来看其实Rust没啥意义了,用Python来做这个代码生成的工作也是一样的,反正最后跑的是朴实无华的C++。
到这其实这个作业都还没有布置。布置作业的习题课我去一听,好像Ray Tracing得分有点低啊,我是不是得再做一个SPPM?其实后来没有真正做出来,SPPM跑出来的图片有明显的瑕疵。我还尝试在网上找了很多资料,想学一下有没有可以加速的技术,但是我其实一开始就没有理解其中的数学原理,所以网上的资料基本也都看不懂。位数不多的收获是找到了一个KD-Tree的论文,在GPU上这个KD-Tree的速度确实还可以,但是也没法像简单场景一样比CPU有好几个数量级的提升,应该也是我写的不行。
后面一直卷啊卷,用高性能的服务器来跑我的图,十台机器,每台12C24T啊,这可是快多了。最后硬跑了一张带色散的图出来,色散的来源是在折射的界面上对折射光的颜色和折射角做随机,这一随机,收敛的计算量要求不知道高了多少。
最后找助教检查的时候跟他说我这是SPPM跑出来的,我也不知道他心里怀不怀疑,反正从分数来看他是没有表现出什么怀疑。
其实以后我肯定不会做图形学这个方向的,我也不知道我做这么多是为了什么,要分数的话A+已经太高了,白白浪费了这么多的时间,而且也没有学到什么东西。我是不是心理有点问题啊?
本来按照chyyuu的要求,我应该寒假就把ucore实验给做了,但是我寒假学托福去了,而且其实心理也有点排斥,感觉很难,自己一个人做不动。最后到开学了也没有做,去听了一下往年大实验的报告,感觉自己没这个水平,不敢去参加。
开始上课的时候感觉很有些慌,这个时候我没有学过组原,不知道会有多大的影响。这是为数不多的非数学课我记笔记的了,其实记下来的东西也没什么意义,可能就是把ppt上的东西抄一遍。学了一段时间之后感觉渐渐好起来了,这东西还是相当有意思的,而且也都是完全可以理解的。
但是这可能并不是什么好心态,我喜欢这门课,这就意味着我看到别人做出那样的成果的时候,心里会很不舒服,为什么我就是做不出来呢?其实我根本没有开始做的勇气,但是就是嫉妒别人的成果,反正就是一种很病态的心理吧。我感觉我的心态就是从这里开始就渐渐发生变化了,在这门课之前我感觉自己在大学里过的是算是“开心”的,做什么事情都感觉能有收获,所以也愿意去做;但是从这之后,我就经常有这种无力感:他们比我优秀那么多,这种工作我去做的话肯定不会有结果的,而且在少数我真的付出过努力去尝试的事情里面,我也确实没有做出来任何结果,这就越发强化了这个心态。我也会怀疑自己的兴趣,我真的对系统方向感兴趣吗,这样什么东西也做不出来也配称感兴趣吗?
我现在心态稍微好了一点,不过大概是这种概念上的好:
上面说的这些逻辑有些混乱,纯属胡言乱语,大家当个笑话看就好。
这门课有期中和期末两次考试,我感觉都是有点难度的,我做的也都不是很好,都是大概90分的样子,最后能得个A算老师给面子了。
算是数学课,所以我上的还是相当认真的。ywj讲课基本就是在念ppt,不过这对我来说就已经相当足够了,多少老师ppt都念不好呢,尤其是数学课的ppt给计算机系的老师念,这能念好已经算是水平不错了好吧。
有一个实验,代码在这:https://github.com/MashPlant/numerical_analysis。就是挑了课本上的几个实验题,自己再选其中几个做一下。我心血来潮用了Rust写,其实显然并不是很合适,有些地方还是写的很别扭的。不过现在来看,自从学了Rust之后,应该所有不限语言的项目我都是用Rust写的,所以这也算正常。
最后考试的时候感觉题目贼简单,状态贼好,最后得了个A-是我没想到的,这种题目我都能失误的吗?回想起来,唯一一个能失误的应该是一个多项式插值的题,我在往年题中见过类似的,可能考试的时候没有想太仔细就写了。当然也有可能是别的题做错了,但是这个题目是现在我唯一能回忆起来的,因为当时我看到它就觉得特别稳。
也是ywj的课,从这就可以看出来能念好ppt确实是需要水平的了,他就念不好这门课的ppt。上课用的教材和ppt明显都是很老的,这算是水平相当差的一本黑书了吧,其实这也没办法,它在以前可能是质量很高的,但是一直不更新那肯定就落后了,它不更新我们的老师肯定也不想着讲点新内容,就一直用着呗。
有几次平时作业,我把它放在REKCARC-TSC-UHT上了(https://github.com/PKUanonym/REKCARC-TSC-UHT/tree/master/大二下/高性能计算导论),其实真不用像我一样报告写这么多的,我纯粹为了画图能画完整一点,担心助教扣分,但是其实随便做一点图就能够达到助教的要求了,倒是代码可能要求会更高一些。
众所周知C/C++里面的volatile
和Java里面的不是一个意思,使用场景也完全不一样,书上对volatile
的用法完全是错误的。看这个书的年龄,说不定它才是万恶之源,可能很多错误的说法就是从这里来的。当时我心血来潮研究了一下内存序,甚至还用我的手机跑了一下程序,结果写在hw4的报告里了。但是到现在其实我也没有理解内存序,我还是不会用,这东西确实太难了,还是老实点用锁吧,别搞那么多花里胡哨的。
考试好像到现在也没有题目流出来,我记得大概就是选择题+填空题+计算/简答题,只要熟记ppt应该是没有什么难度。我记得有一道计算cache命中率的大题,计算机系的课程是不是课均要求掌握cache命中率计算?
上的很难受的一门课,第一堂课就听的昏昏欲睡,感觉msp讲课好像很”用力”的样子,但是就是没有什么效果,况且我对这个方向也没有兴趣。不过这个课我最后还是全勤了,这在现在来看几乎是不可想象的,不是应该就上过一两次课这样更加合理吗?
全勤归全勤,我可是什么也没听进去,讲到什么决策树,遗传算法的时候,感觉在听天书。记忆犹新的是,应该是在讲谓词逻辑的时候,我真睡着了,从第一节课的一半直接睡到了第二节课的一半,这是我记忆中我第一次真的在课上睡着,我敢说是从小学开始的第一次。以前我一直不理解,课上都能睡着的吗,老师在台上那么大声地讲着呢。现在我明白了,他讲的东西如果我不感兴趣,声音再大也没有什么意义,就像是背景的白噪音一样,反而起到一个安神助眠的作用。
预留了两周左右复习,ppt过了好多遍,感觉还是记不牢,能考的东西太多了,基本又都沾点算法。最后考试感觉还是有难度的,尽力发挥吧,A-算是可以的成绩了,虽然我也感觉自己都做出来了,不知道能错在哪里。
有三个实验,第一个拼音输入法比较简单。第二个四子棋要求必须是Windows环境,为此我专门买了一块硬盘装了Windows。四子棋的代码我写的可以说相当有自己的风格,比如为了避开动态内存分配,我用模板参数来表示棋盘的尺寸,输入的棋盘参数范围是确定的,但是并不能在编译期确定,这怎么办呢?写一个大switch
,类似于:
1 | switch (x) { |
第三个实验就是训练神经网络做情感分析,RNN我是真的训练不好,最后效果比我用C++手写的全连接网络(这是一个拓展功能)还差,而且甚至到现在我也没有理解RNN到底是怎么工作的。GPU训练速度还是可以的,虽然我这个GPU也不是什么高端产品。
数设就是前半学期讲课,期中的时候就是期末考试了,因此我们学的内容比上数电的学的少多了,后来他们跟我说的一些东西我根本听不懂。考试我记得有道题我写的特别复杂,后来出来一想感觉是把题目意思理解错了,明明有更简单的理解方法,不知道这个扣了我多少分。
后半学期就做一个大作业,我们是用VHDL写的一个基于象棋的游戏,就是手机上的天天象棋里的那种翻棋什么的,这个其实挺好玩的,在手机上一玩就容易上头。我主要写的是逻辑部分,全都是用模拟器在跑,基本上没有怎么碰板子,涉及到跟其他硬件交互的地方我也基本没有参与。我的队友在机房调试,有几次遇到解决不了的问题就把我叫过去一起看,没遇到的话就是他自己写。感觉我又划水过去了,毕竟这种东西如果只是写点逻辑代码那应该是不会学到什么新东西的。最后看大家展示的时候心里真的是充满敬畏和仰慕。
数字逻辑实验,平时的实验就是连线或者写代码,都是做好了直接去检查,所以大部分都不难。最难的应该是有一次要求写计时器,这里面的D触发器我是真的按照书上D触发器内部的连线写的电路,因为我觉得他们的要求就是这样的,调了半天也没调好,这东西就是玄学。结果后来一看很多人都直接用if-else
了,也没有影响分数。
考试有连线和编程两个部分,连线就是搭一个电路,大概是从0开始计数到自己学号的后三位之类的,编程题目是现场告诉你的,用VHDL或者Verilog写一个程序,具体是啥我忘记了,反正很简单。考试前一天我从早上开始就在宿舍里连线,中途发现器件有问题还跑去东主楼调换了一次,直到晚饭左右才第一次跑起来。这还不够,我把它拆了,在纸上记下每一根线两端的位置,然后照着这张纸再搭了一次,又跑起来了,这才算准备充分了。附图一张:
第二天早上去考试,花了十分钟左右搭好了。给我印象最深的就是机房真的很热,可以说搭得汗如雨下。出了机房,心情甚好,准备去看一下复联,掏出手机一看刚好是昨天下映了。
寒假要做一个调研报告,我就随便写了一个仙桃的调研报告,其实根本没做任何调研,就是过年回去了一趟拍了几张照片。
上课是在一个大教室里,应该是在三教,讲的怎么样我也不清楚,反正我没怎么听。有几次讨论,心里是真的虚。一次是关于调研报告的,一次是自己选的主题,我都不知道自己在说什么,就信口开河乱说吧,现在也记不得了。
最后还有一个口述史报告要写,当然肯定也没人给我口述,就自己硬编。两个报告我写的字数都超出了他的字数要求很多,但是我觉得我没法再写短了,而且最后结果证明写的长也没有什么用。
现在我一个实验也想不起来了,这种东西就是纯恶心人的。报告我每次都是手写的,因为我当时还不会LaTex,要是我去打公式的话应该会更慢一些。反正就是”借鉴”一下前辈留下来的报告,这次我”借鉴”的比上次更彻底,更认真,更仔细了,所以就拿了A-。
本来选的是棒球,第一堂课棒球老师说:“你们如果感觉成绩不够一定要找我重测,我上学期就挂了一个58分的毕业班学生。”。他说的是重测,不是商量改分数,我要是能重测及格我为什么不一开始就及格?后来在选课截止前半小时左右,发现有一个空闲的游泳,就退了棒球选上了游泳。这真的是我的运气好,要是我没有选上游泳,我真不知道结果会怎么样。
每次课都重复一样的内容,就是一直在游泳,感觉自己也没什么长进,根本游不快。D+这个分数可能真的是我应得的分数,是所有小分加起来的结果,说实话我的体育能够凭自己的实力及格就已经很不错了,只是这个老师真的不把分数往上调一点,我也没有什么办法。
我就不指出具体是哪位老师了(反正一共就两位,今年我就选了另一位),这课上的我真是恶心。这也是典型的没有念好ppt的水平的老师,每次课开始前还喜欢讲点什么玄学之类的东西,然后课上对着英文的ppt,不知道他在说什么,我怀疑他自己知不知道自己在说什么?课本也不说清楚是哪本,群里问也没人回,搞的我第一次作业做成另一本书上的题了(你那么喜欢英文的ppt干嘛要用中文的书啊?)。
课上喜欢点名,你自己看不出来吗,就这么点人,你能点到谁在啊。我印象尤为深刻的一次是讲非线性流水线调度的时候,他说:“你们接下来一定要认真听,这个自己下去肯定看不懂。”我认真听了,遗憾的是没有听懂,英文的ppt也看不太懂,百度上随便一搜讲得贼清楚。
后来听说要做带GUI的Tomasulo模拟器。好!崩!撤!卖!溜!
今年重选这门课,我越发不知道他怎么能把课教成这样的,换个老师这不是能讲的很好的吗,在群里也跟同学有互动,有答疑。幸好我退了。
]]>本学期均绩4.0哦。
上课上的非常烦躁,前两节课讲的是一些网络发展的历史,虽然很多课前两节课都是这样的,不过这个我直接就听不下去了,可能跟老师和主题都有关系吧。这两节课之后基本就只零星地去上过课了。
学期当中很慌张,因为零星去的几次也听不懂,感觉内容太多太杂了。自己在宿舍也零星地看过网络原理的书,书也许比ppt讲的清楚一些吧,但是也没有太大的帮助,书上讲的太长了,ppt里没有的东西我也不知道该不该仔细看,最关键的是看完之后过不了多久也就忘记了。虽然大家都很推崇那几本黑书,感觉很高大上的样子,说的好像万能药一样(当然,一般这么说其实目的只是贬低一下我们的ppt),反正我是觉得看书对我没什么帮助。
不过后来事实证明这些担心都是多余的,我考试水平一直可以的,当然也是因为复习的比较仔细。考试周开始前大概两周我就开始复习了,ppt看了一遍又一遍,把重点内容摘出来到一个word文档里,也是看了一遍又一遍,再把往年题多做几遍。当然不是所有的时间都用来复习网络原理,反正这几门课我都是一起复习,直到考试前的一刻。
上考场一看,这50道选择题不都是白给,我连GRE单词都能记,这点东西怎么会记不下来。最难选择题的可能就是计算一个CRC码的校验和了,我算了第一次结果不在选项里,按照我的习惯我就跳了,做完回头再做一次(差点忘记了)才做出一个在选项里的答案,后来还验算了好几遍。大题除了第一题考概念算原题,后面确实都没见过,但是从来不觉得这种东西真的能考验什么能力,只要ppt复习足够,往年题做的足够,新题能有什么难的。
路由器实验很简单,这种代码有手就行,难度全在环境搭建和测试上,而且课上的知识基本也没有用到。根本没必要为了优化性能一直卷,只要功能是正确的几乎不会不是满分。最后的代码在这里https://github.com/MashPlant/Router-Lab。
前半段的课都是比较熟悉的东西,后半段就多了很多奇怪的知识点,不过人也是越来越少了。
我是像复习网络原理一样复习的它,但是考试基本没有考那些死记硬背的东西,还有一些分很高的大题,这做起来就很慌。这种题目就是,没有什么可写的东西,全看你运气好不好,有没有和出题人想到一块去,运气不好就没有什么得分的机会了。最后没有拿到A+,可能是考的不好吧,也可能是造机没造好(好贱的一句话啊^^)。
造机基本是我单挑了,因为我总是想着,这么困难的工作,恐怕我的队友应该不能胜任吧,所以基本把所有的工作都做了。本来基本没有遇到什么问题,基本都是前两周的白天在写,一点夜也没熬。前两周基本不怎么需要板子(广义的板子,包括网络上的那个)来调试,都是用iverilog + gtkwave来模拟,编译非常快,这对调试来说是至关重要的。
然而最后做TLB的时候实在是遇到了很大的困难(顺便说一句,n+e写的TLB绝对是假的),不管怎么改上板子结果都是错的,模拟结果又是对的。我一直觉得,我不理解Verilog到FPGA的编译的根本原理,不理解时序那些东西,那能写出任何东西来都是幸运,现在遇到问题了,就几乎没有解决的机会了。最后一周熬了几次夜,不过最晚应该也不过三点吧,我是不适合熬夜的,我夜里工作效率比白天低多了,不过这样造机其实也不需要什么工作效率,就是随便改改代码试试,然后编译一下等5~10分钟(应该还是我的电脑比较好才有这个速度),然后上板子试一下,得到错误的结果后再重复这个过程而已。
最后也没有解决,只是乱改改出来一个看似还算能跑一些简单的例子的CPU来。我们跟助教说,我们造了TLB,但是这个在高频率下不能稳定的运行,所以只拿来展示一些简单的例子,性能测试就用没有TLB的版本来跑。这倒是没有说谎,TLB在高频率下不能稳定是肯定的,毕竟它在在低频率下也不能稳定。
真的挺难受的,最后也没有学到什么东西,而且基本以后肯定没有信心去碰硬件了。
算是数学味比较重的一门课吧,虽然老师讲着讲着就会说一句”我们这是应用学科,不要太在意严谨的数学证明”之类的话。既然是数学课我肯定是认真记笔记了,感觉自己微积分和复变函数的那些东西已经忘记得差不多了。
我感觉每节课记到最后,我都感觉,啊懂了懂了,原来是这样的,好精妙啊。到了做作业或者下个星期再上课,之前的内容我基本就全忘记了。不过这也正常,数学课不都是这样的吗,笔记本来就不是为了平时巩固用的,考试复习能用到就行了。
和其他的课一起复习,也把往年题基本都做了。往年题中感觉有一些很难的,越到近年越简单。ppt中有一些很复杂的公式,一看就不知道怎么证明的那种,可能是大概类似这种的?
要是我早点写这个总结我就能回忆起来具体是哪个公式了,现在REKCARC-TSC-UHT上也还没有这张卷子,所以我也记不起来是什么题了。
我复习的时候就觉得,这么复杂的公式肯定就是拓展知识,不会考的,况且题目不是越来越简单了吗?当然我还是记了,毕竟没有什么不记的道理,我的记忆容量是充足的。结果考试中还真有一题用到了,而且也不是给你左边要你写右边这么简单,而是要在证明过程中自己发掘的。还有最后一题我也感觉挺难的,想了半天才凑出来证明。考完我就觉得稳了,这套题这么难,但是我都做出来了,这门课拿下。
实验照着学长的做一下就行了,很简单的。装matlab可能会成为一个困难,反正我是没有装,我用的是在线版的:https://matlab.mathworks.com。
上了几次课,感觉很无聊,反正也没有考试,后面就不去了。
整门课基本等于一个大作业。代码在这:https://github.com/MashPlant/db。
我寒假就开始写了,因为害怕自己写不完。开始是照着学长的写的,越看越难受,他们的代码怎么这么垃圾,这全是漏洞和性能缺陷啊。垃圾归垃圾,如果让我从零开始自己写,我觉得我就是做不到,我就是需要别人给我个框架才能往上面加东西。我自己写的也是很别扭,用的是Rust,全是unsafe
,每写一个地方就要为性能和安全性斟酌半天。
开学之后没怎么写,可是听到作业要求之后,感觉学长的功能比今年的基本要求都差远了,以前要求这么低的吗?我也有很多功能没有写,比如实现一个真正动态分配空间的varchar
之类的,最后大概是在十一假期的时候完成了,这之后基本没有动过。我感觉基本功能要求实在是太高了,如果把基本功能全部写完了,那一点推荐的拓展功能实在是太简单了。
学期末去找助教检查,事实证明我的”性能和安全性”确实是那些粗制滥造的C++代码比不了的,不过有的人实现的功能比我多多了,我除了没有实现的功能之外都顺利通过。最后拿了A+也是没想到,难道完成基本功能加这么一点拓展功能就能A+吗?
这可能是这学期我学的最认真的一门课,因为我对这个方向感兴趣嘛。遗憾的是没有超出我的预料,基于数据流的优化我基本都会了,除了PRE这个相当综合的优化我没有写过(当然这也是最难的)。后面讲讲循环,SSA,GC,IPA,verification什么的,都很浅,只靠这门课讲的肯定是写不出什么实际的代码来。
实验和作业的代码在这:https://github.com/MashPlant/compiler-train-19。关于实验作业和考试的介绍也都在里面,我就不复制了。
有一说一,这门课工作量比编译原理小,这不是我的锅,编译原理的实验主题又不是我定的。
手球真的是宝藏课程,非常水的一门体育课。之前上过一次了,拿到了我体育的历史最高分(B-)。这学期幸运又选上了,直接划水一学期,反正学分是0,C-也不影响绩点。
你看我上面每门课说的结果都那么好,是不是一帆风顺的样子。但是这个学期我真的是生活在很大的痛苦当中,科研这方面,我所做的所有的尝试,都没有任何结果。主要应该是我自己的原因,也许也有一点客观原因吧,我不想讨论了。每当我想起自己的表现,我就觉得自己完全是个废物。
我不想在这里说太多负面的东西,我希望我展现给别人的永远是光鲜亮丽的一面,只是不知道这样还能维持多久。
我觉得我就是不适合做科研吧,做作业和考试水平倒是一直可以的,那是因为有个明确的目标,而且”不可能失败”,这是我心里对自己说的原话。但是我还是想出国的,只是为了不辜负我考的那么高的GT。
]]>wasm-bindgen
对用来封装单个基本类型变量的结构体的处理不够优化。你要问我做不做这个优化究竟有什么意义,我只能回答确实没什么太大意义,这几乎不太可能成为性能瓶颈,所以这篇文章主要目的其实只是记录一下我作为初学者探索wasm-bindgen
的工作原理的过程。
先放一下工具链的版本,毕竟关于WASM的一切都还在快速更新,版本不一样结果可能会有不一样:
1 | $ rustc -V |
因为我本身是一个初学者,所以贴一下项目的构建过程:我是follow官方的https://rustwasm.github.io/docs/book/game-of-life/setup.html 的构建过程,看Setup
一章和Hello, World!
一章即可。以下假定项目的名字叫blog
,且目录结构与这篇文档中的一样。
首先在src/lib.rs
里写如下代码(原有的代码删了,都用不到):
1 | use wasm_bindgen::prelude::*; |
the_answer
参数用self
而不是&self
从正常的Rust角度来看是完全合理的,因为Answer
很小,而且实现了Copy
,如果不考虑编译器优化的话传self
可以节省函数体内的一次访存,考虑了编译优化self
和&self
至少也是一样快。
如果是正常的Rust程序,假设是x86_64-linux
的ABI的话,你应该会期望这两个函数非常简单,Answer
结构体可以直接用寄存器传参/返回,所以new
应该直接编译成寄存器赋值,the_answer
应该直接编译成一条寄存器加法(其实是lea
指令)。
我们来编写一点JS代码来测试一下。提醒一下,在此之前请先follow官方的教程,修改www/package.json
以及执行npm install
。把src/index.js
改成这样:
1 | import {Answer} from "blog"; |
在www
目录下运行npm run start
,访问,很不幸出错了,浏览器的输出如下:
1 | 42 |
可见第二次the_answer
调用失败了,就像是我们没有加#[derive(Copy, Clone)]
的时候,Rust编译器不让我们第二次调用the_answer
一样。我大胆地推测一下,目前wasm-bindgen
这个工具并没有考虑我们写的#[derive(Copy, Clone)]
。
我们编写的JS代码和WASM的交互是经过pkg/blog_bg.js
这一层的间接的,看看这里面the_answer
是怎么实现的:
1 | export class Answer { |
原来是调用过一次之后就置成空指针了,这就解释了为什么第二次调用the_answer
时报了一个空指针错误。但是为什么要这样做呢?为什么不能留着这个指针多次调用呢?
稍微修改一下Cargo.toml
,删掉以下的部分,让接下来的结果更易读一些:
1 | [profile.release] |
它为了节约空间会让一些函数不内联,这样我们读起来还需要跨越函数,就不方便了,所以删掉它。删掉它也不会影响其它该做的优化的效果。
那我们现在编译一下,看看WASM下这两个函数会被编译成了什么:
1 | $ wasm-pack build |
请注意wasm-pack build
就是以优化模式编译的,wasm-pack build --debug
才是以调试模式编译的。
看看the_answer
的结果:
1 | (export "answer_the_answer" (func $3)) |
这也太复杂了吧!如果你不熟悉这个WAT(WebAssembly Text Format)的语法,还可以用wasm2c
(与wasm2wat
安装方法一样)来看看它翻译成C之后长啥样:
1 | static u32 w2c_answer_the_answer(u32 w2c_p0) { |
我还可以再人工精简一下这个C代码,不过不是等价转化,只是为了理解方便:
1 | // WASM默认是32位的,所以sizeof(u32 *) == sizeof(u32)成立 |
至此可以总结:Rust中的移动语义在WASM和JS中的体现方式为:
而不是直接传值,即使这个结构体只有一个字段,完全可以由一个JS的number
来表示。
所以,在wasm-bindgen
的代码中用self
这样的移动语义并不能带来任何好处。事实上,如果将the_answer
的参数换成&self
,重复一遍上面的工作,就会发现结果仅有的改变只是WASM中没有了free,JS中没有了指针置0。
我们将用cargo-expand
工具来查看#[wasm_bindgen]
生成的代码,没有安装的人可以执行cargo install cargo-expand
来安装。具体参数是:
1 | cargo expand --lib --target wasm32-unknown-unknown |
注意这个target参数不可缺少,这是Rust编译为WASM时的target参数,如果使用默认target,结果会不一样。
有必要先介绍一下相关的trait的含义是什么,这些在文档(https://docs.rs/wasm-bindgen/0.2.62/wasm_bindgen/ )里都有介绍,为了方便理解我这里再用简单的文字描述一下:
IntoWasmAbi
/FromWasmAbi
,分别表示将一个Rust类型可以转换成一个小的handle类型,和转化回来。尺寸较大的类型没法用一个handle直接表示,所以肯定只能申请动态内存,用对应的指针来表示;尺寸较小的类型,如u32
,f64
等几个基本类型,可以用自身作为handle直接表示。ReturnWasmAbi
与IntoWasmAbi
几乎是一个意思,只是为了处理一些我们这里不用考虑的特殊情形。ReturnWasmAbi::return_abi
的默认实现就是调用IntoWasmAbi::into_abi
。WasmDescribe
:文档中不公开,似乎与内部API的名字有关,看起来它的实现不能乱改,但是具体是怎么影响到其他代码的我现在也没有完全了解。接下来分析一下生成的代码的各个组件,为了美观我对生成的代码做了一点小调整,没有改变语义。请注意之前列出的WASM代码都是Rust代码翻译来的(中间经过了LLVM IR,不过这不重要),而JS代码则不是,是根据Rust代码中的额外信息(即不是代码本身)加上一定的策略生成的,这我后面会提到。
Rust的数据传递给JS:
1 | impl IntoWasmAbi for Answer { |
JS调用Rust的函数:
1 | impl FromWasmAbi for Answer { |
从这一来一回的代码可以看出JS中保存一个指向Rust中的WasmRefCell<Answer>
的指针(实际上是用number
保存的),这个指针是Rust这边动态申请得到的。为什么要用WasmRefCell<Answer>
而不是直接用Answer
呢?可以想象一个场景,Rust向JS export一个接受&mut self
的函数,也从JS import一个函数,其中也调用了接受&mut self
的函数,在Rust这边的函数中可以调用JS的函数,这样就不用unsafe
而违背了借用规则。如果有WasmRefCell
的话,当Rust调用JS,JS再调用Rust时,就会因为运行时的借用检查而失败。
除了这些看起来比较正常的函数之外,还有一些奇形怪状的函数和数据:
1 | impl WasmDescribe for Answer { |
这个一看就知道是在描述Answer
的名字,6是名字的长度,后面的都是ascii码。这个实现是必要的,因为wasm-bindgen
定义trait IntoWasmAbi: WasmDescribe
。
1 | impl From<Answer> for JsValue { |
这个实现让Rust这边可以将一个Answer
转化为JsValue
,这样的trait实现如果我们不用到其功能的话是可以删掉的。
还有一些例如OptionIntoWasmAbi
,RefMutFromWasmAbi
的trait实现,从名字就很容易看出其作用,也都是如果不用可以删掉的。
剩下的对我们的优化没有什么帮助,因此不再分析了(其中有一些我也没有看懂)。
在我们的应用中没有必要用指向WasmRefCell<Answer>
的指针来表示一个Answer
,因为一个u32
足以表示Answer
,而且也没有一个函数需要修改Answer
的内容,完全可以让JS只保存这个u32
,遗憾的是目前wasm-bindgen
没有这样的接口。我们的目标就是手动编写这些函数,实现这个目标。需要说明的是,这些代码中用到了很多wasm-bindgen
的内部API,所以肯定不适合人手工编写,但是如果只是为了探索的目的,用在一些简单的个人项目里,并且写死wasm-bindgen
的版本,我想应该也没有什么问题。
我希望能够重写IntoWasmAbi
等trait的实现,因此不能给结构体加上#[wasm_bindgen]
的标记,而在impl块上的#[wasm_bindgen]
则可以保留。为此,上面提到的所有函数中,除了我指出没有必要的,和与后面定义的函数相关的两个函数之外,其他函数都需要自己写一遍。
1 | // WasmDescribe的实现不用改变 |
至此还有一个问题,即使重写了这些函数,并不会影响到生成的JS的代码中将ptr
置0的操作,这个操作是由fn the_answer(self)
的定义决定的。如果你感兴趣是这个self
是怎么转化到将ptr
置0的操作,可以看下一节,这里我们用另一种方法绕过去:
1 |
|
这是feature吗?还是bug?反正根据实验,加上#[wasm_bindgen(getter)]
标注后生成的JS的代码中就没有置0操作了。你可能会想到,如果在原来的代码中加上#[wasm_bindgen(getter)]
,第二次访问the_answer
的时候不就访问到被free的内存了吗?你想对了,是这样的,我这里会得到一个:
1 | Error importing `index.js`: RuntimeError: memory access out of bounds |
从一般的静态语言的常识来看,这样的错误不总能被检查出来,也许下次就会悄无声息地出错,谁知道呢。
重申一遍,现在的Rust WASM工具链还在快速更新,这些东西如果只是为了探索的目的,用在一些简单的个人项目里,并且写死wasm-bindgen
的版本,我觉得是没有什么问题的。
最终代码为:
1 | use wasm_bindgen::prelude::*; |
更新:
坏消息,这个方法在最新的wasm-bindgen
已经不再能work了。因为我自己给wasm-bindgen
提了一个issue-2168,作者觉得这确实是个bug,并且在commmit-cc36bdc中修复了!修复的方式,修改的代码都与我设想的完全一样。
不过好消息是这个commit是发布在wasm-bindgen
的0.2.63
版本中的(我还不确定Cargo每个版本的代码具体是怎么来的,它是在发布0.2.63
之后版本的一次commit,我用0.2.63
版本测试了一下还能work,也许是0.2.64
版本才会发布这个改动),也就是我用的0.2.62
并不受影响。
一个不利用这个bug也能work的版本,虽然经过了一些间接,麻烦一些,但是经过优化后生成的WASM代码是完全一样的:
1 | // 上面的代码基本复制下来,去掉impl FromWasmAbi for Answer和impl Answer块后再加上这些代码 |
别忘了www/index.js
里的the_answer()
要改成the_answer
。编译,npm run start
,访问浏览器,成功!
看看生成的WASM:
1 | (export "answer_the_answer" (func $0)) |
完美!
上面其实说到了,生成的”代码和数据”,数据在哪里呢?这是为the_answer
生成的一个数组:
1 |
|
如果你把定义改成the_answer(&self)
,就会发现只有一个字节改变了,即Answer\x00\x01\x00
变成了Answer\x00\x00\x00
。删掉the_answer
,只保留它生成的东西,生成的JS中有置0操作;再手动把Answer\x00\x01\x00
改成Answer\x00\x00\x00
,生成的JS中无置0操作。这个对比实验即可证明生成的JS代码的依据确实是这个数组。
我通过阅读wasm-bindgen
的源码已经理解了其中的原理,分别确定了这个字节是由函数的定义决定的,和JS中有无置0操作是由这个字节决定的。但是要完整地分析就太长了,而且这只是人家现在的实现而已,并没有什么原理上值得学习的地方,不值得长篇大论地去分析。这里只贴出相关的链接,感兴趣的人可以自己去阅读(不同文件间基本是按调用关系排序的):
这个字节是由函数的定义决定的:
JS中有无置0操作是由这个字节决定的: