老司机乱谈破局——做自己的破壁人

发表于2016-05-30
评论5 4.8k浏览
老司机乱谈破局
——做自己的破壁人
       2016年4月26日夜,大雨,被困深大操场主席台,雨雾氤氲罩住了整个体育场,远处的腾讯大厦灯火阑珊,有一种水墨画般的质感。随着空气变得清新,思路也变的清晰,于是想到了写一篇关于思考的文章,这就是本文的来由。

静能生慧
       有的人常说,我不快乐,深圳房价太贵了,有房子才能快乐,事实上快乐的人在什么时候都快乐,不快乐的人在什么时候都不快乐。这是由一个人的心决定的。什么样的心境就决定了什么样的思考方式,下图这个漫画就是一个很好的诠释。


       心是思考的出发点,浮躁的内心是没法在思考的路上走的更远。把心摆正,慢下来,远离喧嚣与浮躁才能开始正确的思考。静能生慧,这是思考的前提。

海阔天空
       你的知识体系的容量决定了你思考的广度。google可以是你的知识库,但你需要一串钥匙,所以你的知识体系一定要广。比如Memorybarrier 的问题,如果你都不知道有这种问题,如何去google检索呢?当你的知识体系化足够全面,你思考问题的效率就会更高,思考的过程也会更为全面合理
       全栈工程师为什么比较火,因为掌握了20%的核心技能,剩下80%的技能放在google服务器上,关键的时候知道如何检索。同时减少了沟通成本,减少了文档,避免软件开发中的一大难题——沟通导致的效率下降。以我们以前开发的一款页游来举例,我们项目除了一个美术就是程序,自己出需求,自己开发后台,自己开发前台,最后自己测试。我们可以在人极少的情况下,不加班,快速的完成游戏开发,事实上这个游戏也算是BG最赚钱的页游之一,而且得到运维人员的一致好评。为什么现在的游戏团队如此庞大,但效率却如此低下,虽然加班但是游戏也没做好,这是一个值得我们深思的问题。
       计算机系为什么学习了一大堆课程,就是没有讲过编程。虽然那一大堆课程你可能遗忘了,但是开启这一扇扇大门的钥匙还在你手里。当你思考问题的时候,那些蛰伏已久的知识就会在你的脑袋里闪闪发光。有的人总喜欢问应该学什么编程语言,其实语言只是一个工具学起来很快,而你知识体系的广度和深度以及你的思考方式才是你最宝贵的财富。比如C语言也没几个关键字,很多人很快就学会了,但为什么有的人可以创造出Unix,有的人只能创造HelloWorld
       任何一个事物都是具有多面性的,如果知识面不够宽,视野比较窄,容易形成局限的错误的观点,因为你用平面的方法看事物,既没有立体化也没有参照系。而现实世界的各种事物的参数比较多,了解的越多,观点越趋近于正确。每天可以看看逻辑思维之类的文章,拓宽自己的思路,当然不是说逻辑思维讲的一定是正确的,但可以学习别人的思考发方法和角度,让我们的视野更加宽阔。毫无关系的两个学科的知识也许会通过类比或者其他途径触发你的灵感。比如看一个城市的发达情况,不光是看货币总量,还有看货币流通率。可以理解为一个是电压一个是电流,共同影响了整个系统的功率。当你的知识面宽就可以站在更高的角度进行更高级的抽象,你思维的效率和品质将极大的提高。
       我们说说软件。软件是一种工程,在工程中一切复杂的技术实现都是会被历史淘汰的,最终都会出现一个返璞归真的,容易实现的方法。我们用同步性算法来举一个例子。Paxos是一个很有名的一致性算法,但真的适合于工程吗?该算法如果理解了会觉得比较简单,但是其实并不适用于工程,实现起来会有一些问题以及性能瓶颈,Paxos一般用于工程中,大部分情况也只是用来实现一个选举的过程。事实上最初Paxos的论文在ACM Transaction on Computer Science编辑社沉寂了9年才最终发表。对于工程来说Raft算法更好理解与实现(当然有人说宇宙中只有一种同步算法Paxos,其他算法都是演化或者阉割的版本)。所以当你站的越高,看的越广,对比越多,你的思考你的决策就会越优。大家有兴趣可以学习一下这两个算法并对比一下,了解其中具体的差别。

高屋建瓴
       说了思考的广度,我们再说思考的深度。虽然对很多东西可以作为广泛了解,但对于一些东西也要做到全面透彻的理解。只有透彻的理解才能更好的应用。我们看两个例子。
       协程,协程的本质是什么?宏观上并行,微观上串行。宏观层面上对于程序开发者来说,提高了程序的可读性,提高了开发效率。微观层面上对于计算机来说提高了运行效率。协程为了实现宏观的并行,必须有自己的调度机制,把在等待(比如IO等待)中的代码段暂停,跳转到可以执行的代码段中来。下面用我以前写的一段代码来说明协程的调度核心。


      源码之下 ,了无秘密。这个函数的原型是这样的。


        函数实现了从一个上下文跳转到另外的一个上下文,用来实现在某一段代码受到阻塞之后立即实现用户态的调度,以提高CPU的利用率。核心代码分为2个部分,第一部分备份当前的上下文到自己管理的内存结构中(RDI),第二部分恢复即将运行代码段的上下文(RSI)。另外代码中利用修改函数调用栈中的返回地址来实现了跳转功能。具体实现很简单,先POP出源堆栈内的函数返回地址,PUSH目标地址,最后调用RET来实现跳转。以上过程说起来比较简单,其实涉及到的知识面比较广,科班出身的应该能被这段代码唤起一段回忆吧。不过C++里面实现携程总有点不伦不类,姑且认为是更好的技术出现前的一个过渡吧,但这并不影响我们对于技术的研究。
      附:AMD64 ABI中关于寄存器的约定(适用于Linux环境)。


       我们来看另一个例子,内存屏障。如下两个函数,假如x,y,r1,r2的初始值都是0,cpu1上执行 run1,cpu2上执行run2,执行完后,是否能出现r1=0,r2=0的情况。


      事实上,在我的开发机上测试,运行不到百万次就会出现这个情况。原因是CPU硬件设计为了减少指令延迟,增设了两个缓冲区(Storebuffer,Invalidate queue)。


       比如CPU执行写操作的时候,直接把数据写入Storebuffer就算完成操作。而不是等不同cpu的cache数据完成同步。这样在多cpu的情况下会造成一些数据不同步问题,需要使用显示的内存屏障指令来完成同步。当然一般我们写程序不会遇到这个问题,或者还没遇到这个问题,自己的程序就已经产生其他bug了。但是在linux内核程序中类似的指令是经常用的,比如大家可以看内核中kfifo的实现(BTW这段代码也是写的堪称鬼斧神工),以及怎样用内存屏障进行同步。
如果大家对内存屏障有兴趣可以阅读whymb这篇文章,这篇文章写的是前无古人后暂时无来者(反正我没看到写的更好的),越读越有趣。这篇文章看前半部分即可,已经把内存屏障讲的非常透彻,后半部分涉及太多的具体硬件比较晦涩可以略过。总之硬件工程师挖的坑软件工程师填。
       有的人会说赶紧忙完自己的需求就行了,没空深挖这些东西。《禅与摩托车维修艺术》书中有一句话说的很好,仓促本身就是最要不得的态度。当你做某件事情的时候一旦想要快,就表示你再也不关心它,而是想去做别的事情?

博古通今
       在时间的尺度上思考,因为任何事情都需要放在一个时间的维度上才能是正确的。如果不加上时间这个维度,可能就会得出一个错误的结论。比如C10K年代的一些问题,在现在看来根本不算任何问题。时间在推进,世界在发展,观点必须经常更新。我们已经经历了了人比服务器便宜和人比服务器贵的年代。所以在思考之前,先导入时间坐标,以免自己走入误区。
       大学里我是一个跑1km都很难完成的人,但是2年前跟同事去深大操场跑步的时候,第一次就可以跑10km的距离。因为经常的一些户外运动,使你的身体发生了一些自己都不知道的变化,如果再以自己以前的经验来思考,跑两圈就可以休息了。实际上当你跑了5圈之后你就会发现,还可以再来5圈,当你完成10圈就会坚持到20圈。当你坚持到20圈就会想想再坚持5圈就一万米了。可能让大家想到了《Facing the Giants》这部电影,对,就像电影里最后所说的,”With God, All ThingsAre Possible.“
       很多事情,你不敢做,其实是被自己的固有思想打败了。人生贵在行动,迟疑不决时,不妨先迈出小小一步,若是美好,叫做精彩;若是糟糕,叫做经历!

出其不意
      突破定式思维,用脑洞大开的思考方式。我们看这样一个场景,如果一台计算机做了物理网络隔离且不能使用任何外设装置,如何把这台机器上的数据传输出来呢?

 

      github上有一个解决方案,利用了cpu在执行特定指令的情况下会产生的电磁辐射的特性,程序使cpu在特定模式下工作,把数据调制到cpu产生的电磁辐射中,进行数据传输。使用无线电接收机进行数据接收 
      我们再来看一个rbtree的实现。我们先看看正常点的rbtree的节点定义。


       只是一个颜色,就占用了4个字节,还不够美是不是,毕竟作为基础结构在linux内核中大量使用,能省则省。但是这个结构里有3个指针,多出来的颜色标记怎么办呢,是否能有更优雅的解决办法。
     下面给出一个脑洞大开的解决方法(以下代码来自于2.6内核)。


     新的结构中,被使用了强制对齐,这样有什么用呢?
     我们来看看《深入理解计算机系统》3.10节。许多计算机系统对基本数据类型的可允许地址做出了一些限制,要求某种类型的对象的地址必须是某个值K(通常是2、4或8)的倍数。这种对齐限制简化了处理器和存储器系统之间接口的硬件设计。例如,假设一个处理器总是从存储器中读取8个字节出来,则地址地址必须是8的倍数。如果我们能保证所有的double都将它们的地址对齐成8的倍数,那么就可以用一个存储操作来读或者写值了。否则,我们可能需要执行两次存储器访问,因为对象可能分放在两个8字节的存储器块中。 无论数据是否对其,IA32硬件都能正确共走。不过,Intel还是建议要对齐数据以提高存储器系统的性能。Linux沿用的对齐策略是2字节数据类型(例如short)的地址必须是2的倍数,而较大的数据类型(例如int,int*,float和double)的地址必须是4的倍数。注意,这个要求就意味一个short类型对象的地址最低位必须为0。类似的,任何int类型的对象或者指针的地址的最低两位都必须是0。  
      好了,既然对齐了,指针(rb_parent_color)的最后2位其实都是零,没有用,既然没有用,就把颜色Flag存进去就行了,这样这个结构从存储空间上看又是完美的了,Linux内核中很多代码都体现了美,程序之美。
     当然,回到我们的工作中来,我们毕竟都是开发应用层的软件,需求变化多,工作交接多,代码可读性是最重要的,还是不要写出这样的代码坑害你的队友。什么样的工作场景写什么样的代码。每段代码都是一段故事一段传说。

结语
      心似莲花,学会用自己的方式去思考,不盲从,不妄言。从不同的高度,不同的角度,不同的时间维度去思考,不要让其他人羁绊了你思考的脚步,心怀敬畏,上下求索。十数年后你的人生也许会发生不可思议的改变。最后用苹果97年的广告词送给大家。
Here's to the crazy ones,
the misfits,
the rebels,
the troublemakers,
the round pegs in the square holes,
the ones who see things differently.
They're not fond of rules,
and they have no respect for the status quo.
You can quote them,
disagree with them,
glorify, or vilify them.
But the only thing you can't do
is ignore them,
because they change things.
They push the human race forward.
And while some may see them as the crazyones,
we see genius,
because the people who are crazy enough tothink they can change the world
are the ones who do.
Jaco于2016年5月28

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引