先罗列下我的需求,有两台机器,一台windows 7,另一台mac,现在想在这两个机器间自动同步文件。两台机器位于不同的私有子网段,也就是说,躲在两个不同的路由器后面接入公网。

因为nat的存在,使得两台机器间直接建立tcp连接成为不可能,想了几个方案:

第一,通过一台有公网ip的机器中转,两台机器同时跟这台服务器建立tcp连接,定时交换文件时间戳并相应的更新。这种方案存在巨大的弊端,我的服务器在美国,两台挨在一起的机器,传送数据居然需要绕过层层路由跨过海底光缆传送到大洋彼端然后再传回来,速度慢,占带宽,太折腾。

第二种就是udp打洞了,假设两台机器分别为A和B,中转机器为S,这样A和B都可以向S发送UDP数据包了,S可以得到NAT转换后的ip和端口号,由于UDP不是面向连接的,任何主机都可以向这个ip和端口号发送数据。理论上讲,只要S把这些信息告诉A和B,这两台机器就可以直接通信了。但是因为安全性的考虑,如果这个数据包的ip和端口号不是先前发给S的那个地址,就会被路由器丢弃。如何取得路由器的信任,是打洞最需要解决的问题。一个简单的想法是,A向B的NAT转换后的地址发送一个udp包,这样A的NAT对于B的这个地址就是敞开的了,如果B能发出具有这样地址的包,二者之间的连接就能建立起来。

今天花了一个下午编写调试相应的代码,A和B之间的数据包始终不能成功接收,后来才意识到,A向B再次发送数据包的时候,NAT路由器会重新生成不同的端口号,换了个端口向S发包测试打印地址印证了这个想法,这个端口号似乎是随机生成的。

google了下,有些路由器确实是这样实现的,这种情况下UDP打洞基本不可行。

总之,问题没那么圆满的解决,最终还是使用方案一中转,先只同步小文件吧,至少比手动拷方便些。

客观评价下NAT对互联网的影响吧,好的方面:解决了ip地址快速耗尽的问题,估计延缓了一二十年。也提高了安全性,某种程度把病毒隔离在一个个子网范围内。

另一方面,把端口号扩展为ip地址的一部分,本身就是一种很踅脚的做法,增加了软件的复杂度。一个好的设计应该简单统一,不应该让人在实现的时候感到头疼。

在已有系统上缝缝补补,用最简单的改变来适应环境,是生物的进化的基本特征。互联网在遇到问题时的解决方案似乎也在重复着大自然的脚步。

还是希望ipv6早日普及吧。

最近想买辆车,相关的东西大致都看或者了解了下,以前对机械相关的专业了解的不多,甚至有些偏见。机械是个成熟复杂的领域,积累了很多研究理论,大量的极具巧思的器件。单单是对这些相关的器件的理解,足以对大脑的思考模式产生重大影响。

举一个简单的例子,汽车在行进当中进行转弯的话,内侧和外侧的轮子的速度是不同的,这时候发动机的输出就要对不同的轮子进行适当调整,如何动态的做到?方法有很多种,最简单的方法,只用一个轮子作为驱动轮,这样其他轮子就只在地上滚动了,早期汽车用的就是这种方法,这种方法其实是在逃避问题,一个轮子驱动有很多弊端。现在的汽车都使用一种叫做差速器的简单装置,可以巧妙的处理这个问题,具体细节用文字解释起来比较麻烦,感兴趣的话可以到youtube上看相关的视频解说。

现代的发动机是个复杂的装置,通过厂商鼓吹的一些技术特点,比如涡轮增压,可变气门正时,缸内直喷等,我们就可以猜测它里面的复杂性了,要通过经年累月的技术积累,不断的调整演化,才能成为一项成熟的产品技术,这也是国产发动机跟国外有很大的技术差距的原因。

我们创造的任何事物和技术都处在不断的长期演变进化当中,其中有一些已经趋于稳定,比如剪刀,钳子,它们拥有简单的结构,材料结构的调整已基本最优,更多的变化空间已不大,但是我们仍然有各种针对特殊需求的剪刀设计出来,他们并没有停滞,只是稳定下来了而已。另一些还处于剧烈的变革当中,比如电视机,手机,一方面由于它们本身的复杂度导致更多的可能,另一方面因为它们还不是很完备,需要不断的技术改进,电视机经历了黑白,彩色,阴极射线管,液晶等一代又一代的技术演进,现在仍然不能说成熟稳定,还有很多调整空间。手机同样也经过的很多代,他们都仍处于演化当中。

汽车的发动机也是在不断的进化,一个很好的类比是人类的心脏,经过了亿万年的进化,现在的人类心脏是个复杂而且高效的器官,它在历史当中必定有过不同的形式,通过自然选择的不断调整,最终趋于稳定。作为一种用金属构造的充满各种孔洞机构的燃烧装置,发动机经历了若干代的调整变革,不断的提升燃油利用效率,提高输出功率,跟大自然的进化类似.

如果我们仔细的考察一些就会发现,生物的进化跟技术的演进是何等的相似!生物通过调整改变以适应自然环境,我们创造改进技术来迎合市场需求。抽去形式概念,二者本质没有差别。

在各项技术产品中,汽车是跟生物最为类似且我认为最有进化潜力的一项东西。因为它可以在地球表面移动,这正是动物最重要的特性。

如果远古的地表拥有我们现在道路的形式,我想会有生物进化出轮子的结构,这样的行进方式最节省能量。但是崎岖不平的地表,导致腿成为现今动物界的主导。

我们认为动物之所以为动物,最主要的特征就是动物可以自由的行走移动,避开障碍,而不是具有高级思维能力。

汽车必须靠人来驾驶,但是为了便利,我们逐渐开发除了各种技术减轻人的疲劳。手动档变成自动档,简化了操作流程,定速巡航可以把脚从油门移开,自动泊车系统可以以检测周围空间把车自动倒进狭小的空间里,自适应巡航甚至可以根据前面车的距离自动调整车速跟车。这些辅助性的装置似乎都朝着一个目标,那就是自动驾驶。

想象一下,一辆汽车可以在无人的状态下自动前进,检测避开障碍,自动去加油。我们可以通过无线网络远程把车召唤到眼前,这不是不可企及的想象,未来几年极有可能实现。比如google对自动驾驶投入了很多资金进行研发,并且它的自动驾驶汽车已经安全无事故的行驶了几十万公里。

我们尝试去构造机器人,只是作为兴趣和研究,并没有强大的市场需求。同样也不会为了百分之几的性能优势去花大量时间精力去改进它的某一个部件,但是对于成熟且庞大的汽车市场就是很不同的景象了。就像语音识别随着智能手机的兴盛突然变得炙手可热起来一样,人们愿意为此去投入大量财力精力去研发。

当汽车的自动驾驶成为可能,我们就会考虑如何添加更多的自主性进去了。模式识别(比如现在一些高档车配备的交通路牌识别技术),语音识别,更高级的智能等,都会随之而来。

我可以大胆的预测,将来机器人和人工智能等技术,一定是因为汽车而发展起来,汽车本来就是一个很好的机器人。

为什么是汽车而不是手机或者电视机呢?在我们可以预见的未来,以及事物发展的趋势,我们可以清晰的看到他们各自朝前发展的脉络,以及人们对这些事物功能的期望。电视机被赋予了更轻薄便利色彩真实的期许,手机也是操作反应的功能性需求,而汽车,人类更希望下放更多的权利和自主性过去,通过自动化为生活提供便利。可以说,汽车已经被选定了一条自动化的道路,一条导致智能化自动机器的前进途径。

动物和汽车,器官和部件,形成绝佳的对应类比:心脏和发动机,轮子和脚,关节和离合器差速器,食物和燃油……零件的不断调整改进就是进化。也许有一天,就像变形金刚里,我们会引入汽车人这个称谓。

最近在调一个性能问题,过程中惊讶的发现,cpu竟然能够在一个时钟周期里执行多条指令。

仔细google后发现还是我土了,这是cpu早就支持的一项技术,叫做superscalar,超标量。其实上学时计算机组成原理就翻来覆去的说这个概念,我一直理解为,超标量就是一条指令可以操作多条数据,进而达到一个时钟周期内达到几条指令的效果,而这其实是SIMD所实现的功能。

写了个测试程序仔细研究了下。一个for循环在编译器不开优化选项的情况下,大致翻译成六条指令
[code]
for(unsigned i=0; i<0xffffffff; ++i);
00FC1040 mov dword ptr [i],0
00FC1047 jmp test_cpu+42h (0FC1052h)
00FC1049 mov eax,dword ptr [i]
00FC104C add eax,1
00FC104F mov dword ptr [i],eax
00FC1052 cmp dword ptr [i],0FFFFFFFFh
00FC1056 jae test_cpu+4Ah (0FC105Ah)
00FC1058 jmp test_cpu+39h (0FC1049h)
[/code]
在我的机器,core i5-2400 @ 3.1GHz的cpu上,这个循环跑了8496ms,每秒钟执行的指令条数为0xffffffff*6/8.496=3033168993.644068,平均1.022个时钟周期执行一条指令,事实上还达不到,因为intel的core系列cpu支持Turbo Boost技术,会动态条整cpu的频率,i5-2400可以达到3.4 GHz,一般来讲,差不多也就是1.2个时钟周期执行一条指令。

如果for循环里做几个操作,看下面代码
[c]
int a=0,b=0,c=0;
for(unsigned i=0; i<0xffffffff; ++i){
++a;
++b;
++c;
}
[/c]
翻译成下面的机器代码
[code]
int a=0,b=0,c=0;
0104A0E0 mov dword ptr [a],0
0104A0E7 mov dword ptr [b],0
0104A0EE mov dword ptr [c],0
for(unsigned i=0; i<0xffffffff; ++i){
0104A0F5 mov dword ptr [i],0
0104A0FC jmp test_cpu+57h (104A107h)
0104A0FE mov eax,dword ptr [i]
0104A101 add eax,1
0104A104 mov dword ptr [i],eax
0104A107 cmp dword ptr [i],0FFFFFFFFh
0104A10B jae test_cpu+7Ah (104A12Ah)
++a;
0104A10D mov eax,dword ptr [a]
0104A110 add eax,1
0104A113 mov dword ptr [a],eax
++b;
0104A116 mov eax,dword ptr [b]
0104A119 add eax,1
0104A11C mov dword ptr [b],eax
++c;
0104A11F mov eax,dword ptr [c]
0104A122 add eax,1
0104A125 mov dword ptr [c],eax
}
0104A128 jmp test_cpu+4Eh (104A0FEh)
[/code]
一共是9+6=15条指令,执行时间为9795ms,一秒内总共执行0xffffffff*15/9.795=6577285290.964778条指令,3.1GHz的cpu来看,平均一个时钟周期执行两条指令。

可以接着扩展,比如
[cpp]
int a=0,b=0,c=0,e=0,f=0,g=0;
for(unsigned i=0; i<0xffffffff; ++i){
++a; ++b; ++c; ++e; ++f; ++g;
}
[/cpp]

被翻译成24条指令,10611ms,总条数为0xffffffff*24/10.611=9714373299.406277,平均一个时钟周期执行3条指令。

指令在执行阶段,会被分析数据依赖关系,可以并行的就直接被dispatch到ALU乱序执行。上面的a,b,c三个变量之间没有以来关系,于是就可以发挥superscalar的功效了。

但是如果a,b,c三个变量之间产生关联,就会变成顺序执行了,比如
[cpp]
int a=0,b=0,c=0;
for(unsigned i=0; i<0xffffffff; ++i){
a = b+c;
b = a+c;
c = a+b;
}
[/cpp]

同样编译成15条指令
[code]
for(unsigned i=0; i<0xffffffff; ++i){
00A4A0F5 mov dword ptr [i],0
00A4A0FC jmp test_cpu+57h (0A4A107h)
00A4A0FE mov eax,dword ptr [i]
00A4A101 add eax,1
00A4A104 mov dword ptr [i],eax
00A4A107 cmp dword ptr [i],0FFFFFFFFh
00A4A10B jae test_cpu+7Ah (0A4A12Ah)
a = b+c;
00A4A10D mov eax,dword ptr [b]
00A4A110 add eax,dword ptr [c]
00A4A113 mov dword ptr [a],eax
b = a+c;
00A4A116 mov eax,dword ptr [a]
00A4A119 add eax,dword ptr [c]
00A4A11C mov dword ptr [b],eax
c = a+b;
00A4A11F mov eax,dword ptr [a]
00A4A122 add eax,dword ptr [b]
00A4A125 mov dword ptr [c],eax
}
00A4A128 jmp test_cpu+4Eh (0A4A0FEh)
[/code]
但是却花了28410ms才执行完,0xffffffff*15/28.410=2267670166.3146777,执行一条指令需要1.367个时钟周期。

查了下intel的Software Developer’s Manual关于superscalar的说明

2.2.3.2 Execution Core
The execution core of the Intel Core microarchitecture is superscalar and can process instructions out of order to increase the overall rate of instructions executed per cycle (IPC). The execution core employs the following feature to improve execution throughput and efficiency:

• Up to six micro-ops can be dispatched to execute per cycle
• Up to four instructions can be retired per cycle
• Three full arithmetic logical units
• SIMD instructions can be dispatched through three issue ports
• Most SIMD instructions have 1-cycle throughput (including 128-bit SIMD instructions)
• Up to eight floating-point operation per cycle
• Many long-latency computation operation are pipelined in hardware to increase overall throughput
• Reduced exposure to data access delays using Intel Smart Memory Access

core架构的cpu一共有3个算术逻辑单元ALU,每个cycle最多可以并发6条微指令。

wikipedia上的介绍讲1998年以后的通用cpu,除了低功耗和嵌入式的以外,基本都支持superscalar技术。

其实现在一些高性能的嵌入式cpu也支持超标量了,比如iphone 4使用的A4处理器,包含一个ARM Cortex-A8内核,是一个dual-issue superscalar design,每个周期可以并发执行两条指令。

最近在mac写代码比较多,以前用windows因为眼睛不好,就写了个程序,每隔几十分钟提示休息下。对着mac也觉得有必要搞个类似的东西。
在windows下的主要思路是开个WM_TIMER,然后在系统托盘显示个图标,一直不退出在后台运行,到时间了就在桌面上打印出REST YOUR EYES之类的大字。开始也想在mac上照这个思路做。不过今天在shell敲着命令突然灵光一现,于是就有了下面的这几行bash脚本
[code lang=”bash”]
#!/bin/bash

while [ 1 ]
do
sleep 600 && say rest your eyes
done
[/code]
还是unix的shell功能强大啊,几行脚本就搞定了;-)
btw,mac下的say命令真的很好用,可以把文本读出来,自动合成的语音效果还不错,结合各种命令就能得到十分有意思的效果

之前写过的一个协议解析生成的程序,在windows下用gcc和lex&yacc编译,运行的很好,到了mac下很多问题,先是直接syntax error,仔细排查后发现是之前文件换行用的是CRLF,改了能解析了,结果直接给出个Segmentation fault,折腾了整整一个下午加半个晚上,在每一个值得怀疑的地方加打印信息,最诡异的是,两个函数里的指针的值相同,一个正常,另一个就access violation。
[code lang=”c”]

void* set_comment(struct Entry* ent,char* cmt)
{
strcpy(ent->comment,cmt);
if (strlen(cmt)>0 && cmt[strlen(cmt)-1]==’n’)
ent->comment[strlen(cmt)-1] = ‘\0’;
return ent;
}

void* struct_entry(void* st,char* name,char* arr,char* swname)
{
struct Entry* ent = (struct Entry*)malloc(sizeof(struct Entry));
char* tname = ((struct TypeDesc*)st)->tname;

strcpy(ent->tname,tname);
strcpy(ent->name,name);
strcpy(ent->sw,swname);
strcpy(ent->arrval,arr);

return ent;
}
[/code]
struct_entry里申请struct Entry* ent的内存,返回又被传到了set_comment里面,打印出来两个地址相同,在set_comment就直接指针访问越界了。
最后用gdb一步步调,打印出来两个指针之后才发现,两个指针都是64位的,其中一个被截断了
ent is 100102210
ent is 102210
一开始printf的时候用的是%x,只有后面四个字节,所以相同。
但是为什么会被截断呢,指针返回是64位,传来传去应该都是正常的。在yacc的语法定义里是这么写的
[sourcecode language=”plain”]
st ID arr switchval { $<pval>$ = struct_entry($<pval>1,$<pval>2,$<pval>3,$<pval>4); }
[/sourcecode]
每个地方都貌似不存在问题。在这个执行规约的地方打印,返回的pval确实被截断了,于是开始怀疑是返回值声明导致的问题,找啊找,才发现这个函数没有在头文件里声明,而该死的gcc居然直接链接了,默认C的语法,也许在c89之前,应该是允许不声明直接调用的,默认返回值是int,这个被编译器认为是32位的长度,所以被截断了。

mac是64位的系统,编译出来后,void*之类的指针都是8个字节,int是4个字节,long long是8个。

这个bug差不多算是最近一两年来最让我头疼的一个了,不过最终还是解决了,mac用久了,越来越上手了。

左下角智齿,大二的时候开始长的,当时痛的睡不着觉,脸肿了半边。后来不痛了就再也没有去管过它,前段时间左边的磨牙开始疼,是隐隐作痛那种。去了医院,医生说要把智齿拔掉才行。

上周五去了趟医院,九点多,被告知没有号了,于是折返。周日七点半就到了,总算挂上了号,等到十点半,整整三个小时,总算排到了医生。拍了张X光,医生说我有四颗智齿,左下角的最为严重,已经把第二磨牙顶出缺口来了,必须拔掉,不然磨牙也会挂掉。

但是那颗智齿长得太歪了,拔掉的过程中可能会伤到神经,甚至是终生的,让我回去仔细考虑考虑。实在是纠结,不拔,有危害,拔,可能会导致下巴神经损坏,人生中的许多事情,都是这么错综复杂,动此则伤彼。

排队等候的过程中,看着人来人往,行色匆匆,医院是人类机器的维修机构,每一个人都有所诉求。焦急的心态,凝重的气色,看多了突然觉得有趣,亲身经历更能体会到媒体整天报道的我国医疗的不足。

一个苍蝇在窗口上飞来飞去,一次次的撞击向玻璃,晕头转向,徒劳无功。但是苍蝇却不醒悟,还是一次次飞过去。难道这种生物就没有学习的能力么?我记得看到过蚂蚁,同样在圆形的花盆边缘一圈又一圈的爬行,却不知道自己在兜圈子。可以说,蚂蚁和苍蝇的大脑是简单的,比我们用程序构造的有穷状态机,比如游戏里的怪物,高明不了多少,他们都没有学习和判断的能力,只是先天的被设置好状态,然后根据相应的输入条件执行跳转。

但是蚂蚁和苍蝇却拥有我们人类至今仍未用机器构造出来的东西,那就是它们的机械运动和控制系统。我们的机器人从来没有达到过半点蚂蚁们的灵活程度。蚂蚁苍蝇,在这方面上,和动物人类,都是共用一套类似的系统,只是我们多了一个高度发达的大脑而已。

这套运动系统普遍的存在与各种动物当中,最下层的,是执行某一个具体运动的肌肉,我们可以用电动机模拟它,但是,一个简单的动作,比如行走,如果考虑到各种颠簸不平的路面,对于机器人来说却是极其艰难,对于动物们来说这是轻而易举的事。差别到底在哪里?我们可以用预先编写好的程序代码来控制机器人的行走,预先考虑各种路面情况,判断受力大小位置,以及该如何行动。

动物们从来不理会这套。我突然意识到重要的一点,人生下来是不会行走的,要到很长一段时间才能学会走路。这说明了,走路这项技能,并非先天写进大脑中的,而是后天习得的技能。甚至不只是行走,比如各种体育运动,篮球,足球,都是经过长期锻炼才获得复杂的动作与操控的。这说明了,我们拥有的不是走路的技能,而是一套机制,这套机制保证了应对各种复杂情况的可能。

这套机制需要包含的一些东西,比如,适度控制不同肌肉块的力度,在时间尺度上线性或者各种二次三次或者各种各样曲线式的改变控制力度,这些操作控制连贯起来形成一系列的,有如音乐曲调高低起伏一般的控制脉冲,流向肌肉系统,从而形成各种动作。

还要有某种机制进行选择,比如什么样的脉冲组合是真正正确需要的,这些一般从形成的动作产生的结果,又形成的输入反馈中获得,如果被判定为正确,那么就强化,否则抛弃。

我明显的感觉到这套系统的存在以及对我们各种行为的影响,比如从体育运动中获得乐趣极大程度上就源自于此。甚至音乐给予我们的欢乐,虽然看起来不同,深层次看来也许同样是这套机制导致的。歌声是靠控制声带相关的肌肉产生的,跟跳跃走动,本质并无大异。

也许有一天我们能够通过机器人来实现这套机制,自学习,自适应,这种特点正是智能生命所拥有的一大特性。我们仍然停留在确定性程序的泥淖里,而计算机的通用图灵机的模型的限制在于,它依赖人进行编写。跟生物错综相连的神经元更类似的是可以动态组合的门电路,可惜我们还没有找到可以动态改变并强化他们的方法。FPGA是个好的开端,如果真正构造智能系统,抛弃对通用图灵机编写程序而转向门电路级别也许是明智的。

虽然智齿跟智慧没有什么关联,但却引发了我对智慧这么多的思考,实在是始料未及,世界上发生的毫无关联的事,总是可以找到一些方法关联到一起,每一事件都会对未来的发生造成影响,甚至每一个分子的细微扰动,让我们无法预测未来。

也许正是未来的不确定性才使得我们的存在具有了意义。

几个月没有更新了,这些天一直很头疼。

因为这个月底要首测,之前一直忙着搞登录,打包,构建,升级相关的东西。

网络模块又重新写了一遍,之前是开一个单独的线程,现在换成直接select的方式。

以前幻想实现了一套数据打包的方法,类似于google的protocol buffer,使用xml描述数据结构,生成相应的头文件和元信息,用来读写网络数据包和资源表格。这套东西后来就演变成了研发中心的tdr了,一般来说换成单独的项目组后可能会有更好的维护,事实却恰好相反。老大们都懒得写代码了,都是一帮没有经验的毕业生在搞。东西搞得很不完善,提一个需求过去一两个月都没人搞,真不知道他们都在干什么。公司大了,做事真的很没效率。

资源文件打包换成了TenVFS,这个相对于tdr来说要好用一些。不过还是有些bug,如果资源文件设置密码的话,会导致自动更新无法合并资源包,研发中心这些项目,都给人一种七拼八凑的感觉。跟他们人打交道真够焦头烂额,有乐于帮助的,有很装b的,一个简单的问题来回要很久。为什么程序员喜欢重造轮子,也许就是这个原因吧,自己写的东西,有问题自己解决,找别人真的很烦。

战斗系统还是有很多bug,感觉回合制游戏的战斗系统比即时的还要麻烦,不知道是设计的有问题,还是这个东西本身就存在这个复杂度。即时游戏划分成若干个阶段,比如吟唱,攻击,命中,每一个阶段都跟server有即时的发包回包,很清晰的时序逻辑。而回合游戏却要麻烦的多,开始回合战斗,每个玩家下达指令,服务器统一做计算,回包给所有客户端表现。客户端收到包以后是一个异步的过程,所有结果都已知,但是要单个表现技能,防御,冒血等,需要有一个翻译机制,关键是回合战斗有很多玩法,比如反击,反震,三连击等,很难找一个很统一的方式来处理所有这些问题。

战斗的设计上肯定有很多不完善之处,但总体感觉,这里本身就很复杂。

自动构建,打包是用perl写的,perl就像一把锐利的小刀,无坚不摧,用起来很爽,实在是程序员强大的必备工具。

感觉要把一件事情做好,都会经历一个痛苦疲惫的阶段,似乎是解决问题必须付出的代价。