2017年8月14日星期一

理性被狗吃了

认知科学告诉我们,日常生活是由各种偏见构成的。例如证实性偏见,选择性偏见,幸存者谬误...。理性是被因为社群协作的需求才演化出来的,理性不是我们做决策的第一选择,需要被有意练习才能被自如使用,以至于能借助逻辑、借助抽象、借助分析和演绎来得出更靠谱的决策。

可是理性哪里是那么容易的呢?一个人自己会有理性和感性的时候,或者说大部分是感性的时候,少数是理性的时候。一个人与他人之间竞争与合作,对方的感性会影响到你的决策,你可以用理性也可以用感性应对,我们在生存中学会了很多被动式理性,因为没有被动式理性决策,在生存中就处于劣势。我们又在感情中使用感性,因为感情中很少有理性,大部分是感性。

只要看看我们遇到问题的时候,更喜欢分析事情里哪些是事实的部分,哪些是情绪的部分就可以知道。我们人类是更喜欢用感性的文字代替理性的分析。这有点像黑盒子,当感性文字能掩盖理性分析的缺失,用比喻和修饰代替说理。如果,如果能奏效,她确实很多时候能有一定程度的奏效,像一种安慰剂一样产生安慰剂效应。那也不错。不过似乎常常会有半衰期,因为我们不知道黑盒子背后的机理,于是会不断的寻找新的安慰剂。唯有拨开云雾,深入认识到背后的机理,才是治本的。

那些植入了的生物钟,生物程序。因为习惯的力量,常常难以改变,就像理性被狗吃了一样,让我们在情绪到来时,作出瞬间的错误决定。

2016年11月25日星期五

读书摘要,杂书

《像导演一样思考》

"对说故事而言,有一种本能是不可或缺的:发现观众喜好的能力。不论是去村庄、医院,或是开放的田野,他们总得重新学习如何抓住观众的注意力。 - 双重观点:其中一个观点是深入剧本的世界,通过探究与同情剧中人物最深的欲望和弱点来发掘它的活力;另一个观点则是把焦点集中在结构上。"

"不过,就和每一种艺术形式一样,光有技术根本不够。一个导演要成功,必须具有创造力、创作才能、直觉以及最重要的----热情。 "

"正如演员一站上舞台就应该图谋不轨(an ax to grind,或者说“具有强烈的企图心”),导演艺术家“必须有话要说”。这些话不需要是社会或者政治宣言,也不应该替代或高于戏剧本身要说的话。必须有话要说,指的是传达一种独特观点的热情。因为有话要说,所以说故事,所以当导演。"

"戏剧是由一连串有意的行为和活动交织而成,因此,追求某个目标或欲望的具体情节,是深入剧本的持续不变的通路,对大部分的演员和导演来说,也是最清楚的通路。故事情节是一出戏的内在机制和引擎,确认情节对于形成完整的概念和指导演员都是至关重要的。你一旦破译了每一场戏的情节,整出戏整体的情节(或说目标)就会浮现。如果你有效地明确表达每一个情节,两个(或两组)主要角色的目标就会形成核心冲突,这就是叙述故事和传达概念的关键。 "

《心理学》

大众对心理学的初步接触,通常会是以各种**效应**开始,然而这些效应只是一些边角料,真正阅读心理学的教科书时会发现,我以为我看的是心理学,但是看了差不多半本了,基础的部分基本上是在学生物学、生理学的感觉。但是奇怪的是市面上看到的诸多心理学文章都会感觉是偏主观的鸡汤心理学的感觉,这大概和科普和民科的关系类型:由于人类对特效药的迷恋,导致很人都以为这是一个万能的钥匙,似乎扯上心理学就是心理学了。然而正经的心理学教科书对每个问题都会尽量同时从生物的、生理的、社会的等多角度,基于科学的观察、对比试验小心翼翼的给出它切实能得出的结论,并且时刻不忘提醒其不适用的情况。我觉的这才是一门科学所应该有的态度,而不是“人人都是心理学家”那种单样本的、经验主义的、中医式的武断。

《沉默的大多数》 

去泰国的来回飞机上翻了一遍王小波的《沉默的大多数》。以前在博客园看到一位园友摘录了一些片段,读来感觉文字很有意思,就买来一读。 这是一本杂文集,每篇针对一个生活中的话题来说,作者常常引用中式的和西式的哲学并作对比(常常出现的是罗素),引用科学人物和品名,引用作者七、八十年代的经历,通过逻辑上的正反面推导,通过在常识上的信与不信,人物上的大与小的讨论,通过这些展开一个小话题,当其完毕,即结束一篇。 我一开始以为《沉默的大多数》会在思辨中讨论大多数人为什么沉默之类,然而并不是。

以我的理解,作者表达的反而是沉默的大多数之沉默的一面是人性的一面,自由和平等的一面,因为话语即权利,沉默的大多数那不所表达的一面则恰恰是对话语的内在抵制:“虽然不说,但别以为大家都是傻瓜”。逻辑摆出来,是马是驴还是骡子,道理还是可以讲的清楚的。 参差多态,乃是幸福本源。无论是用一种错误反对另一种错误,还是以道德和价值遮掩事物本来的面目,亦或者自以为是的救世主情结,以及对一个错误的东西专研也能成为专家,甚至追求一本万利的银弹,包治百病的良方...此中种种,“世间百态”在逻辑面前其实都是纸糊的。 沉默的大多数人,(他/她)们只做(他/她)们自己。

泰国简要记录

我们在曼谷,那个城市是旅游城市,很多中国人和欧美人在那边,我们就去了好几个寺庙(卧佛寺、黎明寺..)和大皇宫、木柚宫,等,湄南河的船运也不错,泰国是一个典型的贫富差距巨大的城市,天铁左右50米内比较豪华,大广场和香港也差不多,但是往外200米,就能到乡下的感觉。公交车比较老式,突突车(三轮)和摩的到处都是,速度很快,但他们的的士居然都是全新的丰田的。泰国的水果非常丰富,基本属于不缺,所以水果的质量根本不用担心,芒果和椰子比国内的好吃很多。他们吃沙拉比较多,蔬菜直接切碎生吃,食物加一些香料,不过我们吃不大惯。泰国的天铁,和厦门的BRT类似,不过是轻轨火车,买Rabbit卡一张,充值即可用。有一次我们4个男的从卧佛寺一路随便走,被一只狗给吓住,估计是语言不通,不让我们过去,我们只好往回走,看到有经过的泰国人尾随跟他们一起过去,场景堪比周星驰演的情节。

2016年6月1日星期三

command line as OS

典型场景是:服务器远程登陆,只有命令行界面,此时不得不用命令行工具处理日常工作任务,所以就用,边用边查手册,用久了所以熟悉

日常任务有:
  • 文件上传、下载。一般来说就是sz,rz,wget,curl,scp等。
    • for循环wget,分分钟多进程下载。
    • wget -c 分分钟断点续传。
  • 压缩、解压。所以得会用tar,zip等命令。我一开始的时候,问一个老手tar cvzf,tar xvzf后面一排参数啥意思,老手说:”早就忘记了,天天都这么用“。所以我认为它和一个按钮一个菜单没什么区别,命令行参数的选项和参数一般是经常用就熟悉,久不用就容易忘记,此时复查是正常的事情。顺便吐槽下windows下7z的菜单里直接解压tar.gz要解压两次真是麻烦事,命令行当然可以一次搞定。
  • 目录和文件的创建、移除、拷贝、移动。基本命令是:mkdir,rm,mv等。这是最基本的常规操作了,一般来说需要注意的有:
    • 非空目录的移除,
    • 递归删除时拼接路径的安全隐患(递归删除根目录就一点都不好玩了)
    • 删除时文件被占用
      • 如果可能只是短时被占用,可以sleep几十毫秒再删,重试n次退出。
    • 考虑采用”删除xxx.old, 移动xxx到xxx.old, 拷贝yyy到xxx“的方式代替”直接删除xxx,移动yyy到xxx“的方案、
    • 文件系统
      • 理解硬盘驱动,冗余磁盘阵列,inode,文件和目录,缓存策略等。
  • 文本显示、查找、替换、统计。在命令行下分析程序的日志,必备的技能就是文本操作。基本的命令有cat,tail,less,head,grep,sed,awk,wc,xargs等,这组命令行基本要通过管道来配合使用。我个人觉的awk的设计有点古老,awk的脚本我是一点都不觉得写起来流畅和爽快,sed的稍微好点,但也一般。如果是在windows下,这种流式过滤的工作,我是宁可开个LinqPAD写Linq来做,其实很多时候写脚本我都实际上觉的用正式点的高级语言做会更好。很多shell脚本写的人天马行空,别人读起来就不好玩了,毕竟写shell的人不写注释,遍地意大利面条。吐槽归吐槽,现实上我们不得不妥协,该用还得用。毕竟一切数据都可以输出成文本,所以文本的流式分析是基本技能。
    • 我认为这组命令的组合使用能让人体会到管道的好处。管道让多进程之间的流式操作流畅进行。顺便我认为shell的一个特点是,它天生是多进程程序,一个shell里随便一堆命令就是多进程操作,可能很多人写着的时候并没有意识到这点哦。进一步,多主机之间ssh授权后,shell里的ssh远程命令的组织又天生带有点分布式属性,也许这能带来一些启发。
      • ssh 信任就是非对称加密。
        • A机留私钥; B机在.ssh目录放公钥,在.ssh/authorized_keys里填上公钥内容。
        • 则A机执行ssh user@B cmd 的时候,B机会用公钥和A机之间建立ssh连接,然后执行命令。
          • git 的客户端向git的仓库之间建立ssh授权也是这个过程,实际上任何依赖于ssh建立授权的都是这个过程。
          • 这实际上是一种“反连”协议,思考下私钥在哪里就知道了。
    • 理解管道的另一个要素是,需要理解fork,vfork、clone,exec;需要理解标准输入输出。fork之后,在exec子进程之前重定向父进程的标准输出,重定向子进程的标准输入,这样它们就被流式串在一起。
      • 好吧,被awk粉丝吐槽了:D
  • 文本编辑。
    • vi基本技能。shell下编辑文本只能用这个了,我认为vi或者vim里最重要的技能就三个:导航、查找、替换,带着这个思维去记忆那些命令会明确很多。我觉的任何一个程序的核心功能就几个,每个核心功能的核心命令也就几个,其他的就按需查用即可。~~
      • 理解模式的概念,同样的键盘按钮,在不同vim模式下,操作代表不同的语义。想象一下相机是如何由有限的几个按钮做到多组不同功能的支持。
        • 我认为快捷键并不是vim/emacs之类的最大特点,任何一个支持快捷键的编辑器,你都可以通过熟练掌握常见的快捷键使得操作快捷高效,比如notepad、visual studio、word等,理解这点有助于我们看清事实,驾驭工具。
  • 资源监控。输出的信息都可以配合grep等使用,比如ps aux|grep processname.
    • 任务管理器型:vmstat, top, htop,
    • 进程:ps
      • cpu的虚拟化。
      • 理解OS对process的调度:Scheduling (computing)
        • FIFO,SJF,STCF,RR,MLFQ
        • 理解os调度process的话,再去看协程,用户态协程需要自己做调度:)
        • IO密集和CPU密集?
    • 磁盘:du, iotop, iostat
    • 文件句柄:lsof
      • 比如查看一个进程打开的文件描述符,反向查看哪些进程打开了某个文件。
    • 内存
      • 物理内存的虚拟化(进程的内存分布,内部浪费和外部浪费),分页,页错误。
  • 调试和Hook。开发的话会需要调试程序和Hook程序等。
    • 通过ulimit -c unlimited让程序崩溃时产生coredump,然后用gdb调试,看崩溃堆栈,变量、单步等,这是gdb的常规操作。linux下开发必备技能了。ulimit 还可以限制其他资源,比如-n限制文件句柄个数,-s限制堆栈大小等,具体查文档。
    • 系统调用、堆栈等的Hook:strace,dtrace、pstack等,这组算是lowlevel的神器,在很多时候能起到一针见血的效果。
      • 顺便,可以理解下系统调用的原理,中断的实现原理,这样你在使用strace监控一个程序的系统调用的时候,脑子里会有更清晰的思路。系统调用并不是一个简单的函数调用这件事么..
        • Things UNIX can do atomically 多进程之间可以对文件和文件夹加锁
          • 说起锁,想起Dijkstra,锁、信号量、等并发编程同步的机制最早都是他搞清楚的。
            • 什么是”原子“的,在CS里,这是一个及其重要的概念。
            • 锁应该保护的是数据还是行为?
            • 锁的尽量局部化。
        • 感兴趣?:Operating Systems: Three Easy Pieces
    • export LD_LIBRARY_PATH=$PWD;
      • 理解系统查找共享库的搜索路径顺序。
    • readelf: 都说了任何东西都可以输出成文本来看了,看个依赖库路径还是容易的。
    • perf

      "Have a C program and want to know which functions it's spending the most time in? perf is a sampling profiler for Linux that can tell you that." (这个我还没用过)

    • Linux debugging tools I love
  • 网络。
    • tcpdump抓包是一定要掌握的,抓tcp、udp包是必备的技能。输出pcap格式文件也是要的。如果是有图形界面的话,wireshake在windows和linux下都能用,如果是windows上还可以用microsoft networkmonitor。说实话,我觉的microsoft networkmonitor做的比wireshake好用。
      • 一个tcp连接,三次握手、四次挥手;建立在tcp连接之上的http连接,http request包在什么时候发,seq号是怎样变动的;诸如此类,都可以在这个地方查看。
        • 为什么要三次握手。syn、ack、ackack,它们是原子不可分的么,在更复杂的协议里,一个syn,一个ack、一个ackack本身可能就是一组子协议三者中任何一个环节被打破(Break),都会导致握手失败
          • P2P打洞的原理
            • NAT,限ip,限port,限ip和port,随机ip和port
          • DNS的原理,host,
            • DNS污染
            • gethostbyname不能ipv6,getaddressinfo会卡死,自己写全异步dns协议去解析。
        • 为什么要四次挥手。自己设计流式协议的话,如果你不需要这种Feature,就可以暴力Close了。
          • 知其然,知其所以然。
        • Http协议说到底是一个Tcp协议,这点很重要。
        • 协议森林:协议森林 - Vamei
          • 拥塞控制算法,根据历史数据记录估算未来数据,这是一种学习(统计)
            • Deep Learning,NN
      • 一台机子有多个网卡(ifconfig),或者单网卡,多ip,绑定到0.0.0.0的时候,收发包是怎样的情况?
      • 理解tcp和udp的区别
        • 拆开ip包头,tcp和udp包头,以及做checksum校验的拟包头,理解协议包是如何被Stack的。
          • 话说最早我想当然认为tcp包头里面才是ip包包头,然而事实上这个鸡蛋黄是上层的包头在下层协议包的包体里。不知道是否有同类人有过这种直觉的错觉。
      • windows版本:WinDump - Home
    • nc命令用来探测主机之间的tcp端口或者udp端口是否联通。
    • netstat查看tcp和udp连接情况。
    • iptable的配置,配置tcp、udp端口啥的都要用到。
    • iptraf: 实时网络监控,比如监控某个网卡上的Ip包流量。
      • 估算吞吐量,延迟等。
  • 数据库操作。命令行下,操作mysql和sqlite是常见的事情,这个基本就是常规的sql操作语句。
    • 将数据库dump出SQL文本格式,到别的机子上导入什么的。。
      • 以前有个人打开了这么一份文本,惊讶说:“哇,谁写的SQL创建代码,这么整洁,连注释啥的都很规整...”
    • 理解关系型数据库的范式,事务,一致性。
      • 理解B+树、Block。
      • 基于行存储Block的数据库,基于列存储Block的数据库,各自的优缺点
        • 局部性原理,行Block存储是行记录读写偏好,列的则是部分字段读写偏好
          • trad off,没有最好,只有最合适
    • NoSQL的也操作一些。
      • Map-Reduce,这都已经烂大街了,实际上我不认为会操作和使用NoSQL的数据库就可以,熟练操作一组API本身只需要多次练习就可以达到,不具有门槛。理解系统背后的设计优点,以及其所不能解决的问题才够(有很多领域,都存在不可能三角
        • 以思考”原子“的组件为线,朝着做减法的思维去思考,而不要一味的加功能,堆积木。
      • Apache系列。。
        • 不过积木长什么样子,还是要看看,但并非高大上就神马。
  • 版本管理等
    • svn、git,这个本来就要会的,只是很多人用图形界面的工具多了后,可能命令行使用并不熟悉,但是在Linux shell下,这也算基本技能。
      • 理解“围绕数据结构设计接口”,git是关于commit的链表的数据结构的一组api。
  • 元数据分布式同步,ZoopKeeper
  • 其他选择
    • 更现代的shell:fish
    • 更好的http请求:httpie
  • windows上的使用
    • cygwin太大了,可以选用下面的组合,这样在windows的命令行下也可以使用上述大部分命令行。还是有很多便利的。
  • 手册类:the art of command line,BUT,并不是所有命令在所有环境下都能用。

2016年5月26日星期四

明日的世界(一)

斯雨翻出了几张刚毕业在厦门的照片,我心荡漾。远去的那片海,是怎样的一种意义呢。过去的照片都曾经是未来,当我不断制造时间残余,拼接成一片片昨日的世界,我也在不断用想象中的PlanA和PlanB去突破时间之矢,创造出一缕缕明日的世界。昨日的美好,正出自于今日之进取和勇敢,才有明日之昨日。



2016年2月22日星期一

昨日的世界(八)

404 NOT Found

在程序的世界里,有一大类问题都可以划分到404 NOT Found的类别里,一大票问题都可以归结于查表找不到

构造函数

class Dispatch{
  Dispatch(int i){}
};
Dispatch a;// Compile Error
最早学C++的时候,总是被这个问题搞的莫名奇妙,原来C++编译器会给一个class偷偷添加默认构造函数
但是,如果用户自己定义了一个,则编译器不再提供默认构造函数。此时上述代码要通过的话需要我们
自己给Dispatch添加一个无参数构造函数,方可通过。
class Dispatch{
  Dispatch(){}
  Dispatch(int i){}
};
void DoSomething(){
  Dispatch a;// Compile Error
}
我们可以说
404 NOT Found:您调用的构造函数不存在
  • 添加explicit关键字的单参数构造函数,刻意让想要隐式转换并调用构造函数的时候,404 NOT Found…:
    see:what does the explicit keyword in c mean
  • 至于当时为什么我会在没有无参构造函数的时候写Dispatch a;这样的语句,应该是以为所有的变量都可以这样声明吧。可惜C++里这里的行为就是在栈上分配了内存,调用了无参构造函数。
  • 默认构造函数无参构造函数是两个概念。默认行为往往是坑,一旦使用方的需求和默认行为不匹配,而使用者又对默认行为不清楚,就会出现各种诡异的问题。
    see:Do the parentheses after the type name make a difference with new?
  • 比如说当使用STL的时候,class的拷贝构造函数、赋值操作符,小于比较操作符等都在模板算法里被使用,一个学习STL的新手一定会在不了解模板的duck type interface原理时被这种默认行为坑到。
    see:What is The Rule of Three?
  • C++03里构造函数不能调用别的构造函数,因为class还没构造完呢。。C++11添加了语法糖,支持了…

数组名和指针

C的数组和指针的区别在于,数组名只是数组起始地址的别名,编译器编译后就被替换成了数组的首地址,而一个指向数组的指针则是一个变量,是运行期行为。下面的代码:
char array_place[100] = "don't panic";
char* ptr_place = "don't panic";

int main()
{
    char a = array_place[7];
    char b = ptr_place[7];

    return 0;
}
编译后的汇编是
    char a = array_place[7];

0041137E  mov  al,byte ptr [_array_place+7 (417007h)]
00411383  mov  byte ptr [a],al

    char b = ptr_place[7];

00411386  mov  eax,dword ptr [_ptr_place (417064h)]
0041138B  mov  cl,byte ptr [eax+7]
0041138E  mov  byte ptr [b],cl
所以,而一个函数的数组参数
void foo(char arr_arg[], char* ptr_arg)
{
    char a = arr_arg[7];
    char b = ptr_arg[7];
}
编译后是:
char a = arr_arg[7];

00412DCE  mov  eax,dword ptr [arr_arg]
00412DD1  mov  cl,byte ptr [eax+7]
00412DD4  mov  byte ptr [a],cl

    char b = ptr_arg[7];

00412DD7  mov  eax,dword ptr [ptr_arg]
00412DDA  mov  cl,byte ptr [eax+7]
00412DDD  mov  byte ptr [b],cl
则是一模一样的,这是因为编译的时候,函数并没有被调用,所以编译器并不知道arr_arg的实际地址是什么,所以编译器就只能把它向指针一样处理。
这部分内容源自:are pointer and arrays equivalent in c?
我们可以说
404 NOT Found:函数编译时,数组的实际地址找不到,请看:https://lkml.org/lkml/2015/9/3/428,“because array arguments in C don't
actually exist”

野指针

一个class的指针成员变量,如果未被初始化,则是一个野指针,它不是NULL。
于是在运行的时候,它指向的内存(并不是有效的该类数据)被解码成这个类的数据,此时实际上是乱码的。
这样的乱码数据运行下去,就会有运行期的未定义行为
我们可以说
404 NOT Found:野指针,指针指向的数据并不是有效的对象数据,您要的数据不存在
又:函数里返回局部栈上变量的引用或者指针,函数调用完的时候,函数的Stack被ret掉,局部变量的引用或指针就指向了已经被ret了的内存,
在后续的stack变来边去的时候,那块内存的数据早就不是原来的了。
404 NOT Found:您指向的栈地址的数据早就不是当初那个东西了...
此处有人还长篇大论:Can a local variable’s memory be accessed outside its scope?

未定义符号

C/C++ 编译的时候,出现未定义符号,原因可能是这个符号所在的头文件并没有被包含。
为了找到它,可能的行为包括
1. 指定Include查找目录
2. 添加必要的头文件
我们可以说
404 NOT Found:符号所定义的文件找不到

未解决的符号

C/C++链接的时候,出现未解决的符号,原因可能是这个符号的定义虽然在编译的时候找到了,但是链接的时候没发现它的实现。
为了找到它,可能的行为包括
0. 比如说函数在头文件里实现了,在.c或者.cpp里却没有实现,那么实现它
1. 添加Lib查找的目录
2. 添加需要连接的Lib文件
我们可以说
404 NOT Found:符号没有被实现或者找不到obj,或者找不到库(一堆obj的合集)
又:它的反面是同一个符号有多个实现的版本,比如多个同时链接了多个不同实现的C运行时。此时不是找不到,而是找到了多个不知道用哪个,解决也很简单,明确指定要用的是哪个。
又:还有一种是同时只有多个弱符号,没有一个强符号,则不知道用哪个。
这两种情况,可以说
404 NOT Found: 有多个版本,找不到一个最强的来用

模板的具现化

#define type_zero 0
#define type_one 1
#define type_two 2

template <int type>
struct trait;          //Declare

template <>
struct trait<type_zero>{ //A
  enum {value=0}; 
};

template <>
struct trait<type_one>{ //B
  enum {value=1};
};

template <>
struct trait<type_two>{ //B
  enum {value=2};
}

void DoSomething(){
  std::cout<<trait<2>::value<<std::endl;//(1) 
  std::cout<<trait<3>::value<<std::endl;//(2),Compile Error
}
对于(1):
1. 编译器尝试使用A版本具现化,不匹配,错误A,先不出错,下一步;
2. 编译器尝试使用B版本具现化,不匹配,错误B,先不出错,下一步;
3. 编译器尝试使用C版本具现化,匹配,忽略错误A和错误B
对于(2):
1. 编译器尝试使用A版本具现化,不匹配,错误A,先不出错,下一步;
2. 编译器尝试使用B版本具现化,不匹配,错误B,先不出错,下一步;
2. 编译器尝试使用C版本具现化,不匹配,错误C,抛出编译错误。
如果编译器在错误A的时候就直接编译错误,那就没什么好说了,但编译器会尝试找重载的模板尝试具现化,直到所有的尝试都失败时才认为是真的失败了。
我们可以说
404 NOT Found:找不到可以具现化的模板
在尝试具现化的过程中遇到失败的情况先不抛出的特性也被起了个名字:
SFINAE: "Substitution Failure Is Not An Error"
再来一个例子:
struct example
{
    template <typename T>
    static void pass_test(typename T::inner_type); // A

    template <typename T>
    static void pass_test(T); // B

    template <typename T>
    static void fail_test(typename T::inner_type); // C
};

int main()
{
    // enumerates all the possible functions to call: A and B
    // tries A, fails with error; error withheld to try others
    // tries B, works without error; previous error ignored
    example::pass_test(5);

    // enumerates all the possible functions to call: C
    // tries C, fails with error; error withheld to try others
    // no other functions to try, call failed: emit error
    example::fail_test(5);
}
see: Substitution Failure Is Not An Error

竞态条件

class Dispatch{

public:
  void AddObserver(Observerable* o){
      AutoSpinLock lock(m_lock);
      ...  
  }
  void RemoveObserver(Observerable* o){
      AutoSpinLock lock(m_lock);
      ...
  }
  void NotifyObservers(){
     AutoSpinLock lock(m_lock);
     Observers::iterator it=m_observers.begin();
     while(it!=m_observers.end()){
       Observer* o = *it;
       o.DoSomething();
       ++it;
     }
  }
private:
  typedef std::vector<Observerable*> Observers;   
  SpinLock m_lock;
  Observers m_observers;
}
发生的情况
1. 线程A:NotifyObservers。
2. 线程B:Observer要delete之前,调用Remove,成功,然后就析构自己。
3. 此时A线程的DoSomething还在过程中,崩溃。
此时我们可以说
404 NOT Found:您要通知的对象已被析构...
解决的办法就是用引用计数智能指针+弱引用智能指针
class Dispatch{

public:
  void AddObserver(weak_ptr<Observerable> o){
      AutoSpinLock lock(m_lock);
      ...  
  }
  void RemoveObserver(weak_ptr<Observerable> o){
      AutoSpinLock lock(m_lock);
      ...
  }
  void NotifyObservers(){
     AutoSpinLock lock(m_lock);
     Observers::iterator it=m_observers.begin();
     while(it!=m_observers.end()){
       shared_ptr<Observer> obj(it->lock());// 如果存活,增持,避免被析构
       if(obj){
         o.DoSomething();
         ++it;
       }else{
         it = observers.erase(it);
       }
     }
  }
private:
  typedef std::vector<std::weak_ptr<Observerable>> Observers;   
  SpinLock m_lock;
  Observers m_observers;
}
see:Linux多线程服务端编程
如果不用智能指针,不带引用计数,那么,可以在NotifyObservers的时候,不立刻执行DoSomething,而是投递到目标线程去执行。假设Observer的工作线程是B,Dispatch在线程A,则NotifyObservers可以投递到线程B,在线程B才做真正的遍历触发,则保证Observer的Add、Remove、Find都在线程B,从而避免线程问题。

切换脚本语言

一个同学写了一段时间的lua代码
for i,v in pairs(nodes) do
  --//do something
end
有一天切换成到Python环境下写代码,同样遍历字典
for i,v in pairs(nodes):
  ## do something
404 NOT Found:paris是个什么鬼...
去掉
for i,v in nodes:
  ## do something
404 NOT Found:没有迭代器...
好吧:
for i,v in nodes.items():
  ## do something

声明的时候还没有定义

C的例子

typedef struct{
  ...
  list_node *next; // error
} list_node;
解决:
typedef struct list_node list_node;
struct list_node{
  ...
  list_node *next; 
};
ps,C和C++的tag

C++的例子

典型的C++ 前置声明用来做PIMPL (Private Implementation) 惯用法,避免循环include头文件
class A;
class B;
class C{
public:
  A* GetA();
  B* GetB();
}

Lua的例子

function fac()
  print(fac())--//error, 递归定义,此时fac还不存在
end
解决:
local fac
function fac()
  print(fac())
end
语法糖:
local function fac()
  print(fac())
end

scheme的例子

Y Combinator:http://mvanier.livejournal.com/2897.html
Y Combinator想要使用lambda搞出递归,最后办法就是把代码拷贝一份传递进去..
我们可以说
404 NOT Found:定义还没完成呢,想要用它的话,加个中介待定系数法吧
作为反例,switch-case的case里不能直接定义变量,因为case只是个可以goto的label,如果没有被goto到,这样的变量就只定义而没有初始化…,see:Why can’t variables be declared in a switch statement?

版本

  • A:哥,测一个
  • B:好的,马上布下新服务,
  • 10秒过去..
  • A:哥,协议不对啊,好像你还是旧的协议
  • B:咦,怎么上传的还是旧的
404 NOT Found: 需要的协议版本不对,测试部署最好也做版本号区分

心跳

https://en.wikipedia.org/wiki/Heartbeat_(computing)
https://en.wikipedia.org/wiki/Keepalive
无论是Tcp还是Udp,连接中都会使用心跳包,每隔n秒发送一次心跳包给服务器,每隔m秒判断是否有回包。如果没有回包,则判断连接断开。在某些情况下,允许中间断开一段时间,这样会在稍后重试心跳。程序如下:
1. 开始心跳,如果心跳失败,每个n秒重试,
2. 连续重试m次如果失败,就会等待一个大的时间x秒,x秒后重新开始1
利用心跳,可以做到:
- 在客户端,如果心跳失败,说明要么网络出问题,要么服务器出问题,则客户端可以连不上服务器,在P2P网络里则该客户端甚至无法与其他节点做打洞或者反连。
- 在服务端,根据心跳可以判断在线节点的个数,如果出现大面积不在线,则要么网络出问题,要么客户端出现某种未知异常。
404 NOT Found: 心跳失败,既死亡

分支闭合

分支似乎不用说。编程里最基础的便是if else。然而我们流行一句话: 很多bug最后定位到的时候往往发现是一个很2的问题。
为什么会这样呢?根据我的经验,我觉得还是因为if else没做到处处闭合(封闭性)。编程语言并不强调有if就要有else,这是语法级别的。而且我们很多时候为了避免代码嵌套过深,采用卫语句的方式提前返回。当情况复杂时,就容易漏掉某个分支。
所以,编程还是回归到了最最基本的逻辑,在语义上要强调分支的闭合,有if就要有else,即使你不写出来。而且工程健壮的程序,就是在处理各种错误分支,好的程序对所有的错误分支都是有意识并且有处理的,缺一不可。所谓测试,核心其实就是对分支闭合的测试,这也是体现工程师逻辑是否严密的地方。
404 NOT Found: 这个分支你没处理

IP and Port

Ip和端口构成了一个EndPoint。
网络的世界里,两个EndPoint,A和B之间唯一定位彼此的是靠Ip和Port。可是,中间是各种墙(NAT,Router),只要任何一个地方把src和dest的Ip,Port封掉,它们之间就无法通信。网络上的可靠通信,基本原理是,三次握手打开连接,四次挥手关闭连接。放开关闭不说,单说握手。
1. A send syn to B
2. B send ack to A
3. A send ackack to B
三个步骤中的syn、ack、ackack任何一个包发送失败,都会导致该连接不能建立。而一般来说,如何把syn包从A发给B就是一个难题.
首先要对NAT分类:
see:wiki-NAT
  1. Full-cone NAT, also known as one-to-one NAT.
    • Once an internal address (iAddr:iPort) is mapped to an external address (eAddr:ePort), any packets from iAddr:iPort are sent through eAddr:ePort.
    • Any external host can send packets to iAddr:iPort by sending packets to eAddr:ePort.
  2. (Address)-restricted-cone NAT.
    • Once an internal address (iAddr:iPort) is mapped to an external address (eAddr:ePort), any packets from iAddr:iPort are sent through eAddr:ePort.
    • An external host (hAddr:any) can send packets to iAddr:iPort by sending packets to eAddr:ePort only if iAddr:iPort has previously sent a packet to hAddr:any. “Any” means the port number doesn’t matter.
  3. Port-restricted cone NAT.
    • Like an address restricted cone NAT, but the restriction includes port numbers.
    • Once an internal address (iAddr:iPort) is mapped to an external address (eAddr:ePort), any packets from iAddr:iPort are sent through eAddr:ePort.
    • An external host (hAddr:hPort) can send packets to iAddr:iPort by sending packets to eAddr:ePort only if iAddr:iPort has previously sent a packet to hAddr:hPort.
  4. Symmetric NAT
    • Each request from the same internal IP address and port to a specific destination IP address and port is mapped to a unique external source IP address and port; if the same internal host sends a packet even with the same source address and port but to a different destination, a different mapping is used.
    • Only an external host that receives a packet from an internal host can send a packet back.
根据情况,1,2,3都可以利用规则把完成语义上的syn的动作,如果两端都是Symmetric NAT就没救了。在NAT后面的,要想连接,就得trick NAT的映射机制。
其次对A和B分类,其中,2和3是同一种类型:
1. A和B都在外网
2. A在外网,B在内网(NAT后面)
3. A在内网(NAT后面),B在外网
4. A和B都在内网(NAT后面)
根据情况,1可以直连,2和3可以反连,4可以通过打洞方式,完成syn动作。如果把1,2,3,4做merge,即实现了一个混合多路发syn的connector。
再次,对使用的协议做分类:
1. 使用Tcp
2. 使用Udp
根据情况,1可以做直连和反连,打洞相对困难,但也不是不可以做,用Tcp不用自己做错误处理、拥赛控制。2可以做直连、反连、打洞,但是用Udp需要自己重新实现各种错误处理、拥赛控制以实现可靠连接。
最后,对Ip和端口的恶劣场景做分类
1. 频繁换IP,双网卡
2. 某些端口被NAT和Router封杀
对于这两点,只能通过:
1. 重新绑定Ip和端口
2. 随机化端口,但如果用户有Upnp的情况下,随机的端口可能未必能用。
以上没有说明的一点是A和B在应用层是用什么互相标识的。举例常见的Id情况:直接用Ip,用域名,P2P网络里用PeerId。无论是直连、反连还是打洞、本质上都是在做Id到可达EndPoint之间的转换,在域名系统里,这就是DNS的职责,常见的网络污染就发生在DNS这个环节。
404 NOT Found: Ip和端口不对。

Function

很多时候,我们都在做翻译工作。

跨语言函数调用

比如说跨语言的函数调用,就是在不同语言之间通过在某一个语言端的中介的API做函数调用翻译工作。
  • JNI,通过Java Native Interface,我们的本地代码直接调用JVM(Java Virtual Machine)的指针和Java代码之间跨语言交互。实际上对于JNI代码来说,并不是和Java代码交互,而是和JNIEnv的API交互。
  • Lua,通过Lua的C函数接口,我们可以给Lua注册一堆的C接口。我们的本地代码直接调用的lua_State* 和Lua代码之间跨语言交互。实际上对于Lua的C接口函数来说,并不是和Lua代码交互,而是在lua_State的API交互。
  • P/Inovke,通过.NET P/Invoke,我们可以在.NET里调用C的动态链接库。本地代码只要是标准C导出接口即可。
对比之下,区别在于谁来做
- Lua是固定了接口,参数传递基于lua_State的栈去传递。lua_State做查找工作,程序员手工做了翻译以及参数的出栈、入栈。
- JNI是让C++接口去声明参数类型信息。编译器根据用户在Java和C端声明,做了查找工作。
- P/Invoke是让.NET自己去声明参数类型信息。编译器根据用户的声明做了自动查找翻译工作。
这三者做做了两件事情
1. 约定参数类型映射。
- JNI是在C++端声明Java的参数类型所映射的本地类型。
- P/Invoke是在.NET端声明本地类型到.NET类型的映射
- Lua是在C/C++端让程序员直接隐式假设lua_State栈上每个槽应该放的是什么类型的数据。
2. 约定参数入栈顺序
- JNI和P/Invoke遵循C/C++的调用约定
- Lua则规定了参数的出栈入栈规则
逻辑上跨语言调用做的是在语言之间规定怎样调用对方的函数的规则,是属于函数调用的范畴。
我们考虑每一个函数都有输入参数和返回值。函数可以有单参数和多参数,而多参数可以通过柯里化变成单参数。
所以,函数问题可以简化为单参数单返回值函数的问题。可以用符号表示 r=f(a)
这假设了f总是顺序执行。我们可以通过Continuation的概念把函数表示为f(a,c)的形式,
f接收a,同步或者异步执行完毕后把结果传递给Continuation,也就是c去处理,c的一个例子是
function(r) print(r) end

网络协议

任何和约定相关的,都可以称之为协议。和协议伴生的一般就是协议包,和协议包伴生的一般就是协议包的编码解码。可以看下典型的网络协议。
  • TCP协议和TCP协议包;
  • Udp协议和Udp协议包;
  • 基于TCP的Http协议和Http协议包。
  • 基于Udp的P2P协议和P2P协议包;
一个网络协议是建立在发送一个包a,返回一个包r这样的基本操作之上的。比如说TCP握手:
client a         client b
send syn   ----> recv syn
recv ack   <---- send ack
send ackack----> recv ackack     
我们可以定义函数
send(request,function(response) end)
则TCP握手可以表示为
--client a                       
send(syn,function(ack) 
  send(ack,ignoreresponse)
  send(data,function(dataack)
     send(data,function(dataack)
      ...
     end)  
  end)
end)

--client b
send(nothing,function(syn)
  send(ack,function(ackack)
     send(data,function(dataack)
        ...
     end) 
  end)
end)
Udp协议和TCP协议的区别在于,TCP处理了每个函数调用的异常,例如超时没有返回,我们可以调整send函数原型
send(request,function(response) 
     --// 返回了期待的值 
  end,function(timeout) 
     --// 没收到期待的值,某种重发机制
  end,function(error)
     --// 彻底超时了,失败
  end)
所以,网络上发包和收包可以规约为函数调用。
ps:一个网络协议包可以拆开成包头和包体,而一条汇编指令、一条.NET的IL指令,一条JVM的bytecode,一个Lua的指令,也同样可以有进一步拆开成opcode+register。可以把一个网络协议包看成一条网络汇编指令
网络协议包的包头一般包含有packagetype,可以当作是opcode,包体则是register里的数据。
ps:编程语言的指令,都喜欢把int拆开去使用,前几个位表示什么,接着几个位表示什么。网络协议包也类似。我觉的对bit的操作是编程世界里的最低粒度的封装抽象

RPC

基于网络协议,就可以实现网络通信。在应用层,我们更喜欢把网络通信当成是函数调用了。于是有了RPC.
无论是Apache Thrift还是Google Protocol Buffer还是.NET WCF,都用某种代码生成器去生成网络通信封装成函数的事情。

函数、对象和闭包

无论是函数还是对象,都是一堆数据。不能闭包的语言里,函数只有代码数据。可以闭包的语言里,闭包了的函数除了代码数据还有被捕获的变量的数据(所谓upvalue,up是是upstream,也就是上游,可见只能捕获自己上游作用域的变量)。
在命令式语言里,对象是一个状态机,状态的维护在并发的时候遇到了data race的问题。在纯函数式语言里,数据是不可变的,无副作用的,并发的时候就消除了data race。在一个命令式语言里的函数式特性里还是会遇到data race。

重入

references

Burn Down chart

S型的燃尽图

在一次milestone开发过程中,开发者会持续编辑issue列表,每个issue都有自己的生命周期。燃尽图预期这些issues会被线性的消灭掉,所以从第一天直接到最后一天画个直线表示预期进度变化,然而实际开发会遇到各种困难,所以实际的进度变化曲线往往不是线性变化的,下面这篇文章给出了S型燃尽图:
https://sandofsky.com/blog/the-s-curve.html

三进制

计算机是基于二进制的,有一句经典的台词是:这个世界上有两种人,一种是懂10进制的,另一种是懂10进制的。但是在开发中,我们常常要接触三进制。程序开发中有一个Keep it simple, stupid的所谓KISS原则,就是说我们写程序,在没有必要的情况下,不要增加不必要的抽象层。例如很多初学者在刚接触了设计模式之后,就会在很简单的程序上不必要或者不正确的使用各种模式去写代码,但实际上那种程度的代码只需要把面向过程代码写好,做好函数这个级别的抽象即可。函数简洁但并不就简单,和通常的初学者相反,有经验的程序员更愿意用简洁的代码解决问题,而不会为了用上一堆所谓特性把代码写成花拳绣腿。一个函数需要考虑入口条件,出口条件,是否可重入,是否线程安全,所依赖的状态变化…等等。所以,有时如果你不放弃装饰,你就得放弃本质。
但是,程序从开发的第一天开始就被持续迭代。有一句话说:程序从被写下的第一天开始就开始腐烂的过程,这是说程序会因为需求的不断变化而需要不断的被重构,一开始几行的代码可能会膨胀,再压缩,再膨胀,再压缩。不同地方的代码会出现重复,各种各样的重复。于是,光有KISS是不够的,我们需要另外一个东西:Do Not Repeat Yourself,也就是所谓的DRY原则。怎样才能不自我重复呢?这就是所谓的三进制原则,同样一件事,重复做三次,就可以考虑批处理,计算机是最擅长做批量处理的事情,例如for循环,递归等。批处理的前提是结构的一致性,结构的一致为将他们装到容器里创造了可能。所以,为了批处理,我们常常需要在结构上做抽象。一致性可能是数据上的,也可能是行为上的,根据程序即数据的原理,数据的一致性可以用行为的一致性表示,所以我们可以只对行为的一致性考虑。在面向过程语言里,可能就是在函数这个级别考虑,在面向对象语言里,则更多可以在接口层体现。把共同的结构装到容器里的过程,就是组合的过程。
好了,三进制大概说到这里。

我们需要专职的TA么?

从我一开始进入程序员这个行业开始,我就也会被软件开发中的人员配置问题困扰。一个团队,是否要有专职的项目经理,美工,测试,产品设计,以及并不讨人喜欢的行政,人力资源?从程序员的角度来说,属于技术偏好型,能用技术解决的,绝不愿意通过增加一个人去解决,能用工具解决的,一定批量处理之。先进技术和工具,常常在效率上是数量级的压倒性优势。
回到小题,通过几年的过程,我想角色上很多时候这些角色都有其必要性,但是,一个角色要有多个人还是一个人甚至直接由工具取代,则是可以因团队而不同。怎样取舍和适应?我认为关键在于:是否有效率的解决问题,例如开发和测试,如果开发和测试两个人不能很好的协作,共同有效率解决问题,而一个两者皆做的更有效率,那么明显后者更好,反之则选择前者。
因此,角色分离,但人员可以合一。也就是人剑合一。

持续迭代

前面说燃尽图,软件开发,常常是一个长期过程。不是一锤子买卖,一个基础类库开发加一个上层产品开发就可能两年。但市场常常不等人,因此我们要紧凑的过程和质量,那么就需要持续迭代。持续迭代体现在一个milestone之内的每一天之间,需要每天跟踪昨天,今天,明天的进度,解决的issue,新产生的issue,代码的提交,测试的通过率,发布的版本,用户的反馈等等。持续迭代同时体现在milestone和milestone之间,粒度会更大一些。
一个理解持续迭代的地方是解bug,任何bug,我们都需要首先尝试去重现它,做二分排除定位,做单元测试刷豆子,搜集证据链,一步步掐头去尾缩小范围,最后解决。这也需要持续迭代。
持续迭代不仅在节奏上保持紧凑,而且把过程渐进展开,我们都知道软件开发的敌人是黑盒子,不能预期的时间和进度,所以持续迭代的过程,我们通过燃尽图,issue管理,bug跟踪,反复把过程dump出来,让进度可视化,促进软件开发的正面化学反应和收敛。
while(rest>e){ step(); }

充分必要

软件开发,涉及到很多需要推理的地方,例如每天的解bug,这里需要逻辑,也就需要充分必要。一个论点,它的论据是否是充分必要的,就是体现逻辑的地方。
其他地方,例如做案例分析,我们训练思辨和分析,我想也应当是基于逻辑的。例如做需求分析,也是应该以是否符合逻辑为核心,如果连自己都说服不了,泛泛而谈的形式化需求分析,多半是需要被重新做的。所以,原则应当是,写完自己读一遍,根据是否充分必要去复审,也许需要补充数据,真实的数据很重要,有数据就可以拿数据说话,一个断言在有数据支撑的情况下,更可能有效。但使用数据要避免单样本统计学家式的武断,同时也要不失管中窥豹的洞察力,这大概需要反复锻炼,反复成功或者失败。

工具

程序员是带工具属性的,工程上,每个小事情都可能有相关的工具可以利用,如果没有,我们就造工具。同样的管理源代码,我们有svn,git,当我们赤手空拳,我们合并别人的代码,可能会先临时拷贝自己新改动的代码到某个临时文件夹S,然后把别人的代码覆盖进来,完了再把S和其diff,再修改。用git,这个过程就是git stash,git pull,git stash pop,所以有时候只要想一下一个操作过程当自己没有工具是是怎么做的,可能工具也大概就是这么做的,只是变成了更有效率的命令。工具也是符合逻辑的。
两个团队A和B。A没做事,B不用工具做事,B比A强;A不用工具做事,B用工具做事,期待的是B比A更有效率的解决问题,否则B并不就比A强;如果两队都做事也都有效率使用工具,则可以开始比内容。所以把那些提高效率的工具都用到习以为常,瓶颈不卡在那些上面,我们就进入比拼真正的最终目标上。反复的训练和使用,可以达到这点。

重试

一个任务,耗时估计的靠谱度跟做过这件事的次数有关,一般来说做过两三次额估计就靠谱点。这也是有经验公式可用。如果它是一个普遍原理,我们可以利用它。从概率上来说,一个事情一次的成功率为a,那么,多次之后的成功率是可计算的,手机打字就不展开了。可以验证,重试可以增加成功率。所以,很多事第一次做,可以预估会有失败和错误,所以失败和犯错时冷静的保存数据,证据,记录,探测的这些点,都可以用来在重试时做可能的规避。不过很多人只尝试了一次,这就回到了经典论述:写100个helloworld不如对一个程序迭代100次。当然,我们要考虑到人是复杂的,喜新厌旧是常态,并不能一概而论。因为,每个人都是独立的个体,选择的自由正是这种平均值。重试要注意两次之间是否收敛,如果不收敛,则重试只会导致队列堆积,甚至引起雪崩,此时要检查更深的问题。

基本和到位

我常常做过一件事以后,发现很多事情并不需要各种奇葩的创新点子之类。做着做着就会看到,做好一件事最需要的并不是创新什么的,而是:概念界定之后的基本功。说概念界定,是因为很多人对自己的职业角色定位不清,就会导致做的事情不能抓住基本点。而一旦界定了角色边界,明确了基本点,那么工作就变成如何完美的把那些基本点做到位上面。例如,保持节奏,从不拖延,持续迭代,细节的完备,以及坚持不越界的做不应该自己做(你得让该做这件事的人去做,有原则对彼此都是一种轻松)。把基本功的细节做到位,就是一种职业素养,和专业主义。我想,这是重要的。我最怕角色不清,要么认为你应该什么都做,要么认为你什么都不要做,角色不清,系统的调度会混乱而没有效率。一个人可以做不同的事情,但最好在角色的语义上区分之。
因此,我也觉得很多事,其实能做到这些的人自己也都可以做好,未必得找别人。这和我经历过到处找控件来组装,到掌握组合控件的基本原理后不认为有魔法控件一样,省去了拿来主义。

提前量和惰性求值

当我们预估,我们会没有充裕的时间做完整的过程,我们可以打时间差。我们说对一个系统做性能优化,不外乎在时间和空间上做优化。如果一个系统或者一个过程要到达预期质量所要消耗的最低时间已经不可再压缩,并且预估到时间整体上将呈现下滑趋势,那么就应该在一开始时间还足够充裕的情况下打提前量,通过一开始的紧凑有序的节奏把大部分脚手架都搭建起来,那么越到后期越会从容和有条不紊,反之则会手忙脚乱。凡事预则立,不预则废。
相反的做法是,对事情做惰性求值,直到我们真正需要一个事情的结果时,才会去做这件事,惰性求值有时候能最大限度减少当前要做的事情。但是,惰性求值相当于一次异步投递任务到队列的过程,队列里等待被做的事情会堆积,当需要在限定时间内求某个事情的结果时,队列可能已经雪崩了。所以有队列的地方就要防雪崩,队列不能过长(根据优先级丢弃),保证你的队列可以在最低粒度时间片内被有序求值。如果你不想你必须要做的事情被丢弃,那么那些事情可以通过打时间差提前做掉,达到超出预期的效果。再说异步任务,异步任务都可能由于各种原因而不会有返回值,所以有异步就要有定时器,做超时监听,一旦超时,做对应的处理。

版本号

无论是10进制,还是10进制,亦或是点分10进制。它们都是版本号。我们的人生就是在年轮的版本之间迭代。认真对待每个版本,麻雀虽小,五脏俱全。每个版本开始,明确本次版本的目标,屏蔽其他,只指目标,当其达成,测试通过,打包,发布,写上:
版本 0.0.3
- 修订了xxx
- 移除了yyy
- 改进了zzz
- 新增了abc
版本和版本之间,贵在持续,节奏均匀。版本的发布日志,就是一个diff。两个集合,A和B的对称差,就是发版的日志。这让我想起,从写程序到现在,在各种场合写过两个数组,区间列表等的diff。很多时候,我们都在做diff,我常常从程序处理的过程得到一些概念,借以类比或者理解生活的事情,然而并不将其泛化,人太容易只看到类比事物之间共性的地方,而其实事物的复杂更在于那些不同的地方。手里有一把锤子,就到处找钉子的事情,永远都有。

拥塞控制

经典的生产者,消费者情景是这样的:
- 过程中会不断产生任务,加入任务队列,这是生产者
- 处理器会定时刷帧,每一帧都会检查当前的窗口,在窗口内的任务就会被执行
- 每个任务执行完都会更新系统的资源状态,这些状态用来更新窗口,超时的任务可能没执行完需要重新投递到队列里。
这样的过程,不断执行的任务,获取或者释放系统资源,而系统资源变化可以反馈到调度器的允许执行窗口,每一帧则从队列里取出窗口内的任务执行。
我们说,这是一个拥塞控制。不做拥塞控制的情况下,在执行的任务可能太少而没有充分利用系统资源,或者在执行的任务太多而把系统资源竞争到卡死,无论怎样,系统的资源利用率最饱和和任务的执行最多,是一个优化目标。所以,一个健壮的系统,有队列的情况下,要做拥塞控制。
拥赛控制的核心法则是:多还少补,在慢启动、拥赛避免、快速恢复3个阶段来回切换。

分段燃尽

从瀑布到敏捷,有的人说我是classical的,严格的瀑布有序经典;有的人说,我是删繁就简的,小步敏捷迭代,蚂蚁搬家。
就像我认为角色需要分开,人员可以合一。无论是学理上还是实践上,过程的不同阶段是存在的,但是具体实施上可能会压缩和合并。
Plan, Build,Test,基本的控制点在于这三者,侧重点各不相同。全局一次,还是反复多次,只是调度策略的不同。
假设过程是一条插值曲线,那么分段插值曲线比全局插值曲线有更好的局部性,学过数值分析的应该都知道全局插值曲线在控制点多了之后,插值多项式的次数高,会有振荡现象,就是由于局部性不好。

State of Machine

许多东西都可以看成是状态机,我们只看有限状态机的维基:
Finite-state_machine
A finite-state machine (FSM) or finite-state automaton (plural: automata), or simply a state machine, is a mathematical model of computation used to design both computer programs and sequential logic circuits. It is conceived as an abstract machine that can be in one of a finite number of states. The machine is in only one state at a time; the state it is in at any given time is called the current state. It can change from one state to another when initiated by a triggering event or condition; this is called a transition. A particular FSM is defined by a list of its states, and the triggering condition for each transition.
在非函数式语言里,一个结构体+一组API,或者一个类的接口,都可以看作是一组操作一个状态机的函数。
在网络编程中,一个发包和一个回包,是最基本的收发操作(Atom Operation)。当我们引入状态机,将连续的收、发包操作组装起来,就构成了某个网络协议,一组有效的收发包要么让这个状态机保持某种状态,要么切换到另一个状态。
最早接触状态机是在写OpenGL的时候,知道了OpenGL是一个全局状态机:
OpenGL是一个状态机
OpenGL是一个状态机,它维持自己的状态,并根据用户调用的函数来改变自己的状态。根据状态的不同,调用同样的函数也可能产生不同的效果。
为什么OpenGL的状态机是全局的呢?
Why does OpenGL be designed as a state machine originally?
The reason for this kind of “state machine” is simple: that’s how the hardware generally works.
Because originally, back in OpenGL 1.0, there were no objects (besides display lists). Yes, not even texture objects. When they decided that this was stupid and that they needed objects, they decided to implement them in a backwards compatible way. Everyone was already using functions that operated on global state. So they just said that by binding an object, you override the global state. Those functions that used to change global state now change the object’s state.
OpenGL已经很久没用来写代码干点什么。但是OpenGL的几个模式却常见。

开关

if( glIsEnabled(GL_BLEND) ) {
     // 当前开启了混合功能
} else {
     // 当前没有开启混合功能
}
  • 照相机或者其他设备,常常需要打开某种开关后,某些操作才有效。
  • VIM编辑器,某些操作需要先打开一些开关后,才有效。

出栈、入栈

glPushMatrix();
  // 里面干什么都不会影响到外面的矩阵世界
glPopMatrix();
  • C函数的调用栈
  • Lua的扁平展开的栈
  • Call/CC说的是Stack在Functional语言里的形式
  • GUI渲染的栈
  • 写代码生成器,嵌套的block栈让事情变的简单:
.Line("int test()")
.PushLine("{")
    .Code()
.PopLine("}");

模式

glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION); 
glLoadIdentity();
gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0);
glMatrixMode(GL_MODELVIEW);
我记得小时候,去别人家看录像机,大人们操作几次之后,一帮小孩把大人们按哪个键开关、播放、切换什么的都会记得清晰。后面好几年,接触电子设备少,偶尔来一个相机都不知道那么多按钮是干嘛用的,为什么一会儿这个按钮是干这个用的,一会儿又是那个效果。绕了一圈,后面学了VIM,VIM的每个快捷键的是什么效果会取决于编辑器当前处于什么模式,例如编辑模式、命令模式、可视模式等等。后面我再次用相机的时候,一下就想明白了,电子设备的按钮不可能无限多个,所以引入“模式”即可解决。某个按钮用来控制当前的模式,切换到不同模式后,同一个按钮的作用可以不同。OpenGL的glMatrixMode这个套路在很多场景下都一再的被使用。
写一个代码生成器,先给一组最简单的渲染操作:
private static int indent=0;
public static StringBuilder Push(this StringBuilder sb) {
    indent++;
    return sb;
}
public static StringBuilder Pop(this StringBuilder sb) {
    indent--;
    return sb;
}
public static StringBuilder Line(this StringBuilder sb, string text, bool ignoreIndent=false) {
    if (ignoreIndent) {
        sb.AppendLine(text);
    } else {
        var space=new string('\t', indent);
        sb.AppendFormat("{0}{1}\n", space, text);
    }
    return sb;
}
public static StringBuilder Line(this StringBuilder sb) {
    sb.AppendLine();
    return sb;
}
public static StringBuilder FormatLine(this StringBuilder sb, string format, params object[] args) {
    var space=new string('\t', indent);
    var text=string.Format(format, args);
    sb.AppendFormat("{0}{1}\n", space, text);
    return sb;
}
public static StringBuilder PushLine(this StringBuilder sb, string text) {
    return sb.Line(text).Push();
}
public static StringBuilder PopLine(this StringBuilder sb, string text) {
    return sb.Pop().Line(text);
}
public static StringBuilder PopLine(this StringBuilder sb) {
    return sb.Pop().Line();
}
其中Push和Pop正是借用Stack的概念,来简化block块代码的生成。我们可以如下使用:
var sb = new StringBuilder();
sb.Line("#include \"./PackageUtil.h\"")
  .Line("PACKAGE_HANDLE PackageUtil::CreateEmptyPackage( PackageHeader& header )")
  .PushLine("{")
  .Line("int ret = RESULT_FAILED;")
  .Line("PackageInitData initData = {PackageInitDataType_Header,&header};")
  .Line("switch(header.PackageType)")
  .PushLine("{");

foreach (var p in protocols) {
    sb.FormatLine("case XXX_PACKAGE_{0}:",p.Name)
      .Push()
      .FormatLine("return CreatePackage(PackageTrait<XXX_PACKAGE_{0}>::GetClassName(),&initData,&ret);", p.Name.ToUpper())
      .Pop();
}

sb.Line("default:")
  .Push().Line("return NULL;").Pop()
  .PopLine("}")
  .PopLine("}");
然而,这里的foreach打断了代码的链式形式,实际上这样也就够用了,但我们可以再折腾一下,做点无聊的事情:
public class EachBuilder<T> {
    public IEnumerable<T> List;
    public StringBuilder Builder;
    public List<Action<T>> Actions;
}
public static EachBuilder<T> BeginEach<T>(this StringBuilder sb, IEnumerable<T> list) {
    return new EachBuilder<T>(){
        List = list,
        Builder = sb
    };
}
public static EachBuilder<T> Push<T>(this EachBuilder<T> sbe) {
    var action=new Action<T>(t => sbe.Builder.Push());
    sbe.Actions.Add(action);
    return sbe;
}
public static EachBuilder<T> Line<T>(this EachBuilder<T> sbe, string text, bool ignoreIndent=false) {
    var action=new Action<T>(t => sbe.Builder.Line(text, ignoreIndent));
    sbe.Actions.Add(action);
    return sbe;
}
public static EachBuilder<T> Line<T>(this EachBuilder<T> sbe) {
    var action=new Action<T>(t => sbe.Builder.Line());
    sbe.Actions.Add(action);
    return sbe;
}
public static EachBuilder<T> PushLine<T>(this EachBuilder<T> sbe, string text){
    var action=new Action<T>(t => sbe.Builder.PushLine(text));
    sbe.Actions.Add(action);
    return sbe;
}
public static EachBuilder<T> PopLine<T>(this EachBuilder<T> sbe, string text) {
    var action=new Action<T>(t => sbe.Builder.PopLine(text));
    sbe.Actions.Add(action);
    return sbe;
}
public static EachBuilder<T> PopLine<T>(this EachBuilder<T> sbe) {
    var action=new Action<T>(t => sbe.Builder.PopLine());
    sbe.Actions.Add(action);
    return sbe;
}
public static EachBuilder<T> Pop<T>(this EachBuilder<T> sbe) {
    var action=new Action<T>(t => sbe.Builder.Pop());
    sbe.Actions.Add(action);
    return sbe;
}
public static EachBuilder<T> FormatLine<T>(this EachBuilder<T> sbe, string format, Func<T,string> args){
    var action=new Action<T>(t => sbe.Builder.FormatLine(format, args(t)));
    sbe.Actions.Add(action);
    return sbe;
}
public static EachBuilder<T> FormatLine<T>(this EachBuilder<T> sbe, string format, Func<T, string> args1, Func<T, string> args2) {
    var action=new Action<T>(t => sbe.Builder.FormatLine(format, args1(t),args2(t)));
    sbe.Actions.Add(action);
    return sbe;
}
public static EachBuilder<T> FormatLine<T>(this EachBuilder<T> sbe, string format, Func<T, string> args1, Func<T, string> args2, Func<T, string> args3) {
    var action=new Action<T>(t => sbe.Builder.FormatLine(format, args1(t), args2(t), args3(t)));
    sbe.Actions.Add(action);
    return sbe;
}
public static StringBuilder EndEach<T>(this EachBuilder<T> sbe){
    foreach (var item in sbe.List){
        foreach (var action in sbe.Actions){
            action(item);
        }
    }
    return sbe.Builder;
}
我们把StringBuilder通过通过BeginEach绑定一个IEnumearble变成一个EachBuilder,然后为EachBuilder提供一组同之前同语义的操作,这些操作内部只是把要做的事情通过Action的形式收集起来,保持顺序。在EndEach的时候,再一次性对之前绑定的List做操作。这里我希望使用者感觉这些API和之前的版本没有太大差别。当EndEach完成的时候,EachBuilder就再次转回到StringBuilder。从而,我们可以把代码写成:
sb.Line("#include \"./PackageUtil.h\"")
    .Line("PACKAGE_HANDLE PackageUtil::CreateEmptyPackage( PackageHeader& header )")
    .PushLine("{")
        .Line("int ret = RESULT_FAILED;")
        .Line("PackageInitData initData = {PackageInitDataType_Header,&header};")
        .Line("switch(header.PackageType)")
        .PushLine("{")
        .BeginEach(protocols)
            .FormatLine("case XXX_PACKAGE_{0}:", p => p.Name)
            .Push()
            .FormatLine("return CreatePackage(PackageTrait<XXX_PACKAGE_{0}>::GetClassName(),&initData,&ret);", 
               p => p.Name.ToUpper())
            .Pop()
        .EndEach()
        .Line("default:")
        .Push().Line("return NULL;").Pop()
        .PopLine("}")
    .PopLine("}");
这样,通过切换模式的上下文,就达到了对API语义的切换,使之适用于批处理。可以看到上面的代码的结构和目标生成代码之间具有结构的一致性。如果我们再无聊点,折腾一些模板语言来“声明性”表达这个过程,再写一个parser把它转换为上述代码的逻辑,我们就可以定义一个DSL,这是后话。通过同样的方式,我们可以为树形结构设计BeginTree、EndTree。为图结构设计BeginGraphics、EndGraphics,and so on。

渲染

从上面的过程,我们可以说代码生成器和渲染器(至少OpenGl)有诸多共同的地方,我们可以说代码生成就是在做文本渲染。开头说过网络协议本身是一个状态机,传统上,认为写网络层代码是一件“难”的事情,然而,网络协议状态机是一种适合严格的形式化方法的场景。协议包可以被生成、协议的状态机可以被生成、这样我们可以把精力放在如何设计协议、如何设计好的拥塞控制算法等等真正需要数学和脑力的地方。

2015年4月29日星期三

人类是如何运行的(一)

[进步主义]
进步主义是一种在19世纪末至20世纪初从北美开始的政治运动和意识形态,此意识形态属于中间派,但不是所有中间派皆是进步主义的支持者。进步主义者们支持在混合经济的架构下劳动人权和社会正义的持续进步,他们也是福利国家和反托拉斯法最早的拥护者之一。

[功利主义]
效益主义(英语:Utilitarianism),即功利主义,是伦理学中的一个理论。提倡追求“最大幸福”(Maximum Happiness),认为实用即至善的理论,相信决定行为适当与否的标准在于其结果的实用程度。主要哲学家有杰瑞米·边沁、约翰·斯图尔特·密尔等。

[经验主义]
经验主义(英语:Empiricism)又作经验论,通常指相信现代科学方法,认为理论应建立于对于事物的观察,而不是直觉或迷信。意即通过实验研究而后进行理论归纳优于单纯的逻辑推理。经验主义的代表人物有亚里斯多德、托马斯·阿奎纳、弗兰西斯·培根、托马斯·霍布斯、约翰·洛克、乔治·贝克莱和大卫·休谟。

[理性主义]
理性主义、欧洲理性主义(Rationalism)是建立在承认人的理性可以作为知识来源的理论基础上的一种哲学方法,高于并独立于感官感知。一般认为随着笛卡儿的理论而产生,17-18世纪间主要在欧洲大陆上得以传播。同时代相对的另一种哲学方法被称为不列颠经验主义(经验主义中的一派),它认为人类的想法来源于经验,所以知识可能除了数学以外主要来源于经验。这里主要关注的是人类的知识来源以及证实我们所知的一种手段。理性指能够识别、判断、评估实践理由以及使人的行为符合特定目的等方面的智能。理性通过论点与具有说服力的论据发现真理,通过符合逻辑的推理而非依靠表象而获得结论,意见和行动的理由。

[自由主义]
自由主义(英语:Liberalism)是一种意识形态、哲学,以自由作为主要政治价值的一系列思想流派的集合。其特色为追求发展、相信人类善良本性、以及拥护个人自治权,此外亦主张放宽及免除专制政权对个人的控制。更广泛地,自由主义追求保护个人思想自由的社会、以法律限制政府对权力的运用、保障自由贸易的观念、支持私人企业的市场经济、透明的政治体制以保障每一个公民的权利。

[人文主义]
人文主义(亦作人本主义)是一种基于理性和仁慈的哲学理论的世界观。作为一种生活哲学,人文主义从仁慈的人性获得启示,并通过理性推理来指导。人文主义以理性推理为思想基础,以仁慈博爱为基本价值观。个人兴趣、尊严、思想自由、人与人之间的容忍和无暴力相处等,都是人文主义内涵范畴。同时,与人本主义心理学和人道主义关系密切。

[虚无主义]
虚无主义作为哲学意义,为怀疑主义的极致形式。认为世界、生命(特别是人类)的存在是没有客观意义、目的以及可以理解的真相。与其说它是一个人公开表示的立场,不如说它是一种针锋相对的意见。许多评论者认为达达主义、解构主义、朋克这些运动都是虚无主义性质的,虚无主义也被定义为某些时代的特征。如:布希亚称后现代性是虚无主义时代,有些基督教神学家和权威人士断言现代与后现代由于拒绝上帝而是虚无主义的。

2015年2月27日星期五

昨日的世界(七)

【注】早期的笔记本里的东西,不修改地数字化。持续更新中...

2004/12/25 圣诞节
又是一个圣诞节,清冷的夜里,你我独步天涯,风卷残荷般的凄清,雨打芭蕉似的冷静。心灵深处无尽的愁丝在骨子里渗透,挥之不去。手中的笔如当初一样朴素,生活亦不随时空转移而失去永恒不变的生命主题:矛盾。在这个少雨多风的海边大学里,继续延续着所执着的梦想。慢慢淡去了情绪化的极端思想,而对健康更关注了。
走过之后,才发觉没有想象中的惊奇,反而寄注了深深怀念。多少人或沉或浮,心却总是平静的,不应失去天枰,理应不卑不亢。任然需要勇气面对每个海风轻吹的日子。善于安排精神家具,便织出五彩生活。或忧郁或坦然,全在于对自己的把握。理性与感性交织在一起。理想的至善、至美、至健、至洁、生发出平淡隽永的生命资本,在心灵的港湾荡漾。
携一丝风,圣诞快乐。

2005/1/17 海风
依然是不成熟的一个人,还是在寻找,有些已经找到。想是这样的。虽然有些小隙,毕竟是很幸福的了。
明明是一个很善于思考的人,明明有时会出奇的有创造性思维,却常常像一个反应迟钝的人,有时很搞笑。所谓本性难改,可是生活里应该慢慢。不,应快点成熟,还有开朗点,常说包容,其实自己并不包容,应该做到。
孤独吗?不再孤独,心灵有时依靠,挫折常常犯,1+1=3的“错”。可是天性如此,极力改之,只有学会随时随地可以保持稳重,保持冷静的思维,不可以头脑发热。
荷尽香来。

2005/1/17 风很紧
哭吧,2003很快就变成了2005,这本日记本横跨了过来,所有的辛酸、快乐、悲痛、幸福全在它的背后。虽然只是记录着某一个人的感情,前后却不知有多少个人穿插其中,多少感情交织于字里行间。忧伤似乎是主旋律。然而,性格使然,如我所言:性格加生命、生活,忧伤留在心里,真的没法消除宿命?
笑吧,尽量记忆那些值得我为之灿烂地哭的影子,每一个细节,我都回忆的起来,甚至于每一个文字、话语、动作、特别是眼神。我的生活还是有那么多可以为之一笑的!
然后静静的,净净的。每片发黄的旧纸都是带有感情的,让我哭,让我笑,成为我所拥有的!?

2002/11/23 书摘:刘墉《无聊人生》,汗,我还以为我写的...
有时候,在白天或夜晚,不为什么地,你无法让自己安静的做事,或者,只是安静地坐在房间里。于是,你翻遍电话簿,找到一些朋友或熟人,约他们去某个地方,与他们聊天,与他们打牌,......,但是,那些熟悉的笑声与话语及游戏,不能让你安静,虽然坐在那儿,却不断地有离开的冲动,当聚会结束,你感到说过的一切和听到的一切,竟然那么无聊。你想你应当一个人独自面对世界,但你无法安静地坐下来,于是沿着街道,你从城市的东方向西方漫游,又在西方与南方之间徘徊,你觉得你想找寻一些什么,然而又不太知道要找寻什么,于是就这样飘荡着,像水中失去了根的水草,飘落的花...

2005/1/17 窗外有风
今天是17号。
肖诗以前的号码是17号。
也是妹妹,如同13号。13号终于成了我现在的号码。给我带来天上注定的一切?
难道对妹妹肖诗没有悔疚吗?不,只是一直找借口,但是我写下记下这悔疚有用吗?她还是受到伤害,...,我是自私的,对她而言。
一颗心,善感孤独的自私、复杂、沧桑(没活力?)的心在寻找减轻父母担子的路上犯错(获取不是)。
知道吗?我只是真真的把肖诗当妹妹的。可是,这却也错?现在要真真当Donkey为妹妹,亲妹妹。
时间让痛苦都覆灭,怀念让心灵沉淀。

2006/1/18 奇点
夜深了,我还在听着音乐,写着自己的世界。
真的解析不了自己,像个不连续点函数,到处都是奇点。肖诗曾说我会在这个社会里碰一鼻子灰,甚至会是个流浪汉。当时我说,那总得有人收留我。
我的信仰就是雨,只要下雨,我就会没有烦恼。我以为雨把世界洗涤的干净了。喜欢“雨道”,喜欢“在雨道里奋然前行”这句话。“山村的门虚掩着”也是喜欢的话。喜欢“...”
既然在过程中可以无所谓,也该有勇气为结果负责。能在每一步都走好当然最好,只是强求着避免某些错也不是办法,经历过,然后不在犯,就很好了。















2015年1月16日星期五

昨日的世界(六)

鸿沟 2013-6-x

前阵子看《了不起的狐狸爸爸》,感悟很深。对现在的我来说,你就像片子里的那只狼,无法对话。

只能求同存异了。2年前的不同还只是出于被洗脑的状态。但现在无论如何我是自己独立思考形成的世界观。我在一个一个概念的去区分开来。以前很多东西都混在一起,比如善良、正确就是两个概念,他们是正交的。我想我的世界观倾向于解构,而你越来越倾向于综合。并且你现在观于神的那部分我想我还只是简单排除并未真正自己去系统思考。你知道,像我们这样的人,是无法不经过自己严肃思考而就选择认同的。

不过。我不信神,但我认同人要谦卑、守信、尊重这些理念。

所以,历史上我曾经犯过的那些不谦卑,不守信,不尊重,都属于我的需要自省的部分。我无意逃避这些,面对自己才能活得真实。只是我的这些东西不需要外在的更高级别的存在去支撑。

我承认,这些描述都不严谨。

有时候我会想,或者我选一个大心灵鸡汤就信了就得了,也许就安心了。

但是,你觉得可能么?

我想要的生活 2013-7-x

所有人都在说这句话的各种翻版,「关键是你要做什么」,对我来说挺明确的:酸甜苦辣的生活(ing,basic),探究本质(认知,我认为活着的一大意义就是探究世界,并且我认为科学是人类目前为止最靠谱的),活得真实(不想被各种虚妄的概念所欺骗,这就是我所说的MetaDebug),自由(这个暂时不说,没想好什么是自由)。



如何理解一座城市 2013-10-9

在厦门路痴了10年,靠被动式的[熟悉]建立起了在厦门生存的惯性,反而只在厦门住过一年的萍儿比我还熟。

换了一座城市,深圳。多种原因导致无法再像之前那样做惰性熟悉。如生活上,衣食住行都需要对这个城市有一定的熟悉度;如精神上,需要建立起对新城市的好感和熟悉度以平衡厦门在我脑海中建立起来的偏好;如思维方式上的改变,无法再像过去一样思考,如今的思维方式更加理性,而理性告诉我需要懂机理,对于城市而言,理解其机理就显得尤为重要。

标题是如何理解一座城市。我正在理解深圳这座城市的开头,为哈却急着写下这篇帖子呢?原因很简单,我需要梳理下理解的步骤,这样才能有条不紊,循序渐进。

工具。
1、google地图。
2、百度地图。
3、街上买的纸地图(上面有居家旅行饮食公交等各种信息)。
4、触摸式手机。
5、公交车。
6、城市生活线上社区,比如对于厦门来说有小鱼网,对于深圳来说估计就只能是58同城之类。
7、网上信息和电话查询。

人物。
1、自己。
2、同学。
3、同事。
4、以前来过这里又离开的人。

硬件内容。
1、城市的周边接壤城市。城市的大分区界限,以及大分区之间的主干道。
2、锁定主要需要熟悉的分区边界。
3、主分区的小分区,小分区之间的干道,河流,山,小分区之间的干道。
4、主分区的主要地点,景点,公园,美食街,大学,企业群。
5、主分区的各大超市分布。
6、主分区的各大菜市场分布。
7、主分区的银行分布。
8、主分区的公交路线。
9、主分区的便利店分分布。
10、主分区的功能分类。
11、主分区的幼儿园、小学分布。
12、主分区的政府办公分布。

软件内容。
1、饮食文化。
2、购物文化。
3、社交文化。
4、教育文化。
5、企业文化。
6、政府文化。

大致先写这些内容,需要按分辨率,从轮廓到细节逐步去记忆、体验、理解和内化。我希望自己不肤浅的去理解一座城市,而不是只缘生在此山中。


经过1-2天研究地图,我明白了理解一个城市,需要优先对着地图把主干道的方位识别清楚。以深圳为例,深圳自东向西,分别是罗湖、福田、南山、宝安。然后我在南山这边,所以就先不管罗湖、福田了,只要记住从罗湖到南山是有一个地铁东西横穿就可以。对于南山,中间有条大沙河,我在科技园,是在大沙河的西侧,所以大沙河东侧就不用细究,只要记住有华侨城、世界之窗、红树林等就可以。到了南山大沙河左侧,主干道从北到南有:北环、滨海大道;从东到西:科苑路、南海大道、南山大道、月亮湾。滨海大道北侧是科技园,南侧是深圳大学。科技园里,北到南有:高新1、2、3、4道;西到东有:科技中1、2、3道。科技园再往西,是麒麟花园等生活区,再往西,是中山公园;通过这种对主干道,主要地点的不同分辨率识别和记忆,我很快就理解了深圳的轮廓。

深圳的商业城跟厦门没太大去吧,海岸城类似厦门的中华城。深圳的小吃店则地沟油貌似居多,厦门的话在厦门大学西门那边还是有几家非地沟油的好去处,深圳这边目前看来只有麦当劳最放心。我发现南油这边的面店喜欢做猪杂、三极第等,这实际上是很不健康的,猪肉本来就不如牛肉,加入内脏就更差了,要少吃。

深圳的公交车比厦门的干净。公交车有专门的乘务员,手持移动刷卡设备逐个乘客去刷卡。刷卡起步价是2块钱,跨段加钱。厦门则岛内都是1块,只需在上车的地方固定位置主动刷卡即可。不过深圳公交车的乘务员很专业,是否有人上车,是否有人下车,是否开门,是否关门,到哪站了,有座位了啥的都很周到的调度,乘务员要一直站着,一天下来很辛苦,但她们是我见过最专业的调度员。


深圳的软件园其实并没厦门的大,但这边的大公司多,厦门是个好地方,我觉的厦门如果增加点什么政策好处的话,其实是很有希望吸引大公司去那边落户的。毕竟厦门的空气和城市环境整体比深圳好很多。

这几天找房子,有实地去很多地方走,经验是如果靠近工厂,靠近北环这种大马路的小区不应该租,太吵或者空气不好。

先写到这里,其他时间有想到就再补充,目前看来只会是流水账了。

2014年3月10日星期一

探索那些不常见的控制流(3)

[关键字]
goto, goto with parameters, stack, stackless, continuation

[参考资料]

http://c2.com/cgi/wiki?ContinuationsAreGotos
http://c2.com/cgi/wiki?ContinuationsInCee
http://c2.com/cgi/wiki?SingleUseContinuation

[正文]

这是一个不常见控制流的系列,把参考资料优先放在头部也是不常见的。

最早我们接触编程语言里的的goto,然后我们被告知不要使用goto,使用goto会破坏结构化程序设计。我记得第一次学计算数学的《计算线性代数》、《数值分析》等计算数学入门课程时,我们书上很多算法都是用为代码形式给出,这类算法很多都是迭代型的,只有迭代收敛或超时超出迭代次数就退出。用编程语言的写法,如果没有提供循环这种特性,就只能用1、1、label+goto是实现迭代;2、递归做法。所以递归当然可以完成迭代的任务,这是另一回事。所以goto我们是很熟悉的。实际上if-else-then、while、for、call-function都可以用goto做到。只是由于直接使用goto会导致代码及其难读和不可维护,所以才出现结构程序设计。但即使如此,大部分语言都保留了goto的能力以备不时之需。

我觉得非计算机系的人学语言挺亏的,就像非数学系的人学高等数学很亏一样(当然后面可以弥补,但如果一开始就从正确的入口进去,何必走弯路?)。之所以这么说,是因为数学分析会先把极限的概念、episode-xigema收敛证明法讲解的非常透彻,然后基于这个坚实的概念再往上学微积分,这样学微积分,如果你想剖丁解牛,你就有机会分解下层抽象去做推理,这也解释了另一个软件术语的含义「抽象泄漏法则」,你不得不透过抽象背后的逻辑去理解事物的本质。编程也是这样的,C语言的函数调用直接从汇编的层面看,函数调用栈是一个非常重要的概念,这样能理解函数调用的栈顶保存,参数传递顺序,函数返回前的清空函数栈、恢复上层函数栈顶等动作。从而对函数调用开销有直观的理解,并且也容易去区分和理解栈上变量、堆上变量之间生命周期的区别,并且对寄存器有直观的理解。

<未完待续,这节不好写,慢慢来>








2014年3月8日星期六

探索那些不常见的控制流(2)

尾(巴)递归,尾调用优化,尾递归优化
tailrecursion, tailcall optimization and tailreursion eliminate

最早学scheme的时候就玩过尾递归(tailrecursion),不过当时只是在形式上玩了下,也大概理解尾递归可以让编译器重用函数调用栈,而不会导致函数调用栈溢出发生,同时模糊的说这种写法可以和迭代的效率相当。我们一群人学scheme的时候,只是皮毛的把SICP的前2章的内容和题目看掉、做掉。那段时间我狂看C++的那些书籍,学STL我就直接硬着头皮大致看掉《STL源码剖析》(我才发现要学一个东西未必要按部就班从Prime开始,直接跳过Prime书籍直接看深入一点的书籍也是大有好处的,不用先非常熟悉使用,过了很久之后才想起来去看下源码,才发现之前很多用法实际上多绕了很多弯路,MFC没用过,但我看过《MFC深入浅出》前几章,大概知道那些繁杂的要点在哪,这样就不会心里有惧怕感。);学模板我就看了《The C++ Template》这书,这样就对那些特化、偏特化、typename,template template class,编译时多态(元编程,MetaProgram)等概念有比Prime系统性的一些了解,后面还粗略看了《Morden C++ Template》,知道boost和loki;学C++运行时多态,我看了《Internal C++ Object Model》,这样就知道了class对象的内存分布,也就是虚函数表是最重要的,对象的构造、析构等也与内存分布息息相关,而且虽然不学COM,但知道COM要做ABI也是要知道C++的内存分布的,《COM本质论》前几章也在讲这个。SICP的习题都是一环扣一环,后面的习题直接给予前面的习题来继续做,这样我印象很深的是,抽象是如何被一步步封装起来的,对于每道习题,只要之前习题已经搭建的那些抽象(函数)是坚实的,就可以被用来组合新的抽象,这样我对于「任何软件问题都可以通过添加抽象层解决」这句话就有深入一点的理解,并且印象深刻。scheme由于概念简洁,function是一等公民,所以你会一直专注在解决问题本上上,而不用像C++那样一个又一个特性让你话费大把时间去学习和积累。函数式编程一些重要的概念:惰性求值、无副作用、无状态、不变性等基本概念都深入到我脑子里,以后我用其他语言的时候会不断重新发现这些概念。

想起来之前的那些事就顺便多写了这些,最近开始系统性重新学习和发现那些以前有些清晰有些模糊的概念:tailrecursion, tailcalloptimization, tailrecursioneliminate, continuation passing style, call-with-current-continuation,closure,coroutine,concept, constraint template arguments, lambda, recursion function等等。我想一边把这些编程语言相关的外在概念都梳理一遍,同时系统性学习编译原理、类型理论,再结合实战研究编程语言的实现,希望把这个角落清扫一遍。

恩,本系列以小步迭代方式进行。本节实际上是从下面页面的代码片段拿过来注释了下尾巴递归的概念。

http://c2.com/cgi/wiki?TailRecursion

#include <stdio.h>
#include <stdlib.h>
/**
 *非尾巴递归,return的时候需要做调用自己和一次乘法
 */
int factorial0(int n) {
    if (n == 0) return 1;
    return n * factorial0(n - 1);
}
/**
 *尾巴递归,return的时候只做一件事:递归调用自己
 */
int factorialimpl(int n, int accumulator) {
 if (n == 0) return accumulator;
 return factorialimpl(n - 1, n * accumulator);
}
int factorial1(int n) {
    return factorialimpl(n, 1);
}
/**
 *如果编译器能够识别并为尾巴递归做优化,则尾巴递归
 *等价于如下代码,不需要反复进入和退出嵌套的调用栈
 */
int factorial2(int n, int accumulator) {
 beginning:
 if (n == 0) return accumulator;
 else {
   accumulator *= n;
   n -= 1;
   goto beginning;
 }
}
/**
 *因此,尾巴递归的性能将和下面的迭代等价
 */
int factorial3(int n, int accumulator) {
    while (n != 0) {
      accumulator *= n;
      n -= 1;
    }
    return accumulator;
}

/**
 *这就是尾巴递归的故事,用C语言描述
 */
int main(){
 return 0;
}

上面的c代码演示了什么是尾巴递归,尾巴递归必须在函数返回前只做一件事,那就是递归调用自己。尾巴递归调用的重点在于递归调用返回到上一层调用栈的时候,上一层调用栈并不需要利用这个返回值做点什么事(否则,上层调用栈就必须保留栈上下文),而是直接返回给上上层调用栈。那么对于一定要对递归调用结果「做点什么事」的需求来说,就必须把要参与「做点什么事」的那些数据和函数传递给下层调用栈,比如这个例子里通过accumulator把结果传递给递归调用函数。

那么尾巴递归调用就保证了在尾巴递归的时候不需要保留上层函数调用栈上下文。所以如果编译器能识别尾巴递归调用,则可以对尾巴递归调用做「tailrecursion eliminate」,也就是例中的示意代码。消除了递归调用,从而把递归变成迭代。但这个前提是编译器会帮你做这件事,否则就算你写的代码是尾巴递归调用,但编译器不做「tailrecursion eliminate」的话,则没有这些福利。scheme、lua等语言都有对尾巴递归调用做消除。

进一步的,如果一个函数调用虽然不是经典的「tailrecursion」,但是在return之前只做一件事,就是调用某个其他子过程。则上层函数调用栈也不需要保存,则这个时候编译器可以做「tailcall optimization」,也就是尾调用优化。我们来下下例子:

http://c2.com/cgi/wiki?TailCallOptimization

/**
 *进一步的,尾调用优化
 */
int bar(int a){
     printf("bar called with arg %d\n", a);
     return a * a;
}
int foo(int b){
     return bar(b * b);//函数返回前只做一件事,调用只过程,并且把b*b的结果传入到子过程
}

/**
 *然而,这个例子里,foo的栈如果被优化掉,则a的生命周期
 *被破坏,所以用栈上变量a是不安全的。
 */
int bar(int *b){
     return *b * 10;
}
int foo(){
     int a = 5;
     return bar(&a);
}





2014年3月2日星期日

探索那些不常见的控制流(1)

http://coolshell.cn/articles/10975.html
这篇文章介绍了一个“蝇量级” C 语言协程库的实现。

里面使用宏封装了下,不过我不喜欢宏,觉得宏把简单的东西隐藏而不可见。因为我并不会真正直接用这些宏,只是为了搞清楚如何在C里实现yiled,去掉宏之后,我们可以很清晰的看懂代码的结构和流程。

下面的代码从http://www.chiark.greenend.org.uk/~sgtatham/coroutine.h解析:

#include <stdio.h>
#include <stdlib.h>

/**
 *单线程coroutine,不可重入
 */
int function(void){
static int i,state = 0;
switch(state){
case 0:
for(i=0;i<10;i++){
state = 1;
return i;
case 1:;
}
}
}

/**
 *多线程coroutine,可重入
 */
typedef void* ccr_context_handle;
typedef struct tag_ccr_context{
int state;
int i;
}ccr_context;

int ascending(ccr_context_handle* ccr_context_handle_pointer) {
ccr_context* this = (ccr_context*)*ccr_context_handle_pointer;
if(!this){
this = malloc(sizeof(ccr_context));
*ccr_context_handle_pointer = this;
this->state = 0;
}
if(this){
switch(this->state){
case 0:;
for (this->i=0;this->i<10; this->i++) {
this->state = 1;
return this->i;
case 1:;
}
}
free(*ccr_context_handle_pointer);
*ccr_context_handle_pointer = 0;
return -1;
}
}

/**
 *C测试代码
 */
int main(){
printf("\n");
int i=0;
for(i=0;i<10;i++){
printf("%d",function());
}
printf("\n");

ccr_context_handle handle = 0;
do {
int ret = ascending(&handle);
if(ret!=-1){
printf("%d",ret);
}
}while(handle);
printf("\n");

return 0;
}

当我继续搜索,上述代码在这个页面出现:
http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html
这个页面里描述了在C语言里做coroutine。以及一个典型的生产者消费者模型。在C里面做协程并不轻松,底下更本质的原因是因为C语言里对continuation的支持很低阶,什么是continuation我们会在后面的节里给出。






2014年2月10日星期一

昨日的世界(五)

最喜欢这样的日子了,雾白充斥着整个天地,世界只剩下门前屋后的房子、田地、小鸡、小鸭和每个天真可爱的小女孩,还有屋旁的十几棵参天大树。周围的一切都隐没了,隐没在那白茫茫的雾里。家里只有奶奶和爷爷,所有的因素都是宁静的,雾里飘散着对面的柔和的歌声,如雾般柔和。独坐在屋檐下,止水般宁静地望着小鸡欢叫、小鸭梳理羽毛,有时跑过去和无忧的小孩一起吹泡泡,然后看着那泡泡飘向柔白的雾天里。好漂亮的泡泡,感觉真好,让宁静的心只剩下快乐,或许还有幻想。
霜冻过的竹叶有些发黄,掺和着雾白,朦胧地留在这幅完美简单的画里。那如影母子大杉,依然如故,一高一矮,一胖一瘦。再简单宁静,莫过于春影似雾的山村了。荒芜也隐于雾里,忧伤也隐于雾里,所有都隐在了雾里。你想听吗?听雾里的猪叫,听雾里的鸟鸣,听雾里的车响,听雾里的欢闹。雾里有一切一切的现实和幻想。没有不和谐的因素,只有雾白,雾白,雾白。
延伸到天尽的雾白中的电线,或许代表那漫漫长路,那路的开端永远是这端的静静站立等待的电线杆和这房子。每次都回到这电线杆,回到这雾里,回到这宁静,回到这和谐。
最是今年雾迷人。
——2005.1.29
 
人是会长大的,会的。
时间依然是最公平的,对每个人都是一样的。总有一天,我们都不得不面对这世上我们会碰到的许多事。所以我们终于会学会自己去处理,去接受,而不再幼稚。
命运如此地安排,谁又能抗拒得了那样一种平稳的框架呢?平淡地看待和成熟地面对似乎一开始就成为我们要去实现的。
继续吧,没必要刻意去规划,却要做好准备,毕竟生活还是无常的。
——2005.9.3

当一切都结束时,不属于我的一切人、物都从我的世界里消失的时候,我还会剩下什么呢?什么是值得我去付出的呢?有人说当我们都成为一坯黄土的时候,我们只剩下别人对我们的记忆,只活在那些依然想着我们的人心中。
所以呢,还要多说些什么呢?平静地去好好把握每一天,不要虚度年华,而今,依然不晚。自尊,自信,自理,慢慢地走出一条路。
——2005.9.11 

记不得今天是几号了,一段时间沉在书里之后,今天突然又觉容而怕,似乎有什么东西没做好。不知道哪里来的压力?好像没有什么是能真正留下来的。喧嚣之后那种人去楼空的感觉突然在心里扎根?不要。
电话卡里剩下3、4元,一直没再去充值,只是没有零现金了。想打电话没得打,即便卡里有钱又能打给谁呢?那些逝去飘去的东西加上这样冷风拌成的凉意荡在心里,使感情真心的人难过,别人怎会理解呢?
一丝感伤随着知秋的一叶徐徐飘落,想就叫人心痛。
——2005.11
 
翻开旧旧的《视野》杂志,是今年年初买的,有几段话真的不错,摘其精华如下:
只有完全成熟的人,才有真正的秘密;不太成熟的人,只有暂时的秘密;不成熟的人,没有秘密。
如果你是个铁骨铮铮的好男儿,就应该学会把痛苦作为一种秘密深埋在自己宽厚的胸膛里,永远用你的微笑去面对父母,去感染妻子,永远的用你的笑声去浇灌孩子烂漫的心灵。
意志薄弱的人,为了摆脱孤独,便去寻找安慰和刺激;意志坚强的人,为了摆脱孤独,便去追寻充实和超脱。
感情上的幸与不幸,只有当事者心里最清楚,旁人常常在妄加猜测。有的人脸上有太多太多的微笑,是因为心中有太多太多泪水。
——2005.11

以后应该少吃些泡面。
泡面的味道总是那么迷人,泡面对于我而言还有许多特别的意义。泡面伴我至今,有许多事情和泡面扯上关系,所以吃泡面总会令我想起许多许多的情景,有许多美好的回忆拌在泡面的香喷喷热气里,有许多伤感的回忆混杂着泡面辛辣的味里。
时间在前进,泡面在变化,心情却总会流连、重复、循环。
吃泡面的时候是幸福的。
——2005.11
 
很想事情都朝着好的方向发展。理性的白天里,应该知道如何去做。背负着的令人不安的因素深埋在地下。
飘流着是没有理由、没有借口的,此而让自己放游的。每个海风于吹的日子都该有所得,为了心中遥想的路而奋斗。
毕竟要为自己负责,稳健的心态才能成为真正成熟的人。付出才有回报,真诚做人的原则永不悔。
——2005.11
 
我思故我在。想象的天空里梦想在延伸,荷默有自己理性的原则,用心真诚地为人,坦然面对挫折和胜利,健康成长,梦想会实现。
做有建设性的努力,不再孩子般思考问题,成熟是稳健的。美好的回忆留下,伤感的故事沉淀。亡羊补牢,犹未晚。
每天都是新开始,好好地把握才能成功。
——2005.12.17
 
得失之间,存乎一心。
平凡的或伟大的都是一样的。学会做一个普通人有时候很有必要的。所谓善者,才会是大家。开阔的,包容的,才会是精华的。视野大,才不会留于一时的捉襟见肘。所谓大家亦有相同之处。或者称之为修养,文明的成分。
美好才能赏心悦目,至于糟粕自当远之。勤快是可以弥补环境的先天的缺失。不怀偏见与明亮的胸怀是应该自然流畅地融入本身。
至于拍案而起之锋则可取其气而隐其形,不必拳脚相加、口诛笔伐,礼仪相待,当之谓修为、教养。
——2005.12
 
没有任何进展,效率很低的时候是最让我伤心的时候,希望还只是遥不可及的梦想,或幻想。I want to catch something,但是事实让人很失望,心里真是莫名地难受,好难受,好难受。除了努力,就是泪了,真希望事情早点做好。
——2006.3
 
感冒了,昨天头非常的痛,浑身无力,多亏了同学的感冒药。折腾一天之后,头不痛了,然今天开始流鼻涕了,好难受。但相对于昨天那种程度来说已是不可多得的好,至少有力气,可以吃点东西,不至于爬都爬不起来。仍旧是不停喝水,汗水也就不停地浸透全身衣物。想哭,真想回家。什么都做不了,也什么都不想做,只想早点结束这非人的日子。
——2006.7
 
台风真频繁。六月、七月、八月、九月的时候,台风会有好几次。
台风来的时候总会带来风雨,心里就总会凉凉的,不知道以后台风的时候自己会是在哪里度过的。
梦里的泪光,枝枝节节,牵牵连连。漂走了,永远不会回来的似乎只有自己懂。
空间要撑起来,生活总是挤压空间。
萍真的很忙,我要去打扰她么?一个老是不成熟的人,可恨。
有时偶尔觉得自己漂的好远了。悲伤深处的圣光还是没见到。
伤神的台风,凌乱的夜晚。
——2006.7
 
当许多人都不再写日记的时候,我却还把自己置于这字里行间。
迂腐的只是形式而已。
跃跳度太快意味着不合逻辑,而不合逻辑的就是可笑的。
不懂你在说什么!
当然我明白你想说什么,你想表达什么,然而你没表达清楚。
天真,幼稚。
真的是这样吗?
你相信吗?
梦里的月光迷人,人憔悴。
——2006.7
 
治一种病症的药是好药,
治多种病症的是止痛药,
包治百病的是假药,
药到病除的是毒药。
——读者小语
——2006.9
 
对于爱情,是不能说的,写的。
爱情就多用心,感觉,把握。
婚姻就微调。
——2006.10
 
当然,又是过了很久,才坐下来思考。平常的时候,有时会突然不知道身在何处,将来要做些什么。常常情况是这样的,郁闷大了,失落极了,全身上下充满不安全感。迷惘是非常容易的,但是因为这样,所以努力的时候有个方向、目标。又常常是这样的,挺高兴的。这次一不小心咸鱼翻身了,然而一小段兴奋期过后,你才发觉其实自己依旧是那个自己,前进的路上更需要静下心来去做些什么,踏实地努力着,点点滴滴地把握和渲染,也许这样心灵才会平静,生活才有意义,生命才具价值。
——2006.10.11
 
曾经有一段话陪伴了我。
想永久地记住:
面对失落的痛楚,不要把悔恨的旧梦重拾。沉浸往事只能倾斜心灵的天秤,寻觅过去只能拾回尘土的梦幻。面对失落,应勇敢地追求新的目标,找回精神的追求。失去太阳,请不要流泪,我们还有群星。
——2006.10.11
 
希望能成熟点,让自己的优点发挥的作用大些,不要被缺点绊住脚,要懂得扬长避短。
如果一个环境下没法改变自己的形象,那就换一个好了。某些不可能擅长的东西不如放弃,尽量不要被生活激怒,让平和的心态,坚韧的心占重点。回忆是个漫长的过程,相信自己可以的。头脑要会冷静,尽量不要得意忘形或自以为是,不可以轻视任何人,平等地对待自己与他人。
坚持这样,才会充实,才不会给自己抱怨和后悔的机会。一个人可以哭泣,绝不可以失去自我和没有勇气。
就这样。
——2006.10
 
当我试着给未来一段日子列出一串长长的清单时,当然是指专业书目,我突然想,人的精力有限,而这个世界上的书何其多也,难道我真的会将如此众多繁杂的知识都弄个清楚?况且并未深究,而只是想管窥一斑,都已有如此之多的了。
于是我想是否该重新考虑一下舍取,因为大部分时候要想做出精品只有一种办法,那就是:只做一件事。许多人坚持不涉及另外领域而只是专心一事,但他们做的却绝对是这世上做这事做得最好的。也许,我也因此该专心一事了。
想要的太多、太离谱,反而什么也要不到。明白了这一点,也就懂得如何活得快乐,活得踏实了。
——2006.10.29
 
注定着我不是个甘于平淡的人,看过别人的奋斗历程尤其如此。然而每每只能在想象里看到那些行动,注定我极少地实现那些想来令人激动的事业。原来我是如此的保守与不敢冒险。
心里的躁动与不安总是要涌出来的,有时候显得只有去等待与忍耐,也就是说总有一天要去实现那样的抱负吧。
然而我的性格与环境常显得不谐调。也许我还缺少一些实现梦想的要素,譬如说一个伙伴。实力呢?
难道注定我是个学者?
学海无涯,生存的压力又是常有的。也许眼光该放长点。可能环境影响,有时显得不如别人那样大方,或许我无法不去想这些,因为我不是别人的缘故吧。
——2006.10.30
 
一个人默然前行的时候,有时会感到渺茫与失望之极,不知道从何拾起。猛然间会发现自己能会的原来如此稀少,那种失落的感觉会使我沮丧。
为何自我组织也许是要解决的更为本质的东西吧。
分形,如此美妙的数学我喜欢。

2014年2月8日星期六

昨日的世界(四)

每次整理旧文档,都能在角落里找到以前写的碎片文字,故曰「昨日的世界」。下面这些是写博士毕业论文的那段时间的碎片。无论想法我现在是否还赞同,都在此记录,保持原本的内容,以时间为序。

----------
 20120312
----------
1、开源的本质是我想用我开发,我开发自己用,觉得别人也可能要用,于是我开源。我开源,如果有人觉得他也想用,于是他可以参与开发。并不是我要参与开源,所以开发。

2、封闭的坏处是把人绑定在一个平台上,限制选择,20年前是微软,如今是苹果,如果以前微软是邪恶的,如今之苹果亦然。

3、使用CMD的理由是,windows原生的,不是用第三方exe的原因是保证任何pc上都可以用,js和vb脚本可以用的原因类似。

4、苹果的AppStore依然是卖盒装软件的思想,其做IPAD,提出postpc的目的很可能是为了把pc干掉,因为pc上的盒装软件已经死亡,所以他要创造一个新的封闭环境来维持盒装软件的思维。其对手是Google和亚马逊。

5、所有的AppStore最终都会饱和,每个领域的软件都只剩下一些强势软件,于是开发者在上面的利益渐渐变薄,开发者生态链无法维持而走下坡路,走HTML5之web路线才是正确的路线。做客户端也类似,要摆脱盒装软件思维,这也是互联网企业的精髓。苹果不是一家互联网企业。

6、豌豆荚这种可以随意下载各种盗版软件的软件是会毁掉Android的,原因是因为盗版软件并不是专有软件的死敌,而是开源软件的死敌,盗版软件使得普通用户不会想到去用开源软件。这对开放的Android来说是不利的,正如过去30年来pc上的盗版软件一直都是开源软件的死敌一样。由此可见李开复的创新工厂是一个没有多少原则和理念坚持的拷贝工厂,与YCombinate有天然之别的差距,Y Combinate的创新理念很多都很让人震撼。

7、版本管理是需要的,即使只是自己写自己用,如果有个版本管理,比如使用Google Code svn仓库,哪天有人需要用最近版本的时候就可以去直接checkout出来。

8、GOF设计模式是针对OOP编程打的各种补丁,在非OOP语言里面未必适用,比如javascript里面生搬硬套GOF的设计模式就很奇怪了,javascript更多的语义是函式的,而不是物件导向的。

9、专注是很重要的,专注于那些你认为更有价值能让你举一反三的东西上,而不要被那些繁华喧闹的表面的东西所累赘。只有坚持一点一滴去积累,才能厚积薄发。而每一次的前进并不就代表使命的完成,而只是进阶路上的一小小步伐。业精于勤荒于嬉。

10、专注的技术是否真的能改变世界,我觉得我们还是要首先让自己懂得沉淀。有一颗坚韧的心,不被外界的各种走捷径的坏习惯所诱惑,坚持原则的一致性,努力的实践性。这样无论如何,你是能做点扎实的东西出来的,未必会如现今这个言必称成功的世界所定义的那样成功,但在于修为上的自我完成。

----------
 20120315
----------
1、任何一件事,都应该用技术的方式去对待。何谓技术?技术意味着专业,不是像门外汉那样手忙脚乱,行事没有章法。所谓章法,就是纲举目张,凡事都得有个清晰的目录式设计在脑海中。好记性不如烂笔头,作为专业技术人,应当时刻用手中的工具将抽象和模糊的东西具象化,并且有高效的执行效率。什么叫高效的执行效率?想到一个东西,一个点子,一个解法,都立刻记录下来,形成文档,是一种高效。做事情懂随时懂得遇3则优的原则,批处理的思维运用自如。专业是与差不多先生格格不入的,比如对于汉字,决不允许错别字的存在便是一种每个人都可做到的专业。对于一个事件,是人云亦云,还是独立思考,这便是区别。专业和技术,意味着举一反三,知识和能力的迁移。
2、气质,是一种心态和形态。30而立,很多以前吊儿拦挡的习惯都得改掉。
3、几句从小到大的名言重新拾起来。所谓「朝花夕拾」?
   凡事预则立,不预则废
   业精于勤荒于嬉
   行成于思毁于随
   千里之堤,毁于足下
   静坐常思己过,闲谈莫论人非
   人生在世学几何,学了几何又几何,不学几何又几何。

4、白天忽然想怂恿spacenet去读研,觉得如spacenet这般不做则已,一但认真去做一件事,必然在技术上吃的很透彻,做出来的东西绝不是忽悠人的,很精品很精髓的风格,如果去读研,比然能留下一段传奇。在等公交车时半开玩笑的跟spacenet说起,他立即用自己一贯的迅速回应说,他就本科生水平,在美国他就是本科生水平,研究生的东西(比如数字图像处理,上次他说过),他是看不懂的,他现在就是本科生水平。姑且不论spacenet是否属于自我设限,就我所知,他在很多方面,包括实践能力、方法论等等都是属于上乘的,至少在厦门,他是当之无愧的windows cmd脚本之王。解决问题的方式方法,完全符合计算机科学的内涵。而且他看过诸多各类技术和哲学类书籍,在逻辑方面的反应其实远超过我的速度。我只是在抽象层理解上比他多训练了好多年,我这边博士生很多时候啃东西未必有他那么透彻和精髓。spacenet说自己只有本科生水平,多半是指在计算机科学的数学部分上他看的吃力。而据我看过的书《黑客与画家》分析,计算机科学本质上是一个多学科的杂物,比如涉及到硬件部分实际上可看做是物理学的部分,而涉及到软件部分,则属于编程部分,而涉及到目前的很多研究上,特别是发paper的那部分,实际上很多人做的是数学,而非计算机,计算机只是他们的一种实验。这可以举图像处理,我跟过一小段时间,用到的很多论文都实际上都属于应用数学,只是带有很大比重的实现算法的编程部分,许多计算机系的硕士或者博士,压根就没把那些数学部分理解掉,甚至推导都没过一遍,这是糊涂账部分。当然,并不是说你数学很牛逼就可以把这块做的很好,就像一个英文很好的人未必能写出莎士比亚的作品一样,数学只是一个基本的行内人都看得懂你在说什么的通用科学语言和工具;同样道理,不是你编程很牛逼就能做好研究,编程很牛逼只是说明你对某种语言掌握的很到位,你对软件设计很到位,但计算机的科研,不只是做软件这么简单的事。我理解起来,计算机科研真正做的好,是必须综合以上几部分的理解力之后,加上创造力和持续探索的精神才可能做出点什么出来。一如我在数学领域所理解的一样,能把数学的知识层面吃透的人实际上很多,但是真正有创新力的不多,创新是需要有想象力和构造能力的。我最欣赏的莫过于欧拉、高斯、黎曼、拉格朗日等等构造大师。再提起,创新之前得会找问题,好的问题是我们所缺失的,100多年前希尔伯特提出23个数学问题,使得数学在上个世纪上半叶取得了辉煌的成就。我们国人学生,甚至老师,往往提不出什么问题来做,一般都是所谓跟踪热点,在哪个方面上都落后于人,这当然有多种原因,比如没有专业方向所对应的发达产业,实际上你提的问题都是闭门造车。

5、电子书实际上并不会真得就完全取代纸书,因为电子书再怎么发展,也无法取代实体书的那种质感,随意翻页翻阅的感觉。Apple的新版电子书阅读器诚然在书籍的领域取得了挺大的进步,然而据spacenet观察,那种加入多媒体的手法,在精确性敏感的理工科书籍上有明显的优点,但是在人文哲学领域未必有优点。打一个比喻,很多电影再怎么好看,也总是无法取代原著。我越发喜欢看小说甚过看电影,电影的画面定格给于观众的想象力空间有限,书则不同,带有大量的想象空间。这其实涉及到1对1映射,还是1对多映射,甚至多对多映射信息的关系。一千个读者就有一千个哈姆雷特,多媒体恐怕在这方面未必胜于文本。再罗嗦一句,象形文字最早就是图画。

6、我讨厌病毒和木马,不过我更讨厌杀毒软件制造虚假的病毒和木马威胁感。自由和安全,都应该交予自己。

7、封闭的格式应该去兼容开放的格式,而不是开放的格式必须去兼容封闭的格式。

8、IT产业,什么才是其核心的价值?信息,意味着什么?

----------
 20120316
----------
1、遇到一个问题,从现在开始,要避免不分析问题的前因后果,不对问题的上下文做一番调查就开始做出回应,或者试图毛毛糙糙的把问题推给非义务人去解决。从现在开始,放弃一切试图让非义务方解决自己造成的问题的思维方式。独立之思考,当然需要以独立之解决问题为支撑,否则,独立之思考只是及其狭隘的。

2、谨慎让朋友帮忙,很多时候,你认为不是问题的事情,可能对朋友来说是很为难的事。没有对你有这种义务,这个经常会犯的错误,早该反思反思了。任何情况下,对有隐式和显式雇佣关系的对方,不要混淆雇佣关系下形成的习惯和友谊关系下形成的习惯。否则,会造成很多问题。

3、spacenet人很好,我没说要他帮我弄一个充电器,他却虽然吐槽了好几次,今天却给我弄了个5.3伏的充电器,我突然觉得很惭愧,跟spacenet接触这么久,明明很多正确做人做事的道路都听过无数次,还是在自己身上残留很多坏习惯,很多时候却试图用自己吐槽过无数次的那种思维方式去解决自己遇到的问题。这算是一种隐性的叶公好龙吧,静坐常思己过,我会一点一滴改掉的。

4、毕业这件事上,自己一直没能正式面对,论文投稿的事也怪自己。想想5年前本科毕业的时候对论文的差不多先生思维导致自己的本科毕业论文很一般,几乎是很水,现在难道要让自己重复当年之遗憾么,我想我不应该让历史重复,所以,我决定把最后的时间好好用上,认真把自己的论文写出色。人生难得几回搏,此时不搏何时搏。

5、重构c++代码的时候,觉得以前的很多代码特装B,明明不需要用到那么复杂的东西,却设计成那样的复杂,一个人想要提高对代码的敏感度,最好的方式是每隔一段时间都去重构自己之前的代码,长久习之,定能成大器。现在我也开始讨厌随意使用模板类,感觉模板类只应该由基础类库提供,自己的代码非不得以不要使用,尽量用简单的方法去解决问题,而不要使用各种所谓高级语言特性,对语言特性的热衷现在开始衰退了,不过并不意味着语言是无所谓的,只是说在使用任何一个语言的时候,都能够控制得住自己,保持简洁。不过度设计,过度实现。这很符合人生哲理。

----------
 20120324
----------
1、凡事预则立,不预则废。


----------
 20120522
----------
→白驹过隙
打开log文件夹一看,上次写点随笔已是两个月之前的事了。时过境不迁,翻看当时的日志,犹如昨日之记录。毕业季的这几个月是煎熬的时光,也是磨炼心境的阶段。之前几年欠下的一屁股时间债都堆积在那,我只有一段一段去偿还。凌云六725楼、海运实验楼301和芙蓉餐厅之间三点一线,简单的重复累积着效率、沉淀着心情。未来不再迷茫,梦想不再模糊,依旧的追寻梦想和理想。人说三十而立,我不知道别人在靠近三十岁时是怎样的心情,我自己则是对「三十而立」有着一丝丝感觉了。读书二十年,在书堆中坚持过、在游戏中沉迷过、在亲情中哭泣过、在爱情中执着过、在数学中抽象过、在程序中编织过,不变的是追寻。









2013年8月14日星期三

昨日的世界(三)

tablization
2013-02-06
在公司加班到3点多,回家睡意全无,当年通宵写代码,通宵写论文,通宵看博客,通宵看视频,通宵看书,通宵开心快乐,通宵黯然神伤,各种通宵的那种感觉似曾相识.角色切换,一介码农,所思所想唯有代码.
连续写了3个多月写lua和c#代码,到最好都代码疲劳了,不过我总体来说还是控制住了代码的腐烂速度,没有变的不可收拾,这主要是得益于坚持随时随地的重构工作.尤其是最近这种探索性带有点个人创造性的工作被任务压力紧堆着的时候,总是容易写出一些烂代码出来.可见自己功力还不够,道行还不够深,希望明年内能深入体会和实践kiss原则,唯有内化了的kiss原则才能让自己的代码保持精简而结实.否则的话低级偷懒所带来的重构和测试成本是很高的.
应题,我想要讨论的主要是tab,是的,tab.你正在用tab化的浏览器看我这篇文章,我这篇文章也是在tab化的后台里写的.整个计算机UI史几乎就是在有限屏幕里各种变形tab进行乾坤大挪移的历史.无论是windows的任务栏,还是mac的悬浮Dock,无论是各种客户端软件还是各种网站设计,几乎都是在进行tab的设计.设计好tab,组织和管理好tab,美化好tab,这是UI里最重要的一个环节.设计和实现tab应该要重点考虑哪些要素呢?

以我所见,至少有以下几点需要考虑周到.
1.tab的独立.
2.tab的同步.
3.tab的嵌套.
4.tab的跟随.
5.tab的变形.
6.tab的层次.
7.tab的一致.
8.tab的排斥.
暂时能想到的是这些,有空的话会更仔细琢磨.想要掌控UI,就得不断思考和改进.做UI的程序不能被tab玩死,要玩活tab.我最佩服和欣赏的tab是看不见的tab,存在于你的指尖之下.那就是,the vim editor,vim的有模式编辑,在我看来就是一种顶级的tab,你看不见,不过你却切切实实感受到它的存在,它的犀利,正如命令行是黑客的tab利器.
不过,写tab是很蛋疼的,每次我写到郁闷的时候,就发誓"以后我再也不写tab式软件了!",至少以前跟舍友一起讨论的时候,我们都很唾弃"PPT式"软件.是的,PPT式软件,实际上就是Tab式软件,众观IT世界,PPT式软件无所不在,无所不能,无论什么东西,它都能用PPT给你装进去.PPT是伟大的发明,然而也是偷懒的发明.我们都知道好的PPT和差的PPT会差好几个档次,PPT本身只是一种事物的组织方式,如何用这种组织方式把你的内容,条清缕析,引人入胜的进行组织是一门学问.多思考这些,我们就会发现生活,离不开排版.每次见排版高手们的作品,都很喜欢.不过说到排版,排版式思想只是做UI的一种方法,有其优点亦有缺点,不好的排版策略,会成为"智能的就是傻逼的".比如早期的word.比如LaTeX的图片排版,真是蛋疼.容我吐槽,我现在觉的TeX的精髓应该是在TeX内核本身,而绝不是LaTeX.使用LaTeX并不能带给你排版的真正自由,我觉的TeX内核+宏机制才是能真正带给你自由排版的东西.而不是有限定制的LaTeX.这跟UI一样,只会拖拉控件做UI的永远无法接触到UI的精髓.
今天先写到这,下次,我想谈谈"老头滚动条",或者"上古卷轴".old scroll.

while switch
2013-01-27
2012年写的代码,还是单机版偏多,很多比较核心的模块代码,写起来后发现软件工程神马的都可以去死了,本质上最最最最有趣的居然就是while-switch.while表示要不断去做,switch表示状态切换.与while-switch相伴的几个词是:queue,productor-consumer,thread,try;lock;...
[01].win32消息循环,是一个while-switch,是一个典型的productor-consumer;消息就是个队列.消息循环可以嵌套,内层消息循环和外层消息循环都可以去消费message.message并不只属于外层!程序即数据,把消息即ID.
[02].分组线程池,可分组的线程池;我们本质上也是做成一个productor-consumer,每个threadpool的workitemsgroup就是一个productor,每个threadpool的workitemrunner就是一个consumer;
[03].服务推送,也是一个while-switch.本地应用while监听服务器消息,switch分发给处理者.自动更新只是一个consumer行为.进程间通讯,更多的是互相推送和监听.thrift和WCF都对底层实现做了封装.WCF的概念定义比thrift更具学理:A,B,C.其中A是Adress,通讯的地址;B是Binding,绑定的协议;C是Concat,服务-数据-策略的约定.
[04].下载行为,隐性的while,显性的switch-case;一个典型的有限状态机,下载的行为与状态,界面的更新等等都是很常见的UI场景,这块逻辑一开始写都是比较随意的打补丁,多做几次自然可以在以后新写的时候有干净清爽的设计和实现.
[05].switch-case的关键词是状态,case的是状态的唯一ID.实际编程中很多地方都本质上是用唯一ID定位的,windows下有一个名词叫handle,这东西跟message一样,本质上就是唯一ID.另一个例子是在OOP宿主语言里注册对象给Lua,本质上就是注册唯一ID给Lua就可以.唯一ID要看上下文,唯一并非要全局唯一,有时候只要局部唯一就可以.
[06].while的退出,win32message的例子是发送一个WM_QUIT的消息去结束while.如果是一个异步循环操作,则可以通过一个简单的bool变量在每次循环执行时判断下是否退出,本质上也就是一个if-else.
[07].while的阻塞,win32message的例子是内部的死循环弹出模态对话框.异步操作的例子,是通过每次操作都加锁,需要阻塞时在其他线程获取锁,一旦获取到锁即可阻塞掉循环的下一次执行.

a few tips 4 program
2013-01-27
program是一种技术&艺术.<人月神话>说,没有银弹.对于真正的program来说:向下不断接近硬件,理清大厦的基石如何层层层层构建而来;向上不断面向用户,通过抽象-封装-组合-重构-迭代创造虚拟世界.program有狭义,广义之分:失之狭义,无法享受实做带来的深刻理解和快乐;失之广义,无法由里及外地迁移知识迭代人生.小而庖丁解牛,大而胸怀天下,program的本质在于smart.
1.UML图,流程图,文档本质上都应该是草稿,不是教条,不是目标.是教条是目标是标准化产品;是草稿是脚手架是技艺之结晶.
2.小而精,是为美;大而清,是为良;
3.保持简单,可组合.
4.小偷懒,大成本.
5.简单可并行优于复杂单线程.
6.螳螂捕蝉,黄雀在后.事情往往没那么简单.
7.折腾使人进步,焦虑使人智慧.
8.自由创造.

代码整洁之道
2013-03-09
摘要
本贴记录日常开发中所使用的各种代码整洁技术。在日常开发中,我们需要随时发现重构点,并通过重构使代码保持整洁,从而达到代码的易于阅读、易于维护的目的。开发者总喜欢使用各种其他行业的东西对编程本身做各种隐喻。譬如「建筑」、「写作」、「工程」等等。虽然有的程序员反对这种隐喻,认为编程本身的概念解释就足以阐明程序之道,但软件工程从一开始就借助这些隐喻达到对并不成体系的软件思想的描述和传播。阅读这类软件工程的书,并内化软件工程思想是可以让程序员从普通到优秀,从优秀到卓越。具体来说,有《人月神话》、《代码大全》、《程序员修炼之道-从小工到专家》、《代码整洁之道》、《重构-改善既有代码的设计》、《设计模式-可复用面向对象软件的基础》等等。回顾上述的各种隐喻,我认为用「写作」来对编程做某些方面的隐喻是很恰当的。写作意味着谋篇布局,纲举目张,乾坤大挪移;写作意味着大量阅读优秀作品,不断实践并内化各种软件工程思想,并最终创新作品;写作意味着随时积累小片段,蚂蚁搬家,积少成多,厚积薄发。正是基于上述理由,我认为有必要将日常开发中的小片段条分缕析,归档供查。本贴即是专门积累「代码整洁」相关的小碎片,同时实践“从小工到专家”的思想。

[bolt#]滚动条
2013-02-23
一、折腾。
  今天折腾了一整天没搞定一个问题。下班的时候特别郁闷地发现自己犯了个错误,白白浪费了时间。晚上回来深刻反省,突然明白了很多东西。我觉的有必要在这里记录下,避免以后再折腾。这事得从昨日做程序时间和内存优化说起。
1.时间优化
程序是.NET宿主+BOLT界面组合成的,刚开始我按部就班地先优化程序启动部分的加载时间。基本上是针对程序启动阶段的每个模块的加载做时间分析,按耗时大小顺序进行优化。这部分的优化技术主要包括:
移除无用的加载代码。
延迟加载未直接使用到的模块。
并发加载无空间和时间耦合的模块。
合并和提取重复执行的相同代码。
减少耗时的通信。
尽量减少Dictionary,这是由于string的Hash值计算并不快,数据量大查找慢。

2.空间优化
紧接着第二部分是做空间优化,一开始我只是随意做点能肉眼能看得出来的数据内存优化,比如
- 查找是否有耗时string拼接,改为StringBuilder。这个倒是由于习惯良好,并没有发现。
- 查找无用数据,并去除。
- 查找冗余的数据,合并之。
- 查找大量临时变量产生的地方,添加垃圾收集。
- 用.NET Memory Profile做分析,找出可以优化的内存优化掉。
- 网上搜了下各种内存优化技术,感觉靠谱的做一下,感觉好奇的做下实验。
- 通过二分搜索,找到内存变化比较大的地方进行代码行定位

这个过程并不总是按顺序进行的,交叉进行,然后有一处BOLT调用C#的委托被我注释掉后,神奇的事情发生了,物理内存一下子从100多M减少到30多M,如果加上这句代码内存又会是100多M。然后,我就喊jennal一起来看这句神奇的代码,两个人一起做了半个多小时的测试,最后发现只要在这个调用的函数里循环产生大量临时对象就会导致物理内存是100多M,否则就是30多M。中间我们去吃了个饭,回来继续折腾到10点多没搞定,我就先放弃了,结果坐公交车回来还睡过头,往回走了两站。
今天早上我就早起了去公司继续做单元测试。起初我以为是P/Invoke的问题,经过测试发现不是;接着我怀疑BOLT调用C#导致的问题,做了几组测试后发现只要在这个函数里做GC.Collect()就会导致问题的出现。我就拿这个现象找BOLT群里的几位群主咨询,结果自然是交流了很多意见还是没能解决这个诡异的问题。时间一直在流逝....

等快下班的时候,我发现我在程序启动的地方有调用了一句代码:SetProcessWorkingSetSize(),我把它注释掉后物理内存就是100多M,并不会减少到30多M。这句话这么神奇?马上Google了下,结果发现我被坑了,这句代码压根就是坑爹的。
使用这个函数来设置应用程序最小和最大的运行空间,只会保留需要的物理内存.当应用程序被闲置或系统物理内存太低时,操作系统会自动调用这个机制来设置应用程序的物理内存.
这句代码只是把程序的物理内存里没有用到的部分移到虚拟内存,如果对应的内存再次被用到时,会重新从虚拟内存加载到物理内存。这非但不能减少内存,反而增加了内存页错误,降低了程序的性能!我认真想了下,应该是我看到某个博客上的这句话时加过来做实验的,但忘记了删除。结果,就这样浪费了我一天时间。

二、高效
虽然我被坑爹了,但晚上跟BOLT的聪哥聊天,再结合用bolt开发过程的经验,突然我把很多东西串起来了。首先还是从上述折腾说起,这次折腾的过程中我理解到做内存优化必须老老实实地去做减少不必要资源的创建,增加资源的重用(内存池,对象池,连接池,线程池,享元模式【内蕴状态-外蕴状态】,共享对象...),及时释放可释放的资源(智能指针,IDispose,...),延迟加载。简单说就说:减少创建,增加重用,及时释放,延迟加载。具体在语言层面,又可以针对不同的场景优化数据结构的使用,如果是代码过程中的临时变量则应该对代码做调整减少不必要对象的创建等等。
SetProcessWorkingSetSize这个函数会引起内存页错误,当我跟聪哥说出我感觉内存优化的核心是:减少创建,增加重用,及时释放,延迟加载时,聪哥提到OS本身也是大量使用这样的技术,比如物理内存页,虚拟内存页本身就是按需加载,增加命中率(重用)的典型。我一下子就想起来我做的几个支持大数据的BOLT按需加载控件,不也是这样做的么?

我那几个控件都是带滚动条的支持大数据的控件,我归纳的核心做法是(以列表控件为例子):
- 设计两层结构的UI组织模式:ListControl,ListItem
- 设计ListItem的数据模型(ViewModel)比如叫ListItemData,用数组或者其他容器存储,根据不同场景可能有多层次结构。
- 为ListControl设计数据模型ADT:AddListItemData,RemoveListItemData,InsertListItemData,Clear
- 为ListControl设计UI刷新模型ADT:BeginUpdate、EndUpdate

使用方法是如下的Begin、End模式:
BeginUpdate()
   for ....
     AddListItemData()
   end
EndUpdate()

这里的核心原则是:
- ListControl最多只创建可视区域可以容纳的下的ListItem,一开始全部不可见。
- 在BeginUpdate的时候设置一个变量,挂起UI更新
- 在BeginUpdate和EndUpdate之间的代码负责更新ListControl的ViewModel。
- 整个ListControl只在EndUpdate这个函数里更新界面,包括重新绑定可视区域ListItem当前所绑定的ListItemData,设置可见性等等。
- 根据上一条,需要维护一堆ListItem和ListItemData之间的映射
- 所有交互操作,都会导致当前可视区域的ListItem所绑定的ListItemData的映射表的变动,所以全部交互都会采用上面的BeginUpdate和EndUpdate更新界面。

好了,这就是做按需加载支持大数据的控件的核心内容了。但这与操作系统内存有什么关系呢?也许你已经发现了,物理内存和虚拟内存之间的映射关系和ListItem与ListItemData之间的映射关系几乎一样!CPU执行代码就像是我们在操作界面,CPU执行代码时,指令流发现变化,当发现物理内存不存在时就去从虚拟内存按需加载。所以我们可以把物理内存看成是CPU的可见UI,CPU执行代码看成是UI交互,内存换页看成是动态绑定UI数据。这样我就成功地把带滚动条交互的UI模型用在了理解CPU,内存换页上了。然后,所有你在滚动条上做刷新优化的东西都可以用来理解OS,所有OS的优化技术应该也都可以用来优化滚动条控件。今天就写到这里,下次有时间再写其他的东西。