投机执行
有的时候当你的选择的代价很大时,不如跳过选择两种都尝试一下,一旦等你决定后,可以直接选用其中一种结果。这种方法在对时间敏感的任务中特别的有用。
在计算机理论中有种代码的执行方式叫做投机执行,意指计算的结果可能是不需要的.通常程序中的指令可以分三种:一定需要执行的,一定不需要执行的,和无法确定是否需要执行的.最后一种情况就是和进行投机执行.
现代CPU流水线有一种设计,对于分支的流程代码,可以先猜测哪种是最可能的分支,或者干脆两个分支都执行。这样当实际的判断条件知道之后,直接获取结果就行了。这样的好处是可以提高程序的执行时间,但是浪费电能,对移动应用杀伤力很大。
在分布式计算系统中也有类似的应用,比如Hadoop会把执行得相对过慢的任务用同样的数据再启动一个。以保证一次计算作业能够尽快的完成。
关于磁盘上资料的管理
一度以来,我是严重信赖文件系统目录的方式来组织自己电脑上的文件的。自从接触DOS开始,到XP到Linux。一直试图通过目录把我的文件管理得井井有条——很长一段时间内我的确做到了。
从初中到大学,我的软盘,硬盘和闪盘,甚至Web邮箱里的邮件,都会被我分门别类的放进不同层次的目录里。换用Linux后,我把目录按照Unix风格的简写,改成终端里容易访问的长度。整理文件时,甚至还会把中文文件名改成英文,去掉空格和不易输入的字符等等。每下一张专辑,我也会手动拷贝到各个对应的歌手的目录下,更改目录名,然后在MPD中更新曲库。在这种强迫症式的文档管理习惯下,换来的是超快的文档查找速度——几乎没有动系统的搜索功能。
但是当我系统里的文档数量增长到一定程度时,我开始应付不过来了。在我的Linux系统主目录下有个tmp目录,作为平时未整理文件存放的地方。从我工作以后,这个目录下的文件开始越来越大,各种文献,电子书,幻灯片,歌曲,电影等等,每次分类花费的时间越来越长,我开始越来越懒得整理这个目录,指导最后,我开始在这个目录下简历一个新的临时目录,把某一个类文档,比如说文献,幻灯片,视频等等一股脑的拖进去,再也没有时间一个个的为文件重命名了。这个习惯一直被我带到Mac平台。
噩梦的结束得益于iTunes这款软件。这款软件在接触之前给我的印象就是一款用户体验友好的播放器,和苹果的变态的限制其他人访问iPod/iPhone的所强制使用的必需品。但是用后发现它就是一个歌曲的关系数据库:我的所有的歌曲,Podcast和MP4视频都被当作一条条的记录存在不同的表中,媒体的TAG被提取出来加上电脑和手机上的行为统计被作为N个字段,而各种智能播放列表则构成了一个个数据库视图。结合操作系统的全文索引工具,我对歌曲的所有的管理都可以通过数据库进行。再也不用访问底层的文件系统了。(这时你可以从技术角度理解为什么iPod+iTunes的组合所向披靡了——它们之间是数据库replication,相比其他组合都是文件系统复制,最多是数据库导出文件而已。)
在享受了一两个月的数据库式的歌曲管理之后,我突然想到:为什么不把我其他的文件也用这种方法管理呢?或者,是不是我之前用过的软件已经提供了这种管理的方法了呢?于是我把所有的文献,幻灯片,PDF电子书都导入了Papers,更多的图片都导入了iPhoto,而不仅仅是人物照片。之前的Google Tech Talks和TED的备份,也都重新导入iTunes,不再单独靠拷贝文件的方式备份。
经过一阵折腾之后,我系统里的大部分文件都被导入到了某个管理功能的软件中。余下的“长尾文件”,则分布于少数1-2层的目录下,只需要很少的几次鼠标点击就能够访问得到。几个便利得软件得引入,不仅使得我管理大批量得文件变得干净利落,管理剩下的少量文件也变得迅速了。整理临时目录也腰不酸背不痛了(表打我!)。
这段经验总结起来就是:当重复类型的文件多到一定的程度,就需要借助软件来管理,管理的软件必须提供丰富的查询功能。无法批量管理的文件,则通过和能够批量处理的文件分离来提高处理的效率。这条经验适合于磁盘上的文件,也适用于网上的信息的管理,下篇博客将介绍我怎样借助在线服务解决网上信息过载问题的。
最近对Hadoop的一些了解
最近因为项目的关系,顺便对Hadoop的代码做了一下局部的分析。暂时了解的是任务的提交协议,Streaming API的实现,TaskTracker上的多任务机制和ReducerTask所需的一些工作。
提交任务协议Hadoop采用了非常实用的方法,由JobTracker生成一个Job ID,客户端根据这个ID将任务的jar包上传到HDFS文件系统上,再用JobID向JobTracker进行汇报就行了。这样的异步的方式避免了复杂的RPC设计,也降低了任务控制和存储系统之间的耦合程度。
Streaming API是一个普通的Hadoop任务,它会根据输入参数对管道程序进行打包,并且在TaskTracker上解压运行,Map/Reduce的数据由管道输入输出。Streaming的处理对TaskTracker来说是透明的。
在默认的配之下,TaskManager会为每一个Map/Reduce启动一个JVM虚拟机。Java虚拟机的启动虽然很耗资源,但是对于任务的隔离非查功能有用,尤其是对于容错要求很高的应用。JobTracker会把失败的任务重新分配到其他的节点上执行,保证在硬件出错,Hadoop实现不稳定的情况下,依然能够将分析任务跑完。 所以,对于主要跑Streaming任务的应用,应该把每个JVM启动的任务数量改成和每个TaskTracker的任务数量一致。
由于打算在项目中采用粗粒度的任务拆分,因此通过代码了解了一下ReducerTask的实现。默认在一个Reducer任务所接受的Mapper输出小于100MB的情况下,ReducerTask会将接受的K/V对序列保存在内存中,并且在数量大于一定阀值时进行合并。当输入超过100MB时,会将数据写入本地的磁盘文件。当所有Mapper工作进行完毕,最终的合并完成后,会利用用户提交的Reducer实现进行迭代。
基本上就看了这么多,这些都是一些边缘的细节技术。接下来是了解几种任务调度算法和文件系统的实现。
和同学的关于科学和哲学的讨论
同学(*********) 22:35:44
知性与理性之间,如果单纯是规定性的判断力所连接,那人就成了一个必然性的机器,意志的自由就成为不可能,理性的实践也不可能。康德就此显发了“反思判断力”,使得意志自由与知性的必然有了协调的可能,人首先在艺术上创造出一种自然与道德、必然与自由的统一,而康德又把这延伸到世界从必然到自由的可能。
同学(*********) 22:35:54
拜拜,小李老师
江泽★GNAP(*********) 22:36:24
我现在更信奉维也纳学派发展起来的分析哲学,专门解决语言的问题。
同学(*********) 22:36:38
康德当然不是究竟
但是至少他提出了这个后来哲学家无法避开的三大批判
同学(*********) 22:37:10
我现在看看佛经,研究研究禅。
江泽★GNAP(*********) 22:37:45
形而上的学的问题大多数是不可证伪的,所以分析哲学才排斥形而上学。
同学(*********) 22:39:30
有空看看论语,就直下担当了
同学(*********) 22:40:00
袈裟来了
同学(*********) 22:40:06
刚刚还在说曹操
江泽★GNAP(*********) 22:40:11
所以我现在并不急于区分我到底是自由意志的人还是机器。而是坐一个我有自由意志的假设,接受这个前提,就可以把我的一些选择当作结论。
同学(*********) 22:40:42
你是天地之心,天地是你的躯壳。
江泽★GNAP(*********) 22:40:47
呵呵,古代的哲学的顿悟充满不可重复性。
同学(*********) 22:41:25
如果读研的话我就选马克思哲学
江泽★GNAP(*********) 22:41:55
马哲太形而上。
同学(*********) 22:41:58
坐在小板凳上看共产主义
江泽★GNAP(*********) 22:42:13
卡尔波普的可证伪性就是针对唯物辩证法提出的。
同学(*********) 22:42:28
呵呵,你是说列宁还是斯大林的运动的展现啊
江泽★GNAP(*********) 22:43:23
和后代的解读无关,唯物辩证法一提出就给了后人充分发挥留有了余地。
同学(*********) 22:43:34
“证伪和证真其实是一体的两面,任何以证为前提的活动,都有一个先验的前提,就是可证之存在。相应的,当一个命题被证伪时,只不过同时证明了,在命题所构成的集合里,正确的命题被包含在被证伪命题的补集里。”
同学(*********) 22:44:05
所谓波普尔“证伪原则”,只不过在逻辑上等价于先验地假设可证命题集合的存在以及正确的命题被先验地假设在可证命题集合里。而且,按照所谓的“证伪原则”,同样一个困难的问题会出现,就是“证伪原则”的可证伪性,当波普尔把所谓的可证伪性当成所谓的科学原则时,他自己理论的科学性就此动摇。
江泽★GNAP(*********) 22:44:57
这段论证的本身的先验前提就是事物的两面性。
江泽★GNAP(*********) 22:45:22
所以整段论证一一段重言式。
江泽★GNAP(*********) 22:45:49
可证伪理论是把科学研究的问题和非科学的区分开来。而不是否定后者的存在。
江泽★GNAP(*********) 22:46:11
重点在”可“,而不是”存在“。
同学(*********) 22:46:13
我想说的是:后人对马克思误解太多,也冤枉太多
同学(*********) 22:46:48
科学本身不足以表明其权威
江泽★GNAP(*********) 22:46:56
也把他神话太多,他是个大师,也是古人。现代哲学里他贡献的东西太少了。
江泽★GNAP(*********) 22:47:07
和科学的权威性无关。
江泽★GNAP(*********) 22:47:44
卡尔波普只是区分哪些是科学要研究的,哪些不是。
江泽★GNAP(*********) 22:47:59
并没有说哪种是”权威“的。
同学(*********) 22:48:22
科学其实是这样的玩意:由于每个人都有这样的仪器以及相应的感应仪器,科学首先要假定,存在一种普遍的,能被尽量多的人的仪器所认可的标准,这就是科学观察的基础前提。
江泽★GNAP(*********) 22:49:26
是的,科学建立在形式的逻辑的基础上。形式逻辑的核心是你接受前提必须接受结论。
江泽★GNAP(*********) 22:49:49
但形式逻辑并会不保证前提是正确的,科学没从来没有夸口保证。
江泽★GNAP(*********) 22:50:25
因此,前提错误的可能性并不能证明科学的无效性。
江泽★GNAP(*********) 22:51:03
因此科学才强调实验的重要性,通过实验的结果不断的验证现有的理论。
同学(*********) 22:51:36
是爱因斯坦对牛顿玩了一手之后的事情了
江泽★GNAP(*********) 22:52:07
事实上近代所有科学发现都是这一个过程。
江泽★GNAP(*********) 22:52:32
现在有科学哲学这一门学科,专门研究这类问题。
用MRUnit对MapReduce程序进行单元测试
很多人都为调试MapReduce程序感到头疼,因此有人贡献了MRUnit,专门进行MapReduce程序测试的工具。最新的hadoop-0.20已经包含这个工具,CloudEra甚至将他backport到0.18.x的发布中来。 MRUnit的使用方法非常简单,只要确定输入和期望的输出,剩下的工作交给MapDriver和ReduceDriver就okay了。下面以最简单的IdentityMapper和IdentityReducer做例子, 他们都是将输入直接传递给输出,不做任何的修改。 import junit.framework.TestCase; import java.util.List; import java.util.LinkedList; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapred.Mapper; import org.apache.hadoop.mapred.lib.IdentityMapper; import org.apache.hadoop.mapred.Reducer; import org.apache.hadoop.mapred.lib.IdentityReducer; import org.apache.hadoop.mrunit.MapDriver; import org.apache.hadoop.mrunit.ReduceDriver; import org.apache.hadoop.mrunit.types.Pair; import org.junit.Before; import org.junit.Test; public class TestExample extends TestCase { private Mapper mapper; private Reducer reducer; private MapDriver mapDriver; private ReduceDriver reduceDriver; @Before public void setUp() { mapper = new IdentityMapper(); reducer = new IdentityReducer(); mapDriver = new MapDriver(mapper); reduceDriver = new ReduceDriver(reducer); } @Test public void testIdentityMapper() { List> res; mapDriver.setInput(new Text(“key”), new Text(“keyword1″)); mapDriver.addOutput(new Text(“key”), new Text(“keyword1″)); mapDriver.runTest(); } @Test public void testIdentityReducer() { List> res; List input = new LinkedList(); input.add(new Text(“keyword1″)); input.add(new Text(“keyword2″)); reduceDriver.setInput(new Text(“key”), input); reduceDriver.addOutput(new Text(“key”), new Text(“keyword1″)); reduceDriver.addOutput(new Text(“key”), new Text(“keyword2″)); reduceDriver.runTest(); } }
Linux用户迁移到Mac的经验
很早就因为看Google Techtalk而对Mac系统眼馋了,上周终于忍不住跑到零售店买了这款小白。由于之前已经通过各方面音像博推了解到了很多Mac系统的特性,加上Mac系统本身设计得比较人性化,上手并没有花掉太多的时间。但尝鲜归尝鲜,还是有很多的细节的地方值得注意。
0)先按Command+Shift+A打开Application目录,在Utilities子目录下找到Terminal,拖动到Dock上:P;
1)作为一个Linux用户一打开Mac的终端就会心凉一下。因为Mac的Terminal.app实在太丑陋了。所以先参照这个教程美化一下。美化后虽然还不是256色的,但是总算顺眼多了;
2)Mac下带了bash, zsh和tcsh,可以用chsh替换;
3)XCode是必须要安装的,其中包含了gcc等在Mac下开发必备的工具,Macports也依赖XCode;
4)喜欢Vim的同学可以下Macvim来装,配置文件用Linux下拷贝来的即可。Cocoa界面的macvim会读取~/.gvimrc作为配置文件,guifont不能用Monoscpace\ 10这样的字体了,建议换成Monaco:h14.00,在Mac下很美观。喜欢Emacs的童鞋请安装Aquamacs;
5) Mac下的Finder不支持SFTP的挂载,建议用lftp;(Mac很多原生的收费FTP客户端都支持SFTP,但是都不支持下载到外部设备,如移动硬盘等)
6) 很多Linux下常用的维护工具,如updatedb,可以在/usr/libexec/下找到,名字变成了locate.updatedb。
7) Finder不支持显示以点开头的隐藏文件,虽然用起来不方便,但是藏起来相对安全些.:)
Mac下只要不是拖动到Application目录下就可使用的安装包,卸载起会很麻烦,可以先找到安装包,右键选择Show Package Contents, 参照包种的pkglist文件中的IFPkgFlagDefaultLcation键值指定的位置,逐个的去删包中的文件(慎用);
9) Time Machine一定要用,但要注意排除掉存放电影目录的备份。以Linux/Win下转来的用户的移动硬盘的速度,不重要的数据还是别备份了;
10)从Linux转来的很多用户都离不开虚拟机,所以也注意要把虚拟机磁盘文件所在目录也禁用Time Machine备份。否则只要你虚拟机磁盘文件有了变化,Time Machine会不由分说的备份整个磁盘文件。同理,BT下载文件目录也建议关闭Time Machine;
11) GnomeDo的用户建议安装QuickSilver;
12) 如果你像我一样也是个终端控,安装OpenTerminalHere是个不错的选择;
13)/etc/locate.rc中注释掉/Users目录,平时有Spotlight,locate用来查系统文件就行了。
解决了这些问题,一个Linux用户就可以顺畅的使用Mac OS X了。
Suffix Tree的实现代码
原理见前面的博文。下面是粗略实现的python代码,GPL版权。
#!/usr/bin/env python
def getSuffixes(sequence):
"""Extract suffixes for a sequence"""
return [ sequence[i:] for i in range(0, len(sequence)) ]
def getPrefixes(sequence):
"""Extract preffixes for a sequence"""
return [ sequence[:i] for i in range(1, len(sequence) + 1) ]
def commonDiff(seq1, seq2):
""" Get the common prefix the two sequences and two different suffixes """
index = 0
for i in range(0, min(len(seq1), len(seq2))):
if seq1[i] == seq2[i]:
index += 1
else:
break
return (seq1[0:index], seq1[index:], seq2[index:])
class SuffixNode(object):
"""Node class of a Suffix Tree"""
def __init__(self, parent, path):
self.parent = parent
self.path = path
self.nodes = []
self.suffix = None
def isRoot(self):
"""test if current node is root node."""
return self.path is None
def addSuffix(self, suffix):
"""add a suffix to the current subtree."""
(common, suf1, suf2) = commonDiff(self.path, suffix)
if len(common) == 0:
# do not belong to this branch
return False
elif len(suf2) == 0:
# shorter than current branch
return True
elif len(suf1) == 0:
# longer than current branch
if len(self.nodes) == 0:
# leaf just append to it
self.path = suffix
return True
else:
# internal node, try subbranches
for node in self.nodes:
if node.addSuffix(suf2):
return True
elif len(self.nodes) == 0:
# leaf split it
self.path = common
self.nodes.append(SuffixNode(self, suf1))
self.nodes.append(SuffixNode(self, suf2))
return True
else:
# split the internal node
newnode1 = SuffixNode(self, suf1)
newnode2 = SuffixNode(self, suf2)
self.path = common
for node in self.nodes:
node.parent = newnode1
newnode1.nodes.append(node)
self.nodes = []
self.nodes.append(newnode1)
self.nodes.append(newnode2)
return True
def __repr__(self):
"""representation of node object"""
return self.path
def structure(self):
"""tree structure was return as a nested dictionary."""
return { self.path : [ node.structure() for node in self.nodes ] }
class SuffixTree(SuffixNode):
"""A SuffixTree Implementation."""
def __init__(self, sequence):
super(SuffixTree, self).__init__( None, '')
self.feed(sequence)
def feed(self, sequence):
"""feed the tree a sequence"""
prefixes = getPrefixes(sequence)
for prefix in prefixes:
suffixes = getSuffixes(prefix)
for suffix in suffixes:
succ = False
for node in self.nodes:
if node.addSuffix(suffix):
succ = True
break
if not succ:
self.nodes.append(SuffixNode(self, suffix))
def printTree(tree, level = 0):
"""docstring for printTree"""
if tree == None: return
print ' '*level + tree.path
for node in tree.nodes:
printTree(node, level+1)
def main():
"""docstring for main"""
tree = SuffixTree( ('mississippi') )
printTree(tree)
def test():
"""docstring for test"""
print getPreffixes('mississippi')
if __name__ == '__main__':
main()
用GST解决Longest Common Substring Problem
最长公共子字符串问题,是指给定N个字符串,在每个字符串中找最长的公共子字符串。该问题最常出现在DNA测序的计算中。给定一些列DNA片段,找到重复的地方将其对接起来。解决这个问题需要我们前面提到的后缀树Suffix Tree(ST)和Generalised Suffix Tree(GST)。

创建GST的方法和创建ST的方法类似,首先我们为每一个字符串Sn加上一个终结符号,根据这个终结符号,可以在GST中区别出该后缀来自哪个字符串。然后我们先将每个添加终结符后的字符串Sn’创建一个ST,并且将Sn’添加到GST中去。
如图得到的GST中,可以直观的看到我们要的结果。但程序应该如何判断呢?
首先我们从每个ST(Sn)的每个后缀开始迭代。在GST的底部开始向根节点查找。每遇到一个新的支节点,则将其标记做上属于字符串Sn的标记。如果遇到已经属于本字符串的标记,开始迭代下一个字符串。
然后,我们将获得了所有Sn个字串标记的支节点。取的其代表的公共字符串L(Sn)。如图,得到了A, CTG, TG和G。很显然,其中最长的是CTG。
这样就得到了两个字符串的公共子串CTG。
本文参考了[Inoke Lee 2005] Linear Time Algorithm for the Longest Common Repeat Problem。事实上论文中论述的是最长公共重复子串问题,比本问题还要复杂。由于篇幅限制不在这里做详细介绍,有兴趣的读者可以Google下这篇论文。本文可以看作是GST应用的简单例子。

Online Algorithm
译自维基百科:在计算机科学中,在线算法是指一种可以顺序的方式逐块处理输入的算法。比如说,算法只要“喂”一口数据即可开始,而不需要得到全部的数据。反之,离线算法是指手边的问题必须要一开始就获得全部的数据才能解决。(举例来说,选择排序要求得到整个列表才能开始排序,而插入排序则不需要。)

Online Algorithm
译自维基百科:在计算机科学中,在线算法是指一种可以顺序的方式逐块处理输入的算法。比如说,算法只要“喂”一口数据即可开始,而不需要得到全部的数据。反之,离线算法是指手边的问题必须要一开始就获得全部的数据才能解决。(举例来说,选择排序要求得到整个列表才能开始排序,而插入排序则不需要。)
