小海's profileFlier's SkyPhotosBlogListsMore ![]() | Help |
|
|
January 19 陌生的天花板也许是受了少许遗传因素或家庭传统的影响,从小就不安份于蛰居家中。时常抱着一枕少年的幻想与好奇,随着父母四处游历名山大川;稍大后更是少年不知愁滋味,任性的将时间和金钱挥洒于壮美山河之间,生活只是在旅行与筹划旅行之间切换。 而在习惯了现代化的飞机、汽车,亦或传统一些的徒步、马帮旅行之后,回过头看看一千年前的中世纪,现代意义上旅行这个概念形成之初,会发现我们所谓的旅行实际上早已没有自以为的冒险或辛劳,更多的只是一种体验方式或生活态度。 《中世纪的旅行》并不能算是一本故事性非常强的书。某种程度上来说,他是将中世纪的客观世界和主观认知,在旅行这个层面做了一个切片,然后以旅行方式、行者的心态和回忆中的幻想,展示给我们这些所谓的现代旅行者。 在旅行方式上,传统的交通工具和并不完善的路途保障,导致旅行的距离和时间之比,将人们的视野限制在一个狭隘的范围之内。源自于古罗马大道的路网体系,仅依赖于地方性领主和修道院来维护,必然导致旅行目的地可达的局部性。而时不时爆发的局部战争、四处游荡的匪徒、难以穿越的茂密森林和茫茫沙漠,致使长途旅行变成一项风险极大的行为。 因而在行者的动机和心态上,贸易和宗教,是这样恶劣的环境下,驱使其继续前行的重要原因。朝圣者、僧侣和十字军,抱着单纯或并不单纯的目的,在几百年间不停的,向着远方神许之地进发。在那个传统和时间几乎停滞的时代,试图改善自我生存状态的人,只能选择这种冒险的长途迁徙,来获取心灵上的救赎或荣誉,以及更为现实的物质上的财富。 当然,在这期间,也掺杂着与我们这个时代类似,将旅行作为一种游历的青年;为君王或雇主服务的信使和官员;以及将旅行作为生活本身的探险家和漂泊者。甚至一些朝圣者和大学生,也抱着类似的心态不停在城市间游荡。虽然千年已逝,但人性和生活本质上基本还是没有太大改变的。:) 但因为信息量和交流上的限制,彼时的人们对世界的认识,是狭隘和充满幻想的。种种关于遥远世界的传言和揣测,在主观偏见和客观无知的影响下,形成一个混乱和无序的整体认知。但想想当今,纵然有精确到米的地图,和顷刻跨越千里的交通工具,大众对所谓世界的认知,又何尝不是如此。坐在家中,看着电视里精心剪辑的节目,或翻着层层筛选后的书籍,我们有着近乎于古人想象中神灵的感知能力。但在这呈现给我们的世界背后,同样夹杂着偏见和无知,只不过隐式的被应用于选材、剪辑和我们的遥控器动作中罢了。 这也是为什么我一直倾向于,旅行时一定到当地去以当地人的方式与他们交流。走一样的路,吃一样的饭菜,干一样的活,平等的心态跟他们交流,才能感受到世界的真实存在。否则坐在大巴上看着旅行攻略,和中世纪酒馆里吟游诗人的传说,又有什么本质上的区别呢? 但就像中世纪游历的青年游历骑士所感受的,在旅行中从未知到未知的无限可能性,对遥远世界的向往和体验,以及早上醒来时陌生的天花板,大概可以算是现代意义上旅行的魅力所在吧。 December 14 中世纪的思维盛宴
在这样一个懒散的冬日午后,一个充溢着阳光的房间里靠在桌前,耳边流淌着许巍悠远的歌声在诉说着什么,实在是阅读一本关于中世纪的历史书的最合适的情形。那一个个熟悉又陌生的名字,争先恐后的从书中跃起,在阳光中的尘埃里,大声宣读着他们的理念,自满、虔诚、愚昧、睿智、狡诈;却又随着书页的翻动,慢慢淡去只留下一个剪影,共同组成了那个波澜壮阔的时代。 断断续续用了大半年时间,才结束了这场中世纪的思维探险之旅。一方面是因为大量从前没有接触过的信息和思想,需要时间去深入了解和思考;另一方面也是因为一直不忍读得太快,希望能将思维的盛宴拖的尽可能长一些。 长久以来,在我等被传统教育摧残几至麻木的理科学生眼里,中世纪 (The Middle Age)欧洲的代名词几乎就是黑暗时代 (Dark Ages)、大瘟疫 (Great Plague),亦或百年战争 (Hundred Years’ War)、十字军远征 (Crusades) 这样的考点,以及罗马帝国 (Roman Empire) 的荣光、狮心王 (the Lion-Hearted) 的传奇或伊斯兰全盛时期 (Islamic Golden Age) 的神秘。这整整1000年(公元5-15世纪)仿佛被厚重的名为愚昧的迷雾所遮掩,直到名为文艺复兴 (Renaissance) 的闪电划过天际,欧洲才跃入了一个至今为人羡慕的黄金时代,并立刻与那无知的过去划清界限。 但偶尔在仰慕那些华丽的哥特式建筑 (Gothic Architecture) 的时候,也会悄悄给自己一个问题,那些理应在生存与死亡边缘挣扎的中世纪民众,何以在层层迷雾之中创建如此璀璨的艺术。而在阅读二战或当代关于欧洲民族问题的书籍时,也不时觉得他们那自认为理所当然的理由,似乎隐藏着什么不为我们所知的背书。英国和法国的互相讥讽,德国与法国的时代恩仇以及巴尔干的错综复杂,仿佛都有一个源头。反过来再翻看文艺复兴、工业革命以及现代大陆法系 (Civil Law) 和英美法系 (Common law) 等等至今对我们产生深远影响的事件和概念形成与发展,也无不与那被遗忘的时代有关。
正如作者在导言中说到:
“无论如何,中世纪都不是一个沉睡的、可怕的时代,而是一个充满变化的时代。公元600年的欧洲和公元1000年或1400年的欧洲有着截然不同的景象。 …… 直到今天,中世纪时关于教育、政府、社会结构、社会公正的观念仍然影响着非欧洲国家。大学是中世纪的发明;而现在,大学这种教育机构遍布全球。美国、墨西哥、加拿大、以色列、日本,以及东欧的几个新的民主国家,其立法机构都可以说是源自于中世纪的国会和议会。当今中国、古巴、朝鲜等国的共产主义政体,也源西欧的思想;其中一部分可追溯至卡尔.马克思之前好几百年的中世纪晚期。 …… 简而言之,西欧改变了整个世界,并将它带入我们身处其中的全球化文明;无论这种变化是好是坏,要了解他,我们必须回到中世纪来寻找一部分答案。在漫长的中世纪,欧洲从贫困而极不安定的农业社会发展成强盛而别具一格的文明社会,对当代世界的成形有着深远的影响。” June 21 天堂里没有痛苦——yuannian 师兄,一路走好 晚上参加完公司的培训,回到宿舍刚坐定开始看文档,忽然收到QuickMouse从MSN上发来噩耗,原白云黄鹤BBS站长yuannian去世了。于是匆忙登录已经年余未上的白云黄鹤,在sysop看到了站务公告和QuickMouse的哀悼文章。为进一步获取详细情况,我又google到其父在文学论坛介绍的详情:
一页一页看下来,我已不知该如何形容此时的感觉:几分惋惜,几分悲哀,几分愤怒!
30 岁正值黄金年华,无法成功立业也罢,竟至白发人送黑发人。虽未有妻儿之累,但难以想像其老父母,如何度过痛失骄子的残生,如何面对冰冷的遗像。
5 年的青春都献给了华为,1 年至经理,3 年至技术负责人。在发现白血病时却已至中晚期,后四个月骤然而逝。作为师弟,作为站友,作为IT同业人,我感到悲哀的同时感到无比的愤怒!
首先,根据 google 的结果,白血病分急性和慢性两种:前者是急性发作,起病的时候有很多症状、体征,比如出血,很难说有潜伏期;后者症状不明显,可能让你没有觉察到,很多人是到了有严重的症状才去看病。如果每年有体检的机会,可能比较早期就能发现。通过血象检查,白血病是比较容易确诊的。也就是说,作为一个有几万名高科技人才的华为,一个有着各种完善业务流程的国际化公司,竟然没有一个最基本的,确保其核心资产:人力资源长期可用性的体检制度。而与之相似的更多所谓高科技企业,在这方面也令人深表怀疑。有幸的是我工作的两家企业,无论规模大小在这方面还是非常正规和严谨的,也说明这跟企业规模无关而取决于重视程度。
其次,白血病的早期症状中,除了中后期的各种出血外,“持续头晕、高烧不退,脸色苍白、容易疲倦”等现象,实际上跟目前大部分 IT 人士长期处于的“亚健康”状态类似。以我个人经历为例,在到目前公司之前的大概两三年时间中,也是长期处于高度紧张的工作状态。其时除了高烧不退的情况较少外,实际上跟白血病早期的症状难以分辨。而一旦常年处于这种状态,人对健康的敏感程度也会逐渐下降,等到出现出血等严重症状时可能已经完了。而自己年少轻狂之时,又往往视健康为无物,期望以燃烧生命来弥补知识和技能上的缺陷。
低头反思,自己五一前西藏之行也是折戟于轻视二字。仗着较为丰富的户外经验、几次高原徒步的成功经历,在休息和准备不足的情况下,贸然组队租车向世界屋脊——阿里进发。途中又低估平均四千多米海拔缺氧的严重性,最终因睡觉着凉发展为肺水肿。如果不是路遇几位好心的当地人,被两天三夜连续驱车赶路,送回拉萨住院就诊,估计在白云黄鹤上站务公告里面就是我的名字了。躺在拉萨总参医院的病床上,同样也是连坐起或翻身都困难的我,最终侥幸得还;但师兄却没有这么幸运,没有逃脱病魔的魔爪。
好在经过一段长考,我痛定思痛开始尝试转变生活方式,而这次的濒死体验也进一步完善了自己的认识。期望其他朋友也能多以长远的眼光,来思考和规划自己的未来之路。
btw: 刚刚跟身在华为的另一位同学聊,据称他一位华为的博后师兄,前不久也不幸去世。而他业已准备在今年合同到期后,离职暂时回家修养。
希望这样的事情不要在无谓的重复,yuannian 师兄,一路走好!
November 19 another day 又是平淡、充实的一天,睡到自然醒、美食、电影、羽毛球、麻辣烫,然后在许威悠远的歌声中享受着龙井的清香。但在快步穿过繁华的新街口商业街时,在高高跃起将一个后场球斜线扣死时,亦或隔着车窗看着夜色中快速略过的灯红酒绿时,我却似乎无法感觉到一丝的真实感,仿佛像是一个路过的灵魂在尘世中穿行。
当地铁列车从身边呼啸而过时,我仿佛又听到了小黑湖清晨时暴风雪的尖啸;当骑车的路人在车窗外缓缓穿行时,我仿佛又看到了烈日下慢慢爬升的马队;那路边欢喜的孩子的笑脸,也不知觉中与带着高原红的同样天真的孩子重合。
一霎那间,我仿佛能理解到兄弟连里,温斯特从伦敦地铁出来,在河边漫步,回旅舍在浴缸中泡热水时的那种奇妙感觉。那是一种由两个不同世界堆叠在一起的错乱感,是一种对世俗社会缺乏信任感和归属感的困惑,是一次对 Who am I 和 What am I doing 的质询。看来上一次的长考只管了半年 September 28 NXT 学习笔记 [3] 获取 NXT 信息 (C++) 这个系列文章因为开始涉及到太多技术细节,跟这儿的定位不符合,改到另外一个 blog 上连载了,呵呵
作为一个开放式扩展平台,LEGO 提供了 NXT 相关的非常详尽的资料。而作为二次开发不可或缺的 SDK,当然也包括在其中。在其网站的 NXT'reme 栏目中可以下载到针对 C++ 开发环境的 Driver SDK,支持 Windows 和 MAC 系统。 Driver SDK (fantomSDK1.0.2f0.zip 2.21MB) 因为 NXT 提供了 USB 和蓝牙两种连接方式,所以为了提供同一的编程接口,LEGO 通过一个 DLL 对其进行了封装。这个 DLL 有一个很 cool 的名字 —— 饭桶 (fantom.dll) -_-b 对 USB 方式的连接,LEGO 提供的驱动 fantom.sys 与 NXT 直接进行通讯; 对蓝牙方式,则是在进行配对 (pair) 后,把 NXT 设备映射到一个 COM 端口,然后通过 Win32 文件方式访问端口进行通讯。Microsoft 在 Robotics Studio 中对 NXT 的支持就是通过后者的方式实现的。前者的好处是速度快且一次安装后使用时无需配置;后者则更为灵活,能够无线通讯,且不需要依赖特定驱动的安装。 对于我们日常开发来说,直接使用 fantom.dll 可以很大程度上简化工作,下面我们看看如何通过 fantom 来获取 NXT 的各种信息。 安装 Drvier SDK 后,在 document\fantom.chm 里是基本封装类型的使用帮助,以及一个简单的示例程序代码;includes 里是 fantom 的头文件,以及 labview 的 vi 支持库,这个后面有机会再详细介绍。targets\win32U\i386\msvc71\release 里是需要 link 的库文件。 首先我们需要建立一个 iNXTIterator 对象,此对象能够对注册到系统上的 NXT 设备进行遍历。 int _tmain(int argc, _TCHAR* argv[]) { tStatus status; std::cout << "枚举 NXT 设备...";
// 建立一个迭代子遍历所有的 NXT 实例
iNXTIterator *nxtIteratorPtr = iNXT::createNXTIterator( false /* 不搜索通过蓝牙连接的 NXT (仅应用 USB 连接 ) */, 0 /* 忽略蓝牙搜索的超时时间 */, status ); if (status.isFatal())
{ std::cout << "失败,错误码:" << status.getCode() << std::endl; return status.getCode(); } else { std::cout << "成功!" << std::endl << std::endl; } for (; status.isNotFatal(); nxtIteratorPtr->advance(status))
{ iNXT* nxtPtr = nxtIteratorPtr->getNXT(status); if (nxtPtr && status.isSuccess())
{ // 获取 NXT 设备的信息 iNXT::destroyNXT( nxtPtr ); } } iNXT::destroyNXTIterator(nxtIteratorPtr);
} 这里的 tStatus 对象是对各种错误状态的封装,因为 LEGO 提供的是调试版本的 DLL,所以还可以通过此对象取得错误发生所在的文件和行号 :S 不知道 LEGO 是不是准备后期把这块的代码也开放了。 值得注意的是,在操作 NXT 时创建的各种 iterator 或普通对象,使用完后都必须调用相应的 destroy 方法释放,最好是用 C++ 封装一个类来管理,这里为了简洁暂且不管。 在获得了 NXT 对象后,可以调用其 getDeviceInfo 和 getFirmwareVersion 获得设备的详细信息 const size_t MAX_DEVICENAME_LEN = 16; void dump(iNXT& nxt)
{ ViChar name[MAX_DEVICENAME_LEN]; ViByte bluetoothAddress[6]; ViUInt8 signalStrength[4]; ViUInt32 availableFlash; tStatus status; nxt.getDeviceInfo(name, bluetoothAddress, signalStrength, availableFlash, status);
if (status.isFatal())
{ std::cerr << "获取 NXT 设备信息失败,错误码:" << status.getCode() << std::endl; return; } ViUInt8 protocolVersionMajor, protocolVersionMinor;
ViUInt8 firmwareVersionMajor, firmwareVersionMinor; nxt.getFirmwareVersion(protocolVersionMajor, protocolVersionMinor,
firmwareVersionMajor, firmwareVersionMinor, status); if (status.isFatal())
{ std::cerr << "获取 NXT 设备固件版本失败,错误码:" << status.getCode() << std::endl; return; } std::cout << "设备 " << (char *)name << ", protocol=" << (int)protocolVersionMajor << "." << (int) protocolVersionMinor << ", firmware=" << (int)firmwareVersionMajor << "." << (int)firmwareVersionMinor << ", Flash=" << std::setprecision(4) << (double)availableFlash / 1024 << "KB" << std::endl; } 因为前面指定只枚举 USB 方式连接的设备,因此这儿的蓝牙地址等信息暂时无用。 输出信息大概如下: D:\Study\Robot\LegoDemo\Debug>LegoDemo.exe 枚举 NXT 设备...成功! 设备 NXT, protocol=1.124, firmware=1.3, Flash=50.32KB
对每个 NXT 设备来说,都安装了很多缺省的模块用于对各种外设提供支持,我们可以通过 iModuleIterator 获取获得支持的模块列表。
const size_t MAX_MODULENAME_LEN = 20; void dump(iModule& mod)
{ ViChar name[MAX_MODULENAME_LEN]; mod.getName(name);
std::cout << " 模块 " << std::setw(sizeof(name)) << (char *)name
<< "\tid=0x" << std::hex << mod.getModuleID() << std::dec << "\tsize=" << mod.getModuleSize() << "\tio=" << mod.getModuleIOMapSize() << std::endl; } int _tmain(int argc, _TCHAR* argv[])
{ // 枚举 NXT 设备 std::cout << " 枚举设备支持模块...";
iModuleIterator *modIteratorPtr = nxtPtr->createModuleIterator("*.mod", status); if (!modIteratorPtr || status.isFatal()) { std::cout << "失败,错误码:" << status.getCode() << std::endl; } else { std::cout << "成功!" << std::endl; for (; status.isNotFatal(); modIteratorPtr->advance(status)) { iModule *modPtr = modIteratorPtr->getModule(status); if (modPtr && status.isSuccess()) { dump(*modPtr); nxtPtr->destroyModule(modPtr); } } nxtPtr->destroyModuleIterator(modIteratorPtr); } } 在我的 8527 上的模块列表大致如下,此列表在以后刷 firmware 的时候有可能会变化。 id 是模块的唯一编号;size 是模块的大小,不过似乎没用上;io 是此模块映射的 I/O 地址空间大小。 设备 NXT, protocol=1.124, firmware=1.3, Flash=50.32KB 枚举设备支持模块...成功! 模块 Comm.mod id=0x50001 size=0 io=1896 模块 Input.mod id=0x30001 size=0 io=80 模块 Button.mod id=0x40001 size=0 io=36 模块 Display.mod id=0xa0001 size=0 io=1720 模块 Loader.mod id=0x90001 size=0 io=8 模块 Low Speed.mod id=0xb0001 size=0 io=167 模块 Output.mod id=0x20001 size=0 io=100 模块 Sound.mod id=0x80001 size=0 io=30 模块 IOCtrl.mod id=0x60001 size=0 io=2 模块 Command.mod id=0x10001 size=0 io=32820 模块 Ui.mod id=0xc0001 size=0 io=40 此外另一类重要信息是 NXT 上的文件列表,遍历的方式非常类似
const size_t MAX_FILENAME_LEN = 20;
void dump(iFile& file)
{ tStatus status; ViChar name[MAX_FILENAME_LEN]; file.getName(name);
std::cout << " 文件 " << std::setw(sizeof(name)) << std::right << (char *)name << std::left
<< "\tsize=" << std::setw(10) << std::left << file.getSize(status) << "\tavailable=" << file.getAvailableSize(status) << std::endl; } int _tmain(int argc, _TCHAR* argv[])
{ // 枚举 NXT 设备 status.clear();
std::cout << " 枚举设备包含文件...";
iFileIterator *fileIteratorPtr = nxtPtr->createFileIterator("*.*", status);
if (!fileIteratorPtr || status.isFatal())
{ std::cout << "失败,错误码:" << status.getCode() << std::endl; } else { std::cout << "成功!" << std::endl; for (; status.isNotFatal(); fileIteratorPtr->advance(status))
{ iFile *filePtr = fileIteratorPtr->getFile(status); if (filePtr && status.isSuccess())
{ dump(*filePtr); nxtPtr->destroyFile(filePtr);
} } nxtPtr->destroyFileIterator(fileIteratorPtr); } } 注意在两次枚举之间,需要用 status.clear 显式重置状态,否则调用会失败。 一个典型的文件列表如下,此列表包括在 My Files 菜单下的所有类型文件 size 是文件的实际大小;available 是文件的可用大小,不过似乎也没有支持。 枚举设备包含文件...成功! 文件 config.txt size=77 available=0 文件 Timeglass.ric size=76 available=0 文件 hands2.ric size=834 available=0 文件 main.rxe size=16930 available=0 文件 Program.tmp size=13 available=0 文件 NVConfig.sys size=1 available=0 文件 RPGReader.sys size=14346 available=0 文件 Demo.rxe size=9436 available=0 文件 Try-Touch.rtm size=3788 available=0 文件 Try-Light.rtm size=4456 available=0 文件 Try-Sound.rtm size=6864 available=0 文件 Try-Ultrasonic.rtm size=3756 available=0 文件 Try-Motor.rtm size=2630 available=0 文件 Woops.rso size=4699 available=0 文件 faceopen.ric size=316 available=0 文件 faceclosed.ric size=316 available=0 文件 ! Startup.rso size=8161 available=0 文件 ! Click.rso size=451 available=0 文件 ! Attention.rso size=1755 available=0 实际上这种种的 API 调用,最终实现上都是通过相同的一套通讯协议完成的,也就是前面 NXT 信息中 protocol 版本所指定的协议。LEGO 在 Bluetooth Developer Kit (BDK) 中有一份 Appendix 1-LEGO MINDSTORMS NXT Communication protocol 文档详细介绍了这个协议。 因此我们也可以直接使用此协议,向 NXT 发送命令,这也是 Microsoft Robotics Studio 之所以能通过蓝牙方式,跳过必须安装驱动的限制的原因。 void dump(iNXT& nxt) { // 打印 NXT 基本信息 ViUInt8 cmdPlayTone[] = { 0x03, 0x00, 0x18, 0x10, 0x00 }; nxt.sendDirectCommand( false /* 此命令不需要等待响应 */, reinterpret_cast< ViByte* >( cmdPlayTone ), sizeof( cmdPlayTone ), NULL, 0 /* 没有响应缓冲区 */, status ); } 上述代码会向 NXT 设备发送一个 Play Tone 的直接命令,发出一个短促的响声。回头有机会再详细分析吧 :) September 26 NXT 学习笔记 [2] 运行时数据 前面曾经提到 NXT 的可执行文件由运行时数据 (Run-time Data)、调度 (Scheduling) 和字节码 (ByteCodes) 三部分组成。其中运行时数据可进一步分为数据空间目录 (Dataspace table of contents - DSTOC) 和数据空间缺省值 (Dataspace Defaults)。VM 在加载一个可执行文件时,会在对应的 32K 内存中划出一块专门用于存储数据空间 (Dataspace),这块内存是由一系列不同类型的数据项组成,直接对应与可执行文件中的 DSTOC,如果有缺省值的话则自动进行初始化。因而后期字节码访问数据空间时,可以直接使用进程唯一的 DSTOC 索引号定位实际数据。与字节码指令类似,可执行文件中的 DSTOC 和缺省值不被加载到内存中,直接在 Flash 卡上进行地址映射。 而在数据空间中存储的数据项,其类型也是由 VM 预定义好的,直接在指令一级提供支持。 最常见的简单数据类型就是整数了,NXT 支持 8/16/32 位有符号或无符号整数,以及一系列针对整数的算术操作符。布尔类型在 NXT 中直接以 unsigned byte 表示,0 表示 FALSE,其它值均为 TRUE。而浮点数、分数甚至复数之类都不在直接支持的范畴内,不过可以通过整数运算进行模拟,只是语法一级的问题罢了。 较为复杂的数据类型是集合类型,NXT 提供内建数组 (array) 和簇 (cluster) 的支持。 数组 (array) 是一组具有相同数据类型子元素的集合,并支持在运行时调整大小。例如可以定义一个长度为 0 的数组表示一组点,在运行时根据传感器采样进行扩展和填充。ASCII 字符串是一种特殊的 unsigned byte 数组,为兼容 C 的习惯其末尾会有一个 NULL(0) 作为结束符。 簇 (cluster) 这是类似于其它语言中结构 (struct) 的概念,由一组不同类型子元素组合而成,一般被用于将相关数据封装到一起。 NXT 中聚合类型是可以相互嵌套的,可以在一个簇里面定义几个不同类型的数组,也可以在将数组的元素类型定义为数组来模拟多维数组。 此外 NXT 为多任务支持还提供了一种互斥量 (mutex) 类型。这种类型虽然实现上由一个 32bit 整数表示,但不能被用作聚集类型的子元素,因此只能独立存在并通过内建 OP_ACQUIRE/OP_RELEASE 指令进行操作。 既然 DSTOC 将程序可用到的数据及其类型都已定义清楚,从 VM 角度就可以将之进一步分为运行时内存布局不会发生变化的静态数据,以及运行时需要进行调整的动态数据。前者包括 DSTOC 中绝大多数类型的数据,它们在编译期就有编译器根据情况确定了布局;后者则包括所有的数组类型,因为 NXT 提供动态调整数组大小的能力,所以它们在 DSTOC 中只是一个入口,由 VM 在运行器进行管理。 落实到实现上,VM 将静态数据和动态数据放到两个不同的内存池中,前者在内存低地址区域固定,后者在内存高地址区域向下增长。如果动态内存区域超出内存池限额,系统会显示一个 File Error 的错误(不知道原因的话谁猜得出来)。 而对程序的字节码来说,这些都是透明的,指令只需要使用全局唯一的 DSTOC 索引号即可访问到相应的数据项 数据项地址 = 数据空间开始地址 + DSTOC[数据空间索引].offset 对 VM 来说,静态数据基本上在编译期就确定了,它所需要关心的只是如何对动态数据进行管理。NXT 中引入了一个 dope vector (DV) 的概念,来针对每个动态数据项的变化进行跟踪。DV 结构的伪代码如下: typedef struct DopeVector { short Offset, // 数组数据在内存中的偏移 short ElementSize, // 数组子元素的大小 short ElementCount, // 数组子元素的个数 short BackPointer, // 未用,用于建立双向链表? short LinkIndex // 下一个 DV 的索引 } DV, *PDV; VM 维护了一个全局唯一的 dope vector array (DVA) 来跟踪在动态数据区域中分配的所有内存,所有 DV 又通过 DV.LinkIndex 以链表形式组织在一起,便于系统进行遍历和维护。当然这个 DVA 的存在对字节码来说是完全透明的,只是最大限度利用现有机制来实现自我管理。 当字节码需要通过 DSTOC 索引访问数组时,通过 DSTOC 获得的内存偏移地址实际上指向的是一个 DV 索引,然后再根据 DV 索引从 DVA 中获取到实际的数组偏移地址。计算的方式如下: DV 索引地址 = 数据空间开始地址 + DSTOC[数据空间索引].offset 数据项地址 = 数据空间开始地址 + DVA[DV 索引].offset 通过这样一个两级索引定位,VM 可以在不需要对 DSTOC 和字节码做任何调整的情况下,根据运行时指令的需求修改数组的大小,因此所有的 DV 索引会跟 DSTOC 中的数组数据项绑定。 此外对于嵌套实现的多维数组,除了第一维数组是在 DSTOC 中固定死的,其它每个子数组实际上在上一维中只是一个 DV 而已,字节码可以通过 OP_INDEX 等指令间接任意访问。 NXT 学习笔记 [1] 系统架构 熟悉了基本功能,接着可以看看一些技术层面的内容了,呵呵,毕竟买回来不是看手册拼模型的 :P
Lego 这方面非常厚道,从系统结构到指令集,设置关键部件的线路图都有提供,看来是拼足了劲要把这摊子事搞大啊,呵呵
这个手册虽然号称是可执行文件标准,但实际上对整个系统架构都做了比较详细的介绍。下面的大部分内容都是我通过阅读此手册总结或者翻译过来的,呵呵
NXT 的运算核心是一个 32bit 的 ARM7TDMI 嵌入式 RISC 处理器,主频一般在 115MHz(0.18 μm)-133MHz(0.13 μm) 之间,理论上回头可以自己把它换成 236MHz(90 nm) 的 ARM7,甚至接口兼容的其它型号,ARM 9/9E/10 都提供对 ARM7 程序的二进制兼容性。存储则包括 256K Flash 和 64K RAM,容量上跟现在动辄几 G 的系统没有可比性,不过对于嵌入式系统来说勉强也算够用。此外还集成了一个 8bit 的 AVR 嵌入式 RISC 处理器,以及对应的 4K Flash 和 512B RAM (-_-b),估计是用于 I/O 或步进电机控制。
NXT 的软件系统,主要是一个扮演 OS 角色的 ANSI C 编写的 Frimware,提供对各种传感器/步进电机的控制,并为二次开发提供虚拟机(VM)支持。用户编写的程序会被编译成 NXT 的字节码(Byte Code)指令,以后缀为 .RXE 的特定可执行文件格式存储在 Flash 上,在执行时由 VM 加载并解析,并提供 32K 的 RAM 供执行。 NXT 的可执行文件主要分为运行时数据 (Run-time Data)、调度 (Scheduling) 和字节码 (ByteCodes) 三部分。而在执行时 VM 会根据需求,为前两部分提供对应的 RAM 空间,存储可变的运行时状态信息。 NXT 的字节码允许用户执行常规的数学/逻辑运算、指令流控制以及 I/O 操作等等,基本上是一个精简版本的 RISC 指令集。这些指令将根据功能以名为 clump 的方式组织在一起,类似子过程。NXT 中较为特殊的指令调度信息,将提供各个 clump 分别包括哪些字节码、clump 的运行时状态以及当前 clump 执行后应加载的其它 clump。VM 会根据这些记录,在 clump 所依赖数据就绪后再调用之。clump 之间也可以相互调用,但这个调用过程必须是同步的,调用者需挂起直到调用返回。此外 clump 本身是不可重入的,如果两个不同 clump 需要调用一个共享 clump,则调用者必须通过互斥量 (Mutex) 确保不会重入,因此 clump 调用自身的递归操作也是不被支持的。 NXT 之所以采用这样一个机制,估计是放弃了基于堆栈的思路,通过 clump 为单位来简化 VM 的设计。与之相对应的是其可视化编程环境中,常用操作都是被一个个小方块(Lego 积木?)封装起来,然后通过拖拉和逻辑组合来实现算法;这样一来实现上就较为简单,常用操作映射到 clump,组合逻辑映射到调度 (Scheduling)。不过基于其字节码支持访问内存的考虑,理论上不排除能对其架构进行改进,通过内存操作模仿堆栈来实现 clump 重入和递归的可能性。 此外一个好消息是 VM 能够提供多个 clump 并行执行的支持,完成多个任务的 clump 在数据就绪的情况下能够逻辑上并行执行;同时一个坏消息则是虽然 VM 能在指令一级帮助解决资源冲突,但还是需要自己来判断并处理竞争条件。 NXT 的程序运行可以分为四个阶段: 1.验证 (Validation): 加载文件并验证版本和其它文件头信息 2.激活 (Activation): 在 RAM 中分配并初始化运行时数据 3.执行 (Execution): 解析文件中的字节码指令,根据调度信息运行相应 clump 4.停止 (Deactivation): 重新初始化所有 I/O 系统和 RAM 中的数据结构 值得注意的是在执行过程中,所有的字节码都不会被载入到内存,而是通过直接映射 Flash 卡到地址空间完成。这样的好处是内存只被用来保存可变得程序数据,坏处则是代码在执行的过程中无法被改变。 to be continue...
成功进入机器人时代 :D 经过在 eBay 上近半个月的坚苦搏杀,终于赶在十一前拿到了向往已久的 Lego Mindstorms NXT。与那些貌似有趣但本质上只会摇尾巴的所谓娱乐机器人不同,这次 Lego 发布的 NXT 绝对是一个划时代的产品。内建 32bit ARM7 处理器,自带光、声、雷达和接触传感器,最大三路步进电机输出,支持蓝牙/USB接口。最为关键的是,除了自带的图形化可视编程界面外,提供了较为完整的各种开发SDK,而 MS 更是推出了支持此平台的基于 C# 的开发环境 Microsoft Robotics studio。这意味着 NXT 已经脱离了一个玩具的范畴,第一次让机器人变得平民化,是迈向机器人时代的坚实的一步。回想当年 Intel 推出的 8086/8088 正是扮演了类似的角色,让 PC 真正成为平民化的工具,相信机器人时代的到来也不再是科幻小说中的情节。
说了半天废话,让我们先来看看 NXT 的第一印象,好大一个包装盒,呵呵
完整版本的 NXT 在 Lego 自己网站上标价 $250,但实际上通过 eBay 等方式 $200-$210 就能拿下,不过比较郁闷的是从米国邮寄回来大概需要 $50-$60,如果有朋友能从国外或香港带是最合适不过了。 拆开大盒子,其实里面东西不算太多。最多的是大把大把的 Lego 塑料积木,这是组成各种不同机器人造型的基础原料,种类我可以负责任的说是相当的多,光十字形连接杆就有9种不同长度,各种转接头的种类更是繁多,搞得我大部分时间用在寻找合适的元件上,回头得去买个厨房用的佐料盒分门别类 :P
除此之外在几个小盒子里面装的是 NXT 的核心部件,缺省自带一个主机兼控制器、四种传感器、三个步进电机以及一对乱七八糟的连接线啥的。具体的功效回头等我弄明白了再单独解说。:P
再来一张核心部件的分解图,呵呵 主机上提供是个传感器输入接口(1,2,3,4),三个步进电机输出接口(A, B, C),一个 USB 接口用于从 PC 上下载程序,一个小 LCD 黑白屏用于基本控制和状态显示,此外还内建蓝牙接口用于与外部通讯。能源部分使用六节 5 号电池,没有发现外接电源的接口。可以说整个系统设计的非常简介,在满足基本功能的前提下一点多余都没有,回头有时间将之拆开再仔细分析一把 :D 作为老牌的玩具厂商,LEGO 的可视化编程环境做的非常简单易用。从左边工具条中将各种常见操作(如A口步进电机正向转3圈,或者声音传感器收到一定大小的声音)拖动到中央工作区,然后用逻辑条件进行组合,最后点击一个 Download 按钮就会自动编译并下载到 NXT 的 Flash 上。此外右边还通过 Flash 方式提供了非常详细的 Step by Step 快速入门,可以根据上面的说明快速熟悉各种部件和环境的使用方法。 这是我根据 Quick Start 拼装出来的 TriBot 机器人,呵呵。它首先会用雷达探测面前是否有目标(一个蓝色小球);如果有的话就冲过去,直到碰撞传感器告诉它已经到跟前;接着它会等你的暗号,击掌一声后,用两个大钳子夹起球;掉转180度最大速度跑开(没人追你啊);碰到地上有条黑线后放下球。
呵呵,功能是弱了点,不过好歹是偶的 NXT 第一次露面,来两张特写先 :D June 05 泸沽湖 —— 亚丁 D -1 在我去过的诸多古城或号称古城的地方之中,丽江的大研古城是保护的相对完好的。但这种完好也仅仅体现在建筑风格上,真正的文化的核心————人,实际上也只是保持着商业上的完美。因此除了第一次去丽江时还饶有兴趣的逛逛街市,后面再去时基本上都刻意避开人群,免得看那些已经扮成当地人的外地人,和另外一些想扮成当地人的外地人,在这个纳西主题公园里面,贩卖着廉价的所谓文化。 不过在这熙熙攘攘的闹市之中,也是有不少闹中取静的好去处。例如下午四五点时,爬到狮子山万古楼下找个木椅、亦或躲到黄山公园的凉棚中,砌上一壶清茶、摊开一本好书、换上一副好心情,在从云中洒下的缕缕昔阳中,体会那种宁静而志远的心境。如果时节好的时候,恰好还能碰到低而厚重的雨云,从远处的山边慢慢笼罩过半壁古城,一面是明媚的阳光,一面是茫茫的细雨,不一会儿就云淡风清又一捧彩虹横挂枝头,就如许巍在歌中唱到的一样: ... 好像梦里醒来 见到清新的世界 此刻寂静的心 自在又安祥 我心深处的孤独渴望 我曾莫名的无尽等待 就这样消逝风里风里 就这样消逝风里 无踪影 ... 待天稍稍有些黑了,可以快步穿过四方街上目光呆滞的人群,去七一街抢上一碗川西的肥肠粉。如果食欲好,则会多走几步到木府边上的阿哩哩,尝尝阿呆自酿的青梅酒和姥姥的臭豆腐烧牛肉,这是丽江古城里面为数不多可以算作美食的 :P 说起阿哩哩的阿呆,这又是另一个故事了,有兴趣可以逛逛他的 blog (http://alili.tianyablog.com/)。 如果还是不想回去,可以再爬回狮子山,在半山的青年旅馆,要上一杯云南本地的小粒咖啡,坐在长长的木廊边,凝视着山下的万家灯火。四方街上的熊熊火焰与欢闹人群,给夜幕笼罩下的古城凭增一份奢靡。而在此时,摇曳的灯火和喧嚣的歌声,在黑暗中被放大并扭曲,让距离和时间都慢慢变得不真实起来。 不过这次我还是选择呆在客栈里面调整心态,以尽早从快节奏的都市生活切换到慢节奏的度假生活。 睡到自然醒,洗个热水澡,然后靸着拖鞋,窝在院里的沙发中,看着那只刚刚几个月的小狗,步履蹒跚在院中乱窜。 这家客栈有个小而精致的院落,可以看出以前的房东下过一番心思,虽然面积不大但各种植物错落有致,四季常青且花期相接。一面客房门前立着大片的大叶爬山虎,遮住烈日的同时不忘放入丝丝缕缕的光斑;对过是一棵挺拔的樱花树,不巧刚刚过了花期,只剩一些被雨打过的残花;四周则是一些叫不上名字的高矮植物,零零散散但并不杂乱的点缀在院中。青石和石子铺就的地面古朴而典雅,加上仿古的红漆大门和黄铜门环,真有点古意但并不显得做作。 在院里发了会儿呆翻了会儿书,趁天气凉爽到菜市场去溜达了一圈。如果说有什么地方能够让你直接感受到生活真实,那应该就是菜市场或农贸市场了。四处看看,给朋友带了条当地土烟,本还想整个西瓜回去,掂了掂分量只好放弃 :S 估计是因为老板怕吵,客栈里没有自动洗衣机,于是拿着盆子杀到白马龙潭寺门口洗衣服去 :S 跟一群大妈大婶一起,蹲在三眼井的末端,使尽十八般手艺慢慢折腾那几件衣物,全然不顾旁边参观的旅客诧异的目光。这三眼井历经百年的沧桑,蹲在井边的人换了一代又一代,真让我有点时光回朔的感觉 :) 晚上本想请老板出去喝酒,结果反而被老板拉回家蹭饭,完了又是一人一杯清茶,有一搭没一搭的聊天。虽然有李江山这样一个豪气的汉语名字,但他实际上内心并不适应丽江的商业气氛,对我这种扛着大包的背包组也有着独特的偏爱。在丽江生活几年之后,实际上他的心还留在虎跳峡的群山之中。而与我一样无奈的是,也只能通过不断的抱怨,和偶尔的调剂来缓解,并无法去打破或改变什么。而我这样的背包客,在能唤起他对往日回忆的同时,共同的对大自然的热爱和对生活的感恩,也使得交流起来格外容易。也许这就是现代人的悲哀,在现实和理想之间无法自己把握方向。 June 01 泸沽湖 —— 亚丁 D -2 本来是不打算写游记的,一来自己比较懒而且号称比较忙,二来一直以为旅程是无法记述而只能体会的。但架不住诸多朋友的反复关心,只好把一些零散的感想记录一下,希望能让朋友们分享我的快乐 :)
D -2
直到再一次踏出丽江机场的大门,在寂静星空照耀和凉爽的夜风吹拂下,呼吸着掺杂着沁人心脾的淡淡草香的气息,才真正意识到我又回到这个梦中的地方。虽然此时还不知我的大包被转运到哪架飞机上,也不知未来几天的旅程该如何安排,但现在想什么也没用,当务之急是找个小酒馆吃点新鲜的菜肴,加上一瓶冰镇的本地啤酒。:P
也许这就是我喜欢走长线的原因之一,从一个未知到另一个未知。 虽然从一个多月以前就开始准备这趟行程,但我并无意去把每一个细节都安排清楚。毕竟长线中有太多的不确定因素,明确方向然后坚定的前行是我唯一可控的。而在此过程中碰到的问题和解决问题的过程,往往才是旅程中最有趣的谈资。 就好比今天的旅程:早上还在公司翘着二郎腿开周年庆典大会,中午就匆匆忙忙打车去机场去赶堵车实况,好容易提前5分钟赶到换了登机牌后发现飞机晚点,百无聊赖在候机厅看完所有杂志后,到了丽江机场只好再一路狂奔,可还是晚了半分种才注册完毕,托运行礼却因为还没取到,只能跟着后面的航班转运,还免费获赠一张机场对我行礼不负责任的免责声明。于是我只好在饿着坐了一天飞机后,孤零零一个人躺在候机厅沙发上,等着还不知道在哪儿的行礼 :S 用当时某人给我回的短信的话来说,就是每一个精彩的旅程都有一个糟糕的开始,哈哈,我定义他在夸奖我 :D 也许是老天想多给我一个理由留在丽江,机场工作人员告诉我行礼要跟午夜最后一趟航班过来,于是我用最快的速度冲出机场,快步走向向往已久的最近的一个小酒馆,哈哈,一份火腿、一盆菜汤、一瓶啤酒、一碗米饭,完了再来上一壶龙井,开始有点找到度假的感觉了。 :P 因为准备转长途车去泸沽湖,决定住客运站附近的白马龙潭客栈,打电话跟老板套了套交情并订了床位,领到行礼后跟几个成都来的朋友拼车回丽江,顺道帮客栈老板拉了一票生意,忽悠了一车人直接开了过去。 说起这个客栈老板也是个很有趣的人,父母是在虎跳峡开著名的纳西雅阁客栈,女朋友是以前在虎跳峡客栈拣到的外地游客,然后家族业务拓展到丽江租了个小院落。沉默寡言但却很风趣,待人宽厚但疾恶如仇,没事就坐在院中沙发里跟游客聊天,时不时会掏出一些有趣的照片,或者告诉你一条新的线路,真有点象某某游戏里面客栈的提供游戏线索 NPC,哈哈。 既然到客栈已经一点多了,估计明天也是走不了的,索性跟这哥们一人一杯茶聊上了。春节从虎跳回来去梅里之前,就是住在他这儿,所以先给他灌输了一番梅里的大好形势,然后又听他侃了一通黎明和老君山,看来我的 todo list 又要加长了。 看看天色啥也没看出来,只好看看表已经不早了,于是约着明天找地方请他喝酒,然后洗洗睡一夜无梦。 February 13 被遗忘的寂静村落 从熙熙攘攘的丽江大研古城出发,半天的长途车又加上三个多小时颠簸的土路,才来到这个山腰上似乎被遗忘的寂静村落。 这就是此行的目的地,云南省宁蒗县烂泥菁乡大二地行政村的小二地自然村,一个只有65户309人的小村落,大部分居民是彝族。
因为此行的主要目的是考察教育方面的情况,下车第一件事就是寻找正在使用的校舍。但在当地老乡的带领下,看到的只能算一个校舍的基址了。 因为原本校舍过于简陋,已经被拆掉。而二三十个学龄儿童只能挤在这样一间破旧的木屋里面,在一位外村派来的教师的指导下艰苦的学习。
虽然来之前做好了充分的思想准备,但看到这样简陋的教学设置,心里还是感觉堵得慌。 而那些孩子却似乎早就习以为常,活泼的在“校舍”上爬入爬出,仿佛要向我展示这就是他们的天地。 在与当地村民和孩子的攀谈中,我了解到他们主要靠种田为生。但高寒的气候、贫瘠的土地和落后的耕种方法,导致仅能收获少许土豆、玉米和青稞作为口粮。一日三餐也只能以土豆作为食物,唯一的奢侈品是年节时宰杀的猪肉晒制的腊肉,和偶尔获取到的大米。没错,就是我们平时挑三拣四的大米,被他们作为一种奢侈品进行消费。 “家徒四壁”这个我们从小学课本忆苦思甜文章中熟悉到的成语,是当地居民家里的真实写照。一个火炕、一口大锅,这就是一个普通居民家的客厅。 外表看起来貌似光鲜的家庭,最大的经济来源就是门口这几只羊了。村民平均年收入,出去作为口粮的农作物,大概只有200人民币左右;每学期55人民币的学费,就已使的数个孩子因为经济原因辍学。而全村最高学历的,是一位刚刚搬来不就的高二在读学生。需要读完小甚至初中的学生,要跟不远处根本没有学校的大平子村同学一起,步行5公里山路到大二地村去住读。 从丽江这样的花花世界,来到这个被遗忘的村落,再看着路边村民眼中呆滞的目光,巨大的反差给人一种非常压抑的感觉。 但无论外界条件多么艰难,孩子们眼中的纯真是相同的,从那份企盼中你能感受到希望,获得希望去改变这一切的力量。 虽然我们能做的可能很少,要改变这个状况可能很难,需要耗费的时间可能很长,但我们毕竟开始了,开始做我们力所能及的事情。哪怕这个过程再艰难,只要我们开始去做,坚持去做就一定会有成果。 对我们这可能只是今年的一座校舍,但对这些孩子来说,这可能是一生一次的机会。那位可能初中肆业去打工的小男孩,那位可能高小肆业回家操持家务的小女孩,可能因为我们现在的协助,成为能够更加强有力把握自己命运的主人。 我们所付出的,仅仅是一件喜欢的衣服,一个电脑配件,几顿腐败的晚餐,得到的则将是我们与他们共同的希望。
January 24 又是一年佳节时 听到窗外爆竹声声,才恍然发现又是一年佳节时。
已不记得从哪年开始,对这种节日的热闹氛围日渐生分。在家人围坐在电视前看春晚时,我总是喜欢躲在房间里静静的听着歌,写着一些有用或没用的代码和文章。似乎在这种时候,那种孤寂中的空灵的感觉往往能让我状态奇佳。
依稀记得高考前的那个除夕,在写一个 DOS 下的工具程序。几千行的汇编加上所知的各种 TSR 技巧,基本上是我在 DOS 下编码的顶峰,也让我第一次体会到那种落寞的感觉。
大学毕业前那年的春节,似乎正在写那个又臭又长的《CLR 结构分析》系列。几十M的英文资料,前后几万字的文章和大几百K的代码,也是我现在所无法企及的。
似乎只有在指尖不停的跳动时,在耗尽脑力的疲惫后,才能真实体验到自我的真实存在。
而今年的春节,我选择了行走,不知在虎跳那秀美的山河中,又是何等的一番感受呢?
又是一年佳节时……期待 November 20 透视 Java 自从全面进入网络购物时代之后,唯一能保留下来逛商店习惯的就是逛书店了。虽然不再象以前那样每次大包小包往回扛,但却很享受书店里那种氛围。虽然在一堆一堆的烂书中惹眼的还是那些熟悉面孔,但常常能在不经意中发现几个漏网之鱼。
例如前几个月曾在书架角落里面翻出《精通 AspectJ》、《事务性 COM+ 编程》、《应用框架的设计与实现》等几本计划外的好书 :D 加上今天的意外收获《透视 Java》,都是些潜在读者群较小,内容覆盖面不大但较为深入的书,通俗的话说就是识货的人可能不多,呵呵。不过相对于那些有书店和出版社背景的热抄书籍,这些相对冷门的书往往更能脚踏实地一些。
例如这本清华出的《透视 Java ——反编译、修补和逆向技术》,虽然在技术点上讲的并不非常深入,但在逆向工程的Java领域,却能够非常全面的覆盖从混淆到反编译各个环节。
相对《黑客反汇编揭密》这样的底层剖析书籍来说,此书更偏向于技术方向的介绍,以及相关现有工具和实现的比较。第2-5章简单介绍了Java领域逆向工程一些基本的概念和方法;第6-13章则扩展到测试、调试和性能调优等领域;第14-18章提及了一些较为深入的话题。
晚上看了一下第一部分,大概翻了一下后面的内容,感觉此书的定位还是很明确的。属于让圈外人能够走马观花对整个情况有所了解,圈内人则可以当作饭后消遣了解一下Java领域的特定情况。平均4-5页纸的章节,基本上也能做到言之有物,乌克兰人在这方面还是挺厚道的。毕竟折腾逆向工程的人本来就少,大部分对这方面感兴趣的朋友目的性都很强。真要是把每一点展开来讲,估计除非是真正做这方面的人,否则都会被侃晕过去,呵呵。如何把问题展开来讲,又能收的住,而且言之有物,其实是很见功底的,总不能都像《黑客反汇编揭密》那样一个章节写4xx页吧,呵呵。
因此此书的优点和缺点都是讲的太杂了,横的从混淆、反编译扯到跟踪和性能分析,纵的从包、类一级到字节码和JNI。可以说把这个小小领域里面可能涉及到的名词和概念一股脑都打包给了读者。如果不是在 Java 领域折腾过一段时间,并且有相当底层知识和其他领域基本概念的话,估计这么大信息量还真不一定撑的住,呵呵。不过好处是通读一遍把七七八八概念一记,出去忽悠人的话估计挺管用 :D
另外一个亮点是此书相对来说比较新,加上作者知识面比较广,非常适合希望大概了解这个领域,并寻找学习和研究切入点的同学。毕竟这个领域成型的资料相对较少,有这么个roadmap未尝不是件好事。因此单独把此书拿出来 blog 之,如果大家对其中某些技术点有兴趣,可以一起讨论共同学习 :D
October 24 Java 线程锁定优化策略Brian Goetz 最近在其 dw Java theory and practice 专栏 里发表了几篇有趣的文章,介绍了 Sun HotSpot JVM 在以后几个版本中,对锁定等性能优化的思路。 Synchronization optimizations in Mustang 其中核心的思路是将锁分为 contended 和 uncontended 两类来分别优化。根据 80/20 经验原则和一些实测数据,大多数的线程锁实际上都只是预防性的。例如被广泛使用的 Vector 类中,绝大多数方法都被缺省加上 synchronized 修饰符以避免多线程问题,虽然这能够最大限度降低潜在的问题,但相应付出的代价也是巨大的。因为大量的实际使用都是局部或者不会涉及线程同步情况的,例如下面这种情况:
要缓解此问题,一方面可以从使用者角度通过库的选择来避免无效锁定,另一方面则可以由 JVM 根据行为进行锁定优化。 对前者来说,随着 JDK 的发展可以说灵活性大大增强。例如选择性使用 ArrayList 和 Collections.synchronizedList 可以获得类似的能力,但并不用付出不必要的代价;同时也可以使用已成为 JDK 1.5 标准库的 concurrent 库,根据多线程的使用情况进行锁定优化。这方面 C/C++ 库的设计理念非常优秀,那就是决不为用不到的东西付出代价。 对后者来说,则可以发挥 JVM 与传统静态编译相比最大的优势,根据运行时行为进行优化。 Lock elision 的思路,就是通过 escape 分析找出根本不存在多线程引用可能性的锁。对这种情况 Java 语言规范中明确允许进行优化,直接去掉不必要的锁定语义。而最大的问题则在于如何进行 escape 分析,并找到锁的可优化范围。因为相对于基于栈的 C/C++ 来说,Java 内存模型目前是完全基于堆的,每个对象都在全局堆中可以由任何外部线程访问。 有兴趣深入研究的话,可以看看下面这两篇文章 http://www.research.ibm.com/people/g/gupta/toplas03.pdf http://www.research.ibm.com/people/j/jdchoi/escape-pointsto.html Adaptive locking 的思路,则是将 contended 的锁定按锁定时间进一步细分。因为对大多数锁定来说,锁定的时间都是非常短的,例如很多 get/set 方法,以及简单的内存操作。对这些方法来说,如果要完成一个完整的锁定流程,需要涉及到对象锁状态更新、等待线程对象构建、甚至与操作系统一级的线程调度打交道。相对于实际要工作所耗费的时间来说,锁定这个操作自身消耗的资源可能反而是大头。对这些情况而言,与其建立一个完整的锁上下文,不如直接用 spin 锁机制进行等待:
如果一定时间或尝试次数后还是无法获得锁,则 JVM 可以将此锁的类型转换为长时间的锁,然后构造完整的锁上下文进行管理。而这种优化方法,最能够体现动态 JIT 相对于传统静态编译器的优势,因为这些执行行为的信息是无法静态获取的。 Lock coarsening 的思路,则是通过将临近的几个相同锁定进行合并,以减少不必要的重复锁定操作。这种优化的原理是基于重复方法往往同时出现的模式,例如下面这种常见情况
对这种情况来说,每次调用 add 方法进行锁定和解锁是没必要的,JVM 可以根据运行时信息选择性合并同类型锁。随着现在机器自动代码生成的广泛引用,可以预期这种基于行为对锁进行合并的思路会非常有用。 此外 David Dagastine 在其 blog 上也对 Java 同步锁优化进行了讨论 关于 Escape 分析 Brian Goetz 还在另外一篇讨论 Java 性能问题误解的文章中有所提及。 Urban performance legends Urban performance legends, revisited 文中讨论了一些对 Java 性能问题的常见误解,其中很多问题是旧版本 JVM 和 Java 库中存在的,随着 JDK 的不断更新已经不同程度上得到缓解,例如下述等等问题。 Synchronization is really slow Declaring classes or methods final makes them faster Immutable objects are bad for performance 这些问题的出现,往往是因为使用者对 JVM 的实现和优化思路不熟悉导致的。实际上 HotSpot 自从 JDK 1.3 版本以后,实际上有了非常大的进步,无论是从功能还是性能上,都已经远远超出了某些人的预期。而在可以预见的 Mustang 和 Dolphin 中,更高级和动态的优化还会不断加入进来,并从 JVM 一级对应用产生透明的性能提升。 例如基于 Escape 分析的栈分配优化,有可能一改 JVM 只从堆中建立对象的传统,直接将动态分析得到的仅由线程自身使用的对象,以类似 C/C++ 的方式直接在栈中分配。这一改进如果能够成熟,对 JVM 性能的提升将是非常可观的。因为对现代 CPU 架构来说,提升性能的一个关键问题就在于 Cache 的使用。相对于位置离散的堆来说,栈一般都能确保在缓存中的命中率;而在堆中分配内存,哪怕分配性能很高,但因为缓存往往无法命中,带来的性能缺失会非常大。而且使用基于栈的内存管理模式,还可以享受到析构对象的快捷性,函数调用完成后,只需要将原函数的栈顶指针弹出即可释放所有临时对象。 如果对此方面有兴趣,可以看看 Brian Goetz 这个非常不错的 Java theory and practice 专栏 ,下面是其中关于性能优化的其他文章 Performance management -- do you have a plan? Is that your final answer? To mutate or not to mutate? September 18 《应用框架的设计与实现——.NET平台》 用一周左右时间零零碎碎抽空把《应用框架的设计与实现——.NET平台》一书翻了一遍,感觉作者 Xin Chen 的确有一些自己的想法,但有些问题并没有想的非常明白。在应用框架的层面来说,书中涉及的范围基本上是较为全面的,但就如其示例实现 SAF (Simplified Application Framework) 的名字一样,有些问题的思考和解决方法过于简单。
从全书的整体布局来说,首先是作者的全局理解不错,前三章对应用框架的分析和理解大部分还是很到位的。相对很多上来就大谈实现技术的所谓企业级开发书籍来说,能想到并且言之有物的将 blueprint 先说明白,说明作者在整体框架的发展上有过较多的思考。而实际上这个层面的思考,是 Windows 平台下企业级开发所最为欠缺的。而从第4章到第15章,每章介绍一个框架的主要构成部分,先原理后实现还是挺不错的,最起码能够浅显易懂,呵呵。不过个人感觉对基础性支持介绍的太多了,不如改为结合实际项目的应用场景分析。毕竟能有目的性看这种书籍的读者,大部分都不会存在技术性知识的缺乏。也不知是否书籍定位的问题,感觉很多问题作者是想到但并没有讲明白,给出的解决方法也仅仅停留在思路层面,很多地方缺乏实用性和完整性。
第4章介绍类工厂服务,给出一个基于 reflection 机制的可配置类工厂实现思路。不过居然没有提及任何 IoC 方面的知识,真难以想象这是一本2004年才出版的书。毕竟将构建对象与使用对象解耦,只是容器最基本的一个层面的功能,更高层面的对象依赖关系管理、配置管理、AOP 支持以及服务生命期管理等等,才是一个真正的企业级容器所必须的。
第5章介绍缓存服务,给出一个基于 hashtable 的缓存实现思路。值得肯定的是明确提出了存储策略的引入,这为平滑支持基于外部存储和基于cluster 的缓存提供了思路,实际上 Java 阵营的大部分 Cache 实现也是类似。亮点是采用 XML DOM 树作为 key 的存储机制的思路,通过 xpath 来定位存储内容,相对于平面的基于 key 对象自身可比较性的思路,有着其特有的易用性。可惜给出的参考实现只能用简陋来形容,作为学习用的范例还凑合。
第6章介绍配置服务,基本上就是基于 CLR 现有配置架构,没有太多新意。直接用 Enterpise Library 的相关 Block 是更好的选择。感觉 windows 平台的开发者在 XML 处理方面的思路太僵化了,来来去去都是 MS 那套机制,不像 Java 阵营百花齐放,digester 与 xstream 齐飞,不同模式让人有选择余地。
第7章介绍事件通知服务,给出一个集中事件分发服务的雏形。这块对事件分发和异步执行的好多想法很不错,但实现的方式太简单了。虽然统一的中间服务简化编程模型,但在参数传递、传送拓扑、底层协议等等很多方面受限。某些时候网状拓扑比这种星状拓扑的灵活性更大,或者基于总线的思路也更先进。这方面 ESB 的思路应该是大势所趋,MS 方面就看 Indigo 的本事了。
第8章介绍 Windows 服务,说白了就是用一个 Windows Service 做容器来运行任务。能明确提出这一点来,可以说作者对如何将应用框架与 Windows 平台结合做过一些思考,可惜仅仅在这儿有所体现,而可以做努力的方向还很多。而且其对服务支持仅仅停留在运行容器支持层面,基本上没有考虑如何在服务与服务之间、服务与使用者之间,提供无缝交互的支持,而隔离的服务对于框架来说是孤立的。
第9章介绍消息队列服务,基本上就是对 MSMQ 和 MQSeries 的简单封装。如果能够考虑消息路由、同步模式等等高级特性,可能会更加实用一些。这一章实际上可以跟事件通知服务合并到一起,毕竟他们是为了解决类似的问题,不应该仅仅以实现技术来划分。
第10、11、12章介绍授权、身份验证、加密服务,基本上就是对 CLR 现有机制的包装和整理。亮点是明确提出应该将授权等定义与具体代码实现分离,并给出一个简单但可用的实现。真不明白这么简单的一个道理,为什么 MS 到现在就是不明白 :S 此外这儿单独提出了对身份扮演的支持,但在与架构一级的整合上力度不够,仅仅给出实现一级的方法。
第13章介绍事件服务,主要包括如何通过 COM+/DTC 使用分布式事务。亮点是明确提出抽取独立调度类提供 COM+ 事务与隔离性支持,避免在实现代码一级静态定义。不过作者似乎对 COM+ 1.5 不是太熟悉,居然没有用新增的服务域的特性。而且他在抽取事务与隔离性支持时,使用了设计上巨龌龊的枚举组合方法,用 4x5=20 个服务类来表示所有组合。其实完全可以通过动态类生成方式,提供更为幽雅的实现。而且事务的定义,也应该有相应的配置文件级支持,作者好像把这块也忽略掉了,而前面几个模块缺都有,真是奇怪。
第14、15章介绍文档层和工作流服务,基本上就只是简单介绍了一下概念,感觉是敷衍了事 :S 而且两章罗罗嗦嗦说了半天,说白了都是用的责任链的模式,还挂着个工作流的羊头。估计可能是因为作者有 biztalk 的背景,这部分大部分工作用现成工具完成了吧。
总体来说此书还是一本非常值得一读的入门级应用框架设计书籍,能够非常清晰明确的提出问题并给出简单的参考实现,但在问题思考的深度、广度,以及解决方法的时效性和先进性方面,只能说是一般而已。相对于 SpringFramework 作者那两本重量级作品,此书也就是大概翻翻就行了。 Zen Neeon 如果说现在说有什么电子产品,能单靠工业设计来以起购买欲的话,Apple 的 iPod 系列可以算一个了。非常纯粹的简洁设计风格,非常合我的胃口,可惜操作模式和各种参数不太令人满意。要么就是用硬盘的版本太笨重,要么就是用 flash 卡的版本容量太小。好容易等到了 iPod nano 的出现,搞得我这不太对电子产品感冒的人,都难免有购买欲,呵呵。
昨天兴冲冲跑到鼎好,转了一圈居然 4G 版本都没有货。刚好碰到 Creative 拿来与 Apple iPod 死磕的 Zen Neeon 大做广告,去看了看貌似也还不错,就顺手整了一个回来。
与 iPod 走的特例独行的 iPod 文化不同,Creative 这款机器更世俗化一些。6G 超大容量、比 iPod 更平易近人的价格、7 色 LED 背景、七种机壳色、各种花里胡梢的贴纸、-_-b... 看来 Creative 是准备跟 Apple 死磕了,呵呵 平心而论这款 Zen Neeon 基本上可以算是一款中规中矩的中高档音乐播放器,整体风格上与 iPod 走着较为不同的路线,也许更适合中国这种消费者普遍品位不高的市场。看看宣传画上那十几种只能用热闹来形容的贴纸,再看看 iPod 那唯美纯粹的简约风格,就会发现其实两者定位的用户群是不同的。不过这些贴纸也实在是 TMD 太圡了,好在裸机的工业设计也还凑合,偶就不去赶这种时髦了。 具体功能上来说,在这个档次上也只能说是平淡而已。支持播放 MP3、WMA、WAV 格式、支持 FM 以及录音、支持中文以及播放列表云云。自带的耳机居然没有线控,也不知是节约成本还是因为强着发布。好在随机带的耳机效果还不错,也算没有砸 Creative 的名头。比较 BT 的是居然只有 USB 充电,而且需要六个小时,充电时还不能使用 操作习惯上比较失败的设计,是没有提供直接回到上级菜单的快捷键,非得用滚轮选列表中的一个箭头才行,如果一个目录下面文件较多就非常不实用了。而且那个滚轮,也不知道是故意还是什么原因,灵敏度并不是很高。 比较郁闷的是居然不支持 WMA 的无损压缩格式,试了半天死活就是放不出来,搞得我那一堆古典音乐都没办法弄上去听。不过现在机器都是支持 fireware 升级的,而 Creative 的格式转换器是提供无损压缩格式支持的,因此还有一线希望等升级版本,毕竟这还是第一个版本的软件。 现在音乐播放器都发展到这个程度了,怎么就没有一个厂商跳出来,整一款可以自己 DIY 系统的型号出来呢?其实说白了不就是一个音频接口、一个硬盘、一个液晶屏么,给个开发包让我自己攒套 Linux 跑跑,想用什么格式用什么格式,想支持什么功能自己写,那样多 happy 啊,人民的力量是无穷滴……
September 15 企业级信息搜索服务 前几天部门开会讨论知识管理,其中提到一个很重要的问题,就是如何对企业内部各种积累下来的信息,提供统一、方便且安全的检索支持。虽然大部分现有机制,例如 SPS、论坛、邮件、IM 等等,基本上都提供了一定程度的检索支持。但从企业知识管理这个层面,并没有一个很好的现存解决方案。
而要解决这个问题,基本上是一个非常复杂的系统工程,需要涉及很多方面:
虽然看起来非常复杂,但技术上还是可以通过对现有工具的重用,达到最大限度降低二次开发工作量的。 首先,可以通过模拟 GDS/SPS 的相关检索插件运行环境,重用这些针对特定格式的分析器。这样一来直接就可以提供大部分常用文档格式的支持,且以后的扩展和升级余地较大。 其次,可以通过分布式代理,将检索插件的分析结构,汇总到中央服务器统一进行存储。通过 lucene 等现成检索支持工具,完成基于关键字的存储和检索支持,并可以获得现成的使用界面和管理支持。 此外,可以在存储分析后内容时,通过预定义树型结构来区分内容。以便于在实际使用时,根据可定义的权限系统来保护机密内容。 最后,可以针对不同的客户端和业务应用,开发相应的检索界面和检索代理,以完成实际的工作。
Seems funny……回头整个原型出来 :D 此外还可以类似 google 的企业级搜索那样,弄个基于网络流量监听的检索支持服务,可以把 snort 之类的 ids 引擎改改,做一些简单的协议如 http, smtp, pop3 的解码,并针对其内容进行检索和存储。
感觉这块如果能做好,市场潜力应该还是挺大的,毕竟知识管理和检索是绝大多数企业所关心,也能够给其迅速带来切身利益的。 September 03 AT-700 下午奢侈了一把,买了一枝 YONEX ARMORTEC 700 羽毛球拍
虽然价格和质量同样精彩,但 YY 的主流型号的确是物有所值,而且方头加长偏硬球杆,非常适合我这种懒得跑位懒得起跳扣球的人,哈哈
不过鸟枪换炮后,现在拉后场球的确非常轻松,在上支拍子球框拉弯导致几个月没打球的情况下,还能非常轻松的拉底线对角,搞得对面的哥们非常郁闷,呵呵。但新拍子新长度适应起来挺困难,折腾一个多小时才好容易找好后场球的击球点,但小球和扣杀却怎么也打不顺。特别是中后场扣杀,原本是我买这个拍子的主要目的,但居然次次下网,搞得我郁闷的要死,都不好意思继续扣杀了。因此打到后面,基本上就是我一个人在后场对拉,和我配合的哥们在前场捡漏,呵呵。这样拉了半个小时后,就明显看出以前培训班教练观点的正确性。对没有针对性训练过的对手,保持后场持续压力然后寻机吊球,这种基本战术的优势非常大,呵呵。
以后又少了一个懒得运动的借口了,呵呵
![]() August 28 some interesting books 刚刚看完《岁月台湾》这本书,感觉非常不错。对大陆和台湾的普通民众来说,海峡对岸的历史和现状一直都是非常神秘的。虽然最近几年随着交往的逐渐增多,相互之间的透明性逐渐增强,但偶对台湾的近代史一直没有一个基本的概念。从教科书或官方媒体了解到的,往往是经过意识形态修正后的扭曲的历史,而《岁月台湾》则是从图片的角度展现一个真实的台湾近代史。从这里能体会到照片的重要意义,毕竟话是靠人说理论是靠人编,但照片缺总是真实的记录下历史的时时刻刻。看着那些与我们同祖同宗流着共同血脉的人,他们的真实生活与喜怒哀乐,真是倍感亲切。
此外昨天逛书店时顺便买了两本萨苏的小集子:《梦里关山走遍》和《中国厨子》。老萨写的文章的确是可读性强,虽然集子里的文章以前都看过,还是手不释卷的把《中国厨子》重读一遍。除了他的文笔和卖关子的功夫,其渊博的知识和涉猎,真正体现出一个世家子弟的风采。说起来世家子弟以前一直是个偏贬义的词,但实际上接触过一些此类人的文与话之后,发现他们在对问题的理解上往往有非常别致的独到之处。这种在文章字里行间体现出来的内涵和底蕴,绝对不是现在看似走红的所谓网络写手能够比拟的。可惜没能买到另外几本:《嫁给太监》、《梦里燕赵》、《蓝天轶事》,回头到网上定吧 :S
相比之下现在流行文化越来越低俗化,书名都开始叫什么《决不裸奔》或《潘金莲的发型》,作者群中也开始出现大量一点写作概念都没有的人,语句前后不通内容纯属 yy。虽然作为一个并不善于书写的民族,能够有越来越多的人参与到写作上,在大的趋势上是好的。但这种冲击给尚未具备鉴别能力的一代,带来的影响就无法预期了。也许随着岁月的流逝,他们也会开始象80年代那批先锋作家一样开始重读百年前的世界名著吧。回头去弄本余华沉寂多年后的新著《兄弟》,看看他在诡异的《灵魂饭》之后从历史沉淀中汲取到了什么。 巍巍太行 刚刚看了年度主旋律大片《太行山上》,感觉还是挺不错的,起码比起以前的那些蹩脚的煽情片来说,整体上来说进步还是很明显的。
两个比较明显的思路转变:
1.不再局限于宣传老毛为核心的党中央,开始刻画朱德等其他抗战英雄
2.不再一味丑化国民党在抗战中的形象,开始表现其在正面战场的作用
说道第一点,以前凡是这种大一点规模的作战影片,无不着力宣扬太祖如何英名神武,如何千里之外运筹帷幄。然后找几个党内外的人来挑战一把,最终都被折服的五体投地,或者被打倒再踩两脚。而事实上老毛虽然在战略眼光上无人出其右,但在具体的战役部署上,还是非常充分放权相信前指人员的。这也是老毛与老蒋或希特勒等等相比最大的优点之一,将将者方能为帅。
第二点则是一个非常重要的转变,因为一直以来在主旋律的影片中,国军都是枪一响就四散奔逃的形象。而实际上撇开内战期间不谈,在抗日战争的初期、中期和后期,都存在着大量敢打敢拼的热血将士。而且与共产党的游击战不同,国民党以当时中国正规军的身份,不可能采取类似的灵活战法,很多情况下是知其不可为而为之。说起来还是挺同情片中的阎锡山,中央军和八路军打胜了就站稳脚跟,打摆了拍拍屁股战略转移,就苦了他这个土地主,呵呵。
不过就具体的表现手法来说,对好莱坞大片的模仿痕迹太重,而且限于投入和经验不足,好些画虎成猫的地方。加上主旋律片里传统的各种恶寒的 pose,以及武侠片似的独臂大侠、红缨枪雨等等,时不时搞得偶笑也不是不笑也不是 :S
此外给设计造型的老兄也够损的,那一群日本高级军官开会下命令的场景,怎么看也像是《人猿星球》里面猿人开会,哈哈,笑得我半死 :P
|
|
|