Google 搜索原理论文精编版

来自: http://www.cftea.com/c/2008/05/D98RHGCRV8B1JXA6.asp

这篇文章中,我们介绍了google,它是一个大型的搜索引擎(of a large-scale search engine)的原型,搜索引擎在超文本中应用广泛。Google的设计能够高效地抓网页并建立索引,它的查询结果比其它现有系统都高明。这个原型的全文和超连接的数据库至少包含24000000个网页。我们可以从http://google.stanford.edu/ 下载。

设计搜索引擎是一项富有挑战性的工作。搜索引擎为上亿个网页建立索引,其中包含大量迥然不同的词汇。而且每天要回答成千上万个查询。在网络中,尽管大型搜索引擎非常重要,但是学术界却很少研究它。此外由于技术的快速发展和网页的大量增加,现在建立一个搜索引擎和三年前完全不同。

本文详细介绍了我们的大型搜索引擎,据我们所知,在公开发表的论文中,这是第一篇描述地如此详细。除了把传统数据搜索技术应用到如此大量级网页中所遇到的问题,还有许多新的技术挑战,包括应用超文本中的附加信息改进搜索结果。

本文将解决这个问题,描述如何运用超文本中的附加信息,建立一个大型实用系统。任何人都可以在网上随意发布信息,如何有效地处理这些无组织的超文本集合,也是本文要关注的问题。

关键词 World Wide Web,搜索引擎,信息检索,PageRank, Google

1 绪论

Web 给信息检索带来了新的挑战。Web上的信息量快速增长,同时不断有毫无经验的新用户来体验Web这门艺术。人们喜欢用超级链接来网上冲浪,通常都以象 Yahoo这样重要的网页或搜索引擎开始。大家认为List(目录)有效地包含了大家感兴趣的主题,但是它具有主观性,建立和维护的代价高,升级慢,不能包括所有深奥的主题。基于关键词的自动搜索引擎通常返回太多的低质量的匹配。使问题更遭的是,一些广告为了赢得人们的关注想方设法误导自动搜索引擎。

我们建立了一个大型搜索引擎解决了现有系统中的很多问题。应用超文本结构,大大提高了查询质量。我们的系统命名为google,取名自googol的通俗拼法,即10的100次方,这和我们的目标建立一个大型搜索引擎不谋而合。

1.1网络搜索引擎—升级换代(scaling up):

1994-2000 搜索引擎技术不得不快速升级(scale dramatically)跟上成倍增长的web数量。1994年,第一个Web搜索引擎,World Wide Web Worm(WWWW)可以检索到110,000个网页和Web的文件。到1994年11月,顶级的搜索引擎声称可以检索到2‘000’000 (WebCrawler)至100‘000’000个网络文件(来自 Search Engine Watch)。可以预见到2000年,可检索到的网页将超过1‘000’000‘000。同时,搜索引擎的访问量也会以惊人的速度增长。在1997年的三四月份,World Wide Web Worm 平均每天收到1500个查询。

在1997年11月,Altavista 声称它每天要处理大约20’000’000个查询。随着网络用户的增长,到2000年,自动搜索引擎每天将处理上亿个查询。我们系统的设计目标要解决许多问题,包括质量和可升级性,引入升级搜索引擎技术(scaling search engine technology),把它升级到如此大量的数据上。

1.2 Google:

跟上 Web的步伐(Scaling with the Web)建立一个能够和当今web规模相适应的搜索引擎会面临许多挑战。抓网页技术必须足够快,才能跟上网页变化的速度(keep them up to date)。存储索引和文档的空间必须足够大。索引系统必须能够有效地处理上千亿的数据。处理查询必须快,达到每秒能处理成百上千个查询(hundreds to thousands per second.)。随着Web的不断增长,这些任务变得越来越艰巨。然而硬件的执行效率和成本也在快速增长,可以部分抵消这些困难。

还有几个值得注意的因素,如磁盘的寻道时间(disk seek time),操作系统的效率(operating system robustness)。在设计Google的过程中,我们既考虑了Web的增长速度,又考虑了技术的更新。Google的设计能够很好的升级处理海量数据集。它能够有效地利用存储空间来存储索引。优化的数据结构能够快速有效地存取(参考4.2节)。进一步,我们希望,相对于所抓取的文本文件和HTML网页的数量而言,存储和建立索引的代价尽可能的小(参考附录B)。对于象Google这样的集中式系统,采取这些措施得到了令人满意的系统可升级性(scaling properties)。

1. 3设计目标

1.3.1   提高搜索质量。我们的主要目标是提高Web搜索引擎的质量。

1994 年,有人认为建立全搜索索引(a complete search index)可以使查找任何数据都变得容易。根据Best of the Web 1994 — Navigators ,“最好的导航服务可以使在Web上搜索任何信息都很容易(当时所有的数据都可以被登录)”。然而1997年的Web就迥然不同。近来搜索引擎的用户已经证实索引的完整性不是评价搜索质量的唯一标准。用户感兴趣的搜索结果往往湮没在“垃圾结果Junk result”中。实际上,到1997年11月为止,四大商业搜索引擎中只有一个能够找到它自己(搜索自己名字时返回的前十个结果中有它自己)。导致这一问题的主要原因是文档的索引数目增加了好几个数量级,但是用户能够看的文档数却没有增加。用户仍然只希望看前面几十个搜索结果。因此,当集合增大时,我们就需要工具使结果精确(在返回的前几十个结果中,有关文档的数量)。由于是从成千上万个有点相关的文档中选出几十个,实际上,相关的概念就是指最好的文档。高精确非常重要,甚至以响应(系统能够返回的有关文档的总数)为代价。令人高兴的是利用超文本链接提供的信息有助于改进搜索和其它应用 。尤其是链接结构和链接文本,为相关性的判断和高质量的过滤提供了大量的信息。Google既利用了链接结构又用到了anchor文本(见2.1和2.2节)。

1.3.2  搜索引擎的学术研究随着时间的流逝,除了发展迅速,Web越来越商业化。

1993 年,只有1.5%的Web服务是来自.com域名。到1997年,超过了60%。同时,搜索引擎从学术领域走进商业。到现在大多数搜索引擎被公司所有,很少技公开术细节。这就导致搜索引擎技术很大程度上仍然是暗箱操作,并倾向做广告(见附录A)。Google的主要目标是推动学术领域在此方面的发展,和对它的了解。另一个设计目标是给大家一个实用的系统。应用对我们来说非常重要,因为现代网络系统中存在大量的有用数据(us because we think some of the most interesting research will involve leveraging the vast amount of usage data that is available from modern web systems)。例如,每天有几千万个研究。然而,得到这些数据却非常困难,主要因为它们没有商业价值。我们最后的设计目标是建立一个体系结构能够支持新的关于海量Web数据的研究。为了支持新研究,Google以压缩的形式保存了实际所抓到的文档。设计Google 的目标之一就是要建立一个环境使其他研究者能够很快进入这个领域,处理海量Web数据,得到满意的结果,而通过其它方法却很难得到结果。系统在短时间内被建立起来,已经有几篇论文用到了Google建的数据库,更多的在起步中。我们的另一个目标是建立一个宇宙空间实验室似的环境,在这里研究者甚至学生都可以对我们的海量Web数据设计或做一些实验。

2. 系统特点

Google搜索引擎有两个重要特点,有助于得到高精度的搜索结果。
第一点,应用Web的链接结构计算每个网页的Rank值,称为PageRank,将在98页详细描述它。
第二点,Google利用超链接改进搜索结果。

2.1 PageRank:给网页排序:

Web 的引用(链接)图是重要的资源,却被当今的搜索引擎很大程度上忽视了。我们建立了一个包含518000000个超链接的图,它是一个具有重要意义的样本。这些图能够快速地计算网页的PageRank值,它是一个客观的标准,较好的符合人们心目中对一个网页重要程度的评价,建立的基础是通过引用判断重要性。因此在web中,PageRank能够优化关键词查询的结果。对于大多数的主题,在网页标题查询中用PageRank优化简单文本匹配,我们得到了令人惊叹的结果(从google.stanford.edu可以得到演示)。对于Google主系统中的全文搜索,PageRank也帮了不少忙。

2.1.1计算PageRank

文献检索中的引用理论用到Web中,引用网页的链接数,一定程度上反映了该网页的重要性和质量。PageRank发展了这种思想,网页间的链接是不平等的。

PageRank定义如下: 我们假设T1…Tn指向网页A(例如,被引用)。参数d是制动因子,使结果在0,1之间。通常d等于0.85。在下一节将详细介绍d。C(A)定义为网页A指向其它网页的链接数,

网页A的PageRank值由下式给出: PR(A) = (1-d) + d (PR(T1)/C(T1) + … + PR(Tn)/C(Tn))

注意PageRank的形式,分布到各个网页中,因此所有网页的PageRank和是1。 PageRank或PR(A)可以用简单的迭代算法计算,相应规格化Web链接矩阵的主特征向量。中等规模的网站计算26‘000’000网页的 PageRank值要花费几小时。还有一些技术细节超出了本文论述的范围。

2.1.2 直觉判断 PageRank被看作用户行为的模型。

我们假设网上冲浪是随机的,不断点击链接,从不返回,最终烦了,另外随机选一个网页重新开始冲浪。随机访问一个网页的可能性就是它的PageRank值。制动因子d是随机访问一个网页烦了的可能性,随机另选一个网页。对单个网页或一组网页,一个重要的变量加入到制动因子d中。这允许个人可以故意地误导系统,以得到较高的PageRank值。我们还有其它的PageRank算法,见98页。

另外的直觉判断是一个网页有很多网页指向它,或者一些PageRank值高的网页指向它,则这个网页很重要。直觉地,在Web中,一个网页被很多网页引用,那么这个网页值得一看。一个网页被象Yahoo这样重要的主页引用即使一次,也值得一看。如果一个网页的质量不高,或者是死链接,象Yahoo这样的主页不会链向它。PageRank处理了这两方面因素,并通过网络链接递归地传递。

2.2链接描述文字(Anchor Text):

我们的搜索引擎对链接文本进行了特殊的处理。大多数搜索引擎把链接文字和它所链向的网页(the page that the link is on)联系起来。另外,把它和链接所指向的网页联系起来。这有几点好处。
第一,通常链接描述文字比网页本身更精确地描述该网页。
第二,链接描述文字可能链向的文档不能被文本搜索引擎检索到,例如图像,程序和数据库。有可能使返回的网页不能被抓到。注意哪些抓不到的网页将会带来一些问题。在返回给用户前检测不了它们的有效性。这种情况搜索引擎可能返回一个根本不存在的网页,但是有超级链接指向它。然而这种结果可以被挑出来的,所以此类的问题很少发生。链接描述文字是对被链向网页的宣传,这个思想被用在World Wide Web Worm 中,主要因为它有助于搜索非文本信息,能够用少量的已下载文档扩大搜索范围。我们大量应用链接描述文字,因为它有助于提高搜索结果的质量。有效地利用链接描述文字技术上存在一些困难,因为必须处理大量的数据。现在我们能抓到24000000个网页,已经检索到259000000多个链接描述文字。

2.3其它特点除了PageRank和应用链接描述文字外,Google还有一些其它特点。

第一,所有hit都有位置信息,所以它可以在搜索中广泛应用邻近性(proximity)。
第二,Google跟踪一些可视化外表细节,例如字号。黑体大号字比其它文字更重要。
第三,知识库存储了原始的全文html网页。

3  有关工作

Web 检索研究的历史简短。World Wide Web Worm()是最早的搜索引擎之一。后来出现了一些用于学术研究的搜索引擎,现在它们中的大多数被上市公司拥有。与Web的增长和搜索引擎的重要性相比,有关当今搜索引擎技术的优秀论文相当少。根据Michael Mauldin(Lycos Inc的首席科学家)) ,“各种各样的服务(包括Lycos)非常关注这些数据库的细节。”虽然在搜索引擎的某些特点上做了大量工作。具有代表性的工作有,对现有商业搜索引擎的结果进行传递,或建立小型的个性化的搜索引擎。最后有关信息检索系统的研究很多,尤其在有组织机构集合(well controlled collections)方面。在下面两节,我们将讨论在信息检索系统中的哪些领域需要改进以便更好的工作在Web上。

3.1信息检索信息检索系统诞生在几年前,并发展迅速。

然而大多数信息检索系统研究的对象是小规模的单一的有组织结构的集合,例如科学论文集,或相关主题的新闻故事。实际上,信息检索的主要基准,(the Text Retrieval Conference),用小规模的、有组织结构的集合作为它们的基准。

大型文集基准只有20GB,相比之下,我们抓到的24000000个网页占147GB。在TREC上工作良好的系统,在Web上却不一定产生好的结果。例如,标准向量空间模型企图返回和查询请求最相近的文档,把查询请求和文档都看作由出现在它们中的词汇组成的向量。在Web环境下,这种策略常常返回非常短的文档,这些文档往往是查询词再加几个字。例如,查询“Bill Clinton”,返回的网页只包含“Bill Clinton Sucks”,这是我们从一个主要搜索引擎中看到的。网络上有些争议,用户应该更准确地表达他们想查询什么,在他们的查询请求中用更多的词。我们强烈反对这种观点。如果用户提出象“Bill Clinton”这样的查询请求,应该得到理想的查询结果,因为这个主题有许多高质量的信息。象所给的例子,我们认为信息检索标准需要发展,以便有效地处理Web数据。

3.2  有组织结构的集合(Well Controlled Collections)与Web的不同点

Web是完全无组织的异构的大量文档的集合。Web 中的文档无论内在信息还是隐含信息都存在大量的异构性。例如,文档内部就用了不同的语言(既有人类语言又有程序),词汇(email地址,链接,邮政编码,电话号码,产品号),类型(文本,HTML,PDF,图像,声音),有些甚至是机器创建的文件(log文件,或数据库的输出)。可以从文档中推断出来,但并不包含在文档中的信息称为隐含信息。隐含信息包括来源的信誉,更新频率,质量,访问量和引用。不但隐含信息的可能来源各种各样,而且被检测的信息也大不相同,相差可达好几个数量级。例如,一个重要主页的使用量,象Yahoo 每天浏览数达到上百万次,于此相比无名的历史文章可能十年才被访问一次。很明显,搜索引擎对这两类信息的处理是不同的。 Web与有组织结构集合之间的另外一个明显区别是,事实上,向Web上传信息没有任何限制。灵活利用这点可以发布任何对搜索引擎影响重大的信息,使路由阻塞,加上为牟利故意操纵搜索引擎,这些已经成为一个严重的问题。这些问题还没有被传统的封闭的信息检索系统所提出来。它关心的是元数据的努力,这在Web搜索引擎中却不适用,因为网页中的任何文本都不会向用户声称企图操纵搜索引擎。甚至有些公司为牟利专门操纵搜索引擎。

4 系统分析(System Anatomy)

首先,我们提供高水平的有关体系结构的讨论。然后,详细描述重要的数据结构。最后,主要应用:抓网页,索引,搜索将被严格地检查。 Figure 1. High Level Google Architecture

4.1Google体系结构概述

这一节,我们将看看整个系统是如何工作的(give a high level),见图1。本节不讨论应用和数据结构,在后几节中讨论。为了效率大部分Google是用c或c++实现的,既可以在Solaris也可以在Linux上运行。

Google 系统中,抓网页(下载网页)是由几个分布式crawlers完成的。一个URL服务器负责向crawlers提供URL列表。抓来的网页交给存储服务器 storeserver。然后,由存储服务器压缩网页并把它们存到知识库repository中。每个网页都有一个ID,称作docID,当新URL从网页中分析出时,就被分配一个docID。由索引器和排序器负责建立索引index function。索引器从知识库中读取文档,对其解压缩和分析。每个文档被转换成一组词的出现情况,称作命中hits。Hits 纪录了词,词在文档中的位置,最接近的字号,大小写。索引器把这些hits分配到一组桶barrel中,产生经过部分排序后的索引。索引器的另一个重要功能是分析网页中所有的链接,将有关的重要信息存在链接描述anchors文件中。该文件包含了足够的信息,可以用来判断每个链接链出链入节点的信息,和链接文本。 URL分解器resolver阅读链接描述anchors文件,并把相对URL转换成绝对URL,再转换成docID。为链接描述文本编制索引,并与它所指向的docID关联起来。同时建立由docID对组成的链接数据库。用于计算所有文档的PageRank值。用docID分类后的barrels,送给排序器sorter,再根据wordID进行分类,建立反向索引inverted index。这个操作要恰到好处,以便几乎不需要暂存空间。排序器还给出docID和偏移量列表,建立反向索引。一个叫DumpLexicon的程序把这个列表和由索引器产生的字典结合在一起,建立一个新的字典,供搜索器使用。这个搜索器就是利用一个Web服务器,使用由DumpLexicon所生成的字典,利用上述反向索引以及页面等级PageRank来回答用户的提问。

4.2  主要数据结构

经过优化的Google数据结构,能够用较小的代价抓取大量文档,建立索引和查询。虽然近几年CPU和输入输出速率迅速提高。磁盘寻道仍然需要10ms。任何时候Google系统的设计都尽可能地避免磁盘寻道。这对数据结构的设计影响很大。

4.2.1  大文件

大文件BigFiles是指虚拟文件生成的多文件系统,用长度是64位的整型数据寻址。多文件系统之间的空间分配是自动完成的。BigFiles包也处理已分配和未分配文件描述符。由于操纵系统不能满足我们的需要,BigFiles也支持基本的压缩选项。

4.2.2  知识库

Repository Data Structure 知识库包含每个网页的全部HTML。每个网页用zlib(见RFC1950)压缩。压缩技术的选择既要考虑速度又要考虑压缩率。我们选择zlib的速度而不是压缩率很高的bzip。知识库用bzip的压缩率接近4:1。而用zlib的压缩率是3:1。文档一个挨着一个的存储在知识库中,前缀是docID,长度,URL,见图2。访问知识库不需要其它的数据结构。这有助于数据一致性和升级。用其它数据结构重构系统,我们只需要修改知识库和crawler错误列表文件。

4.2.3 文件索引

文件索引保存了有关文档的一些信息。索引以docID的顺序排列,定宽ISAM(Index sequential access mode)。每条记录包括当前文件状态,一个指向知识库的指针,文件校验和,各种统计表。如果一个文档已经被抓到,指针指向docinfo文件,该文件的宽度可变,包含了URL和标题。否则指针指向包含这个URL的URL列表。这种设计考虑到简洁的数据结构,以及在查询中只需要一个磁盘寻道时间就能够访问一条记录。还有一个文件用于把URL转换成docID。它是URL校验和与相应docID的列表,按校验和排序。要想知道某个URL的docID,需要计算URL的校验和,然后在校验和文件中执行二进制查找,找到它的docID。通过对这个文件进行合并,可以把一批URL转换成对应的docID。URL分析器用这项技术把URL转换成docID。这种成批更新的模式是至关重要的,否则每个链接都需要一次查询,假如用一块磁盘,322‘000’000个链接的数据集合将花费一个多月的时间。

4.2.4 词典

词典有几种不同的形式。和以前系统的重要不同是,词典对内存的要求可以在合理的价格内。现在实现的系统,一台256M内存的机器就可以把词典装入到内存中。现在的词典包含14000000词汇(虽然一些很少用的词汇没有加入到词典中)。它执行分两部分—词汇表(用null分隔的连续串)和指针的哈希表。不同的函数,词汇表有一些辅助信息,这超出了本文论述的范围。

4.2.5 hit list

hit list是一篇文档中所出现的词的列表,包括位置,字号,大小写。Hit list占很大空间,用在正向和反向索引中。因此,它的表示形式越有效越好。我们考虑了几种方案来编码位置,字号,大小写—简单编码(3个整型数),紧凑编码(支持优化分配比特位),哈夫曼编码。Hit的详细信息见图3。我们的紧凑编码每个hit用2字节。有两种类型hit,特殊hit和普通hit。特殊hit包含URL,标题,链接描述文字,meta tag。普通hit包含其它每件事。它包括大小写特征位,字号,12比特用于描述词在文档中的位置(所有超过4095的位置标记为4096)。字号采用相对于文档的其它部分的相对大小表示,占3比特(实际只用7个值,因为111标志是特殊hit)。特殊hit由大小写特征位,字号位为7表示它是特殊hit,用4比特表示特殊hit的类型,8比特表示位置。对于anchor hit八比特位置位分出4比特用来表示在anchor中的位置,4比特用于表明anchor出现的哈希表hash of the docID。短语查询是有限的,对某些词没有足够多的anchor。我们希望更新anchor hit的存储方式,以便解决地址位和docIDhash域位数不足的问题。

因为搜索时,你不会因为文档的字号比别的文档大而特殊对待它,所以采用相对字号。 hit表的长度存储在hit前。为节省空间hit表长度,在正向索引中和wordID结合在一起,在反向索引中和docID结合存储。这就限制它相应地只占8到5比特(用些技巧,可以从wordID中借8bit)如果大于这些比特所能表示的长度,用溢出码填充,其后两字节是真正的长度。 Figure 3. Forward and Reverse Indexes and the Lexicon

4.2.6 正向索引

实际上,正向索引已经部分排序。它被存在一定数量的barrel中(我们用64个barrels)。每个barrel装着一定范围的wordID。如果一篇文档中的词落到某个barrel,它的docID将被记录到这个barrel中,紧跟着那些词(文档中所有的词汇,还是落入该barrel中的词汇)对应的hitlist。这种模式需要稍多些的存储空间,因为一个docID被用多次,但是它节省了桶数和时间,最后排序器进行索引时降低编码的复杂度。更进一步的措施是,我们不是存储docID本身,而是存储相对于该桶最小的docID的差。用这种方法,未排序的barrel的docID只需24 位,省下8位记录hitlist长。

4.2.7 反向索引

除了反向索引由sorter加工处理之外,它和正向索引包含相同的桶。对每个有效的docID,字典包含一个指向该词所在桶的指针。它指向由docID和它的相应hitlist组成的doclish,这个doclist代表了所有包含该词的文档。 doclist中docID的顺序是一个重要的问题。最简单的解决办法是用doclish排序。这种方法合并多个词时很快。另一个可选方案是用文档中该词出现的次数排序。这种方法回答单词查询,所用时间微不足道。当多词查询时几乎是从头开始。并且当用其它Rank算法改进索引时,非常困难。我们综合了这两种方法,建立两组反向索引barrel,一组barrels的hitlist只包含标题和anchor hit,另一组barrel包含全部的hitlist。我们首先查第一组索引桶,看有没有匹配的项,然后查较大的那组桶。

4.3   抓网页运行

网络爬行机器人是一项具有挑战性的任务。执行的性能和可靠性甚至更重要,还有一些社会焦点。网络爬行是一项非常薄弱的应用,它需要成百上千的web服务器和各种域名服务器的参与,这些服务器不是我们系统所能控制的。为了覆盖几十亿的网页,Google拥有快速的分布式网络爬行系统。一个URL服务器给若干个网络爬行机器人(我们采用3个)提供URL列表。URL服务器和网络爬行机器人都是用Python实现的。每个网络爬行机器人可以同时打开300个链接。抓取网页必须足够快。最快时,用4个网络爬行机器人每秒可以爬行100个网页。速率达每秒600K。执行的重点是找 DNS。每个网络爬行机器人有它自己的DNS cache,所以它不必每个网页都查DNS。每一百个连接都有几种不同的状态:查DNS,连接主机,发送请求,接收回答。这些因素使网络爬行机器人成为系统比较复杂的部分。它用异步IO处理事件,若干请求队列从一个网站到另一个网站不停的抓取网页。运行一个链接到500多万台服务器的网页爬行机器人,产生1千多万登陆口,导致了大量的Email和电话。因为网民众多,总有些人不知道网络爬行机器人是何物,这是他们看到的第一个网络爬行机器人。几乎每天我们都会收到这样的Email“哦,你从我们的网站看了太多的网页,你想干什么?”还有一些人不知道网络搜索机器人避免协议(the robots exclusion protocol),以为他们的网页上写着“版权所有,勿被索引”的字样就会被保护不被索引,不必说,这样的话很难被web crawler理解。因为数据量如此之大,还会遇到一些意想不到的事情。例如,我们的系统曾经企图抓一个在线游戏,结果抓到了游戏中的大量垃圾信息。解决这个问题很简单。但是我们下载了几千万网页后才发现了这个问题。因为网页和服务器的种类繁多,实际上不在大部分Internet上运行它就测试一个网页爬行机器人是不可能。总是有几百个隐含的问题发生在整个web的一个网页上,导致网络爬行机器人崩溃,或者更糟,导致不可预测的不正确的行为。能够访问大部分Internet的系统必须精力充沛并精心测试过。由于象crawler这样大型复杂的系统总是产生这样那样的问题,因此花费一些资源读这些 Email,当问题发生时解决它,是有必要的。

4.4 Web索引分析

任何运行在整个Web上的分析器必须能够处理可能包含错误的大型集合。范围从HTML标记到标记之间几K字节的0,非ASCII字符,几百层HTML标记的嵌套,各种各样令人难以想象的错误。为了获得最大的速度,我们没有采用YACC产生上下文无关文法CFG分析器,而是采用灵活的方式产生词汇分析器,它自己配有堆栈。分析器的改进大大提高了运行速度,它的精力如此充沛完成了大量工作。把文档装入barrel建立索引—分析完一篇文档,之后把该文档装入barrel中,用内存中的hash表—字典,每个词汇被转换成一个 wordID。当hash表字典中加入新的项时,笨拙地存入文件。一旦词汇被转换成wordID,它们在当前文档的出现就转换成hitlist,被写进正向barrel。索引阶段并行的主要困难是字典需要共享。

我们采用的方法是,基本字典中有140万个固定词汇,不在基本字典中的词汇写入日志,而不是共享字典。这种方法多个索引器可以并行工作,最后一个索引器只需处理一个较小的额外词汇日志。排序—为了建立反向索引,排序器读取每个正向barrel,以 wordID排序,建立只有标题anchor hi t的反向索引barrel和全文反向索引barrel。这个过程一次只处理一个barrel,所以只需要少量暂存空间。排序阶段也是并行的,我们简单地同时运行尽可能多的排序器,不同的排序器处理不同的桶。由于barrel不适合装入主存,排序器进一步依据wordID和docID把它分成若干篮子,以便适合装入主存。然后排序器把每个篮子装入主存进行排序,并把它的内容写回到短反向barrel和全文反向barrel。

4.5搜索

搜索的目标是提供有效的高质量的搜索结果。多数大型商业搜索引擎好像在效率方面花费了很大力气。因此我们的研究以搜索质量为重点,相信我们的解决方案也可以用到那些商业系统中。
Google查询评价过程见图4。

1. 分析查询。
2. 把词汇转换成wordID。
3. 在短barrel中查找每个词汇doclist的开头。
4. 扫描doclist直到找到一篇匹配所有关键词的文档
5. 计算该文档的rank
6. 如果我们在短barrel,并且在所有doclist的末尾,开始从全文barrel的doclist的开头查找每个词,goto 第四步
7. 如果不在任何doclist的结尾,返回第四步。
8. 根据rank排序匹配文档,返回前k个。图4 Google查询评价在有限的响应时间内,一旦找到一定数量的匹配文档,搜索引擎自动执行步骤8。这意味着,返回的结果是子优化的。我们现在研究其它方法来解决这个问题。过去根据PageRank排序hit,看来能够改进这种状况。
4.5.1 Ranking系统

Google比典型搜索引擎保存了更多的web信息。每个hitlist包括位置,字号,大小写。另外,我们还考虑了链接描述文字。Rank 综合所有这些信息是困难的。ranking函数设计依据是没有某个因素对rank影响重大。首先,考虑最简单的情况—单个词查询。为了单个词查询中一个文档的rank,Goole在文档的hitlist中查找该词。Google认为每个hit是几种不同类型(标题,链接描述文字anchor,URL,普通大字号文本,普通小字号文本,……)之一,每种有它自己的类型权重。类型权重建立了一个类型索引向量。Google计算hitlist中每种hit的数量。然后每个hit数转换成count-weight。Count-weight开始随hit数线性增加,很快逐渐停止,以至于hit数与此不相关。我们计算count-weight向量和type-weight向量的标量积作为文档的IR值。最后IR值结合PageRank作为文档的最后rank。 对于多词查询,更复杂些。现在,多词hitlist必须同时扫描,以便关键词出现在同一文档中的权重比分别出现时高。相邻词的hit一起匹配。对每个匹配hit 的集合计算相邻度。相邻度基于hit在文档中的距离,分成10个不同的bin值,范围从短语匹配到根本不相关。不仅计算每类hit数,而且要计算每种类型的相邻度,每个类型相似度对,有一个类型相邻度权type-prox-weight。Count转换成count-weight,计算count-weight、 type-prox-weight的标量积作为IR值。应用某种debug mode所有这些数和矩阵与查询结果一起显示出来。这些显示有助于改进rank系统。

4.5.2  反馈

rank 函数有很多参数象type-weight和type-prox-weight。指明这些参数的正确值有点黑色艺术black art。为此,我们的搜索引擎有一个用户反馈机制。值得信任的用户可以随意地评价返回的结果。保存反馈。然后,当修改rank函数时,对比以前搜索的 rank,我们可以看到修改带来的的影响。虽然不是十全十美,但是它给出了一些思路,当rank函数改变时对搜索结果的影响。

5   执行和结果

搜索结果的质量是搜索引擎最重要的度量标准。完全用户评价体系超出了本文的论述范围,对于大多数搜索,我们的经验说明Google的搜索结果比那些主要的商业搜索引擎好。作为一个应用PageRank,链接描述文字,相邻度的例子,图4给出了Google搜索bill Clinton的结果。它说明了Google的一些特点。服务器对结果进行聚类。这对过滤结果集合相当有帮助。这个查询,相当一部分结果来自 whitehouse.gov域,这正是我们所需要的。现在大多数商业搜索引擎不会返回任何来自whitehouse.gov的结果,这是相当不对的。注意第一个搜索结果没有标题。因为它不是被抓到的。Google是根据链接描述文字决定它是一个好的查询结果。同样地,第五个结果是一个Email地址,当然是不可能抓到的。也是链接描述文字的结果。所有这些结果质量都很高,最后检查没有死链接。因为它们中的大部分PageRank值较高。PageRank 百分比用红色线条表示。没有结果只含Bill没有Clinton或只含Clinton没有Bill。因为词出现的相近性非常重要。当然搜索引擎质量的真实测试包含广泛的用户学习或结果分析,此处篇幅有限,请读者自己去体验Google,http://google.stanford.edu/

5.1存储需求

除了搜索质量,Google的设计可以随着Web规模的增大而有效地增大成本。一方面有效地利用存储空间。表1列出了一些统计数字的明细表和Google存储的需求。由于压缩技术的应用知识库只需53GB的存储空间。是所有要存储数据的三分之一。按当今磁盘价格,知识库相对于有用的数据来说比较便宜。搜索引擎需要的所有数据的存储空间大约55GB。大多数查询请求只需要短反向索引。文件索引应用先进的编码和压缩技术,一个高质量的搜索引擎可以运行在7GB的新PC。

5.2  系统执行

搜索引擎抓网页和建立索引的效率非常重要。Google 的主要操作是抓网页,索引,排序。很难测试抓全部网页需要多少时间,因为磁盘满了,域名服务器崩溃,或者其它问题导致系统停止。总的来说,大约需要9天时间下载26000000网页(包括错误)。然而,一旦系统运行顺利,速度非常快,下载最后11000000网页只需要63小时,平均每天4000000网页,每秒48.5个网页。索引器和网络爬行机器人同步运行。索引器比网络爬行机器人快。因为我们花费了大量时间优化索引器,使它不是瓶颈。这些优化包括批量更新文档索引,本地磁盘数据结构的安排。索引器每秒处理54个网页。排序器完全并行,用4台机器,排序的整个过程大概需要24小时。

5.3搜索执行改进

搜索执行不是我们研究的重点。当前版本的Google可以在1到10秒间回答查询请求。时间大部分花费在NFS磁盘IO上(由于磁盘普遍比机器慢)。进一步说,Google没有做任何优化,例如查询缓冲区,常用词汇子索引,和其它常用的优化技术。我们倾向于通过分布式,硬件,软件,和算法的改进来提高Google的速度。我们的目标是每秒能处理几百个请求。表2有几个现在版本Google响应查询时间的例子。它们说明IO缓冲区对再次搜索速度的影响。

6   结论

Google设计成可伸缩的搜索引擎。主要目标是在快速发展的World Wide Web上提供高质量的搜索结果。Google应用了一些技术改进搜索质量包括PageRank,链接描述文字,相邻信息。进一步说,Google是一个收集网页,建立索引,执行搜索请求的完整的体系结构。

6.1 未来的工作

大型Web搜索引擎是个复杂的系统,还有很多事情要做。我们直接的目标是提高搜索效率,覆盖大约100000000个网页。一些简单的改进提高了效率包括请求缓冲区,巧妙地分配磁盘空间,子索引。另一个需要研究的领域是更新。我们必须有一个巧妙的算法来决定哪些旧网页需要重新抓取,哪些新网页需要被抓取。这个目标已经由实现了。受需求驱动,用代理cache创建搜索数据库是一个有前途的研究领域。我们计划加一些简单的已经被商业搜索引擎支持的特征,例如布尔算术符号,否定,填充。然而另外一些应用刚刚开始探索,例如相关反馈,聚类(Google现在支持简单的基于主机名的聚类)。我们还计划支持用户上下文(象用户地址),结果摘要。我们正在扩大链接结构和链接文本的应用。简单的实验证明,通过增加用户主页的权重或书签,PageRank可以个性化。对于链接文本,我们正在试验用链接周围的文本加入到链接文本。Web搜索引擎提供了丰富的研究课题。如此之多以至于我们不能在此一一列举,因此在不久的将来,我们希望所做的工作不止本节提到的。

6.2   高质量搜索

当今Web搜索引擎用户所面临的最大问题是搜索结果的质量。结果常常是好笑的,并且超出用户的眼界,他们常常灰心丧气浪费了宝贵的时间。例如,一个最流行的商业搜索引擎搜索“Bill Clillton”的结果是the Bill Clinton Joke of the Day: April 14, 1997。Google的设计目标是随着Web的快速发展提供高质量的搜索结果,容易找到信息。为此,Google大量应用超文本信息包括链接结构和链接文本。Google还用到了相邻性和字号信息。评价搜索引擎是困难的,我们主观地发现Google的搜索质量比当今商业搜索引擎高。通过PageRank分析链接结构使Google能够评价网页的质量。用链接文本描述链接所指向的网页有助于搜索引擎返回相关的结果(某种程度上提高了质量)。最后,利用相邻性信息大大提高了很多搜索的相关性。

6.3可升级的体系结构

除了搜索质量,Google设计成可升级的。空间和时间必须高效,处理整个Web时固定的几个因素非常重要。实现Google系统,CPU、访存、内存容量、磁盘寻道时间、磁盘吞吐量、磁盘容量、网络IO都是瓶颈。在一些操作中,已经改进的Google克服了一些瓶颈。Google的主要数据结构能够有效利用存储空间。进一步,网页爬行,索引,排序已经足够建立大部分web索引,共24000000个网页,用时不到一星期。我们希望能在一个月内建立 100000000网页的索引。

6.4  研究工具

Google不仅是高质量的搜索引擎,它还是研究工具。Google搜集的数据已经用在许多其它论文中,提交给学术会议和许多其它方式。最近的研究,例如,提出了Web查询的局限性,不需要网络就可以回答。这说明Google不仅是重要的研究工具,而且必不可少,应用广泛。我们希望Google是全世界研究者的资源,带动搜索引擎技术的更新换代。

7、致谢

Scott Hassan and Alan Steremberg评价了Google的改进。他们的才智无可替代,作者由衷地感谢他们。感谢Hector Garcia-Molina, Rajeev Motwani, Jeff Ullman, and Terry Winograd和全部WebBase开发组的支持和富有深刻见解的讨论。最后感谢IBM,Intel,Sun和投资者的慷慨支持,为我们提供设备。这里所描述的研究是Stanford综合数字图书馆计划的一部分,由国家科学自然基金支持,合作协议号IRI-9411306。DARPA ,NASA,Interva研究,Stanford数字图书馆计划的工业合作伙伴也为这项合作协议提供了资金。

参考文献。

  • [Abiteboul 97] Serge Abiteboul and Victor Vianu, Queries and Computation on the Web. Proceedings of the International Conference on Database Theory. Delphi, Greece 1997.
  • [Bagdikian 97] Ben H. Bagdikian. The Media Monopoly. 5th Edition. Publisher: Beacon, ISBN: 0807061557
  • [Chakrabarti 98] S.Chakrabarti, B.Dom, D.Gibson, J.Kleinberg, P. Raghavan and S. Rajagopalan. Automatic Resource Compilation by Analyzing Hyperlink Structure and Associated Text. Seventh International Web Conference (WWW 98). Brisbane, Australia, April 14-18, 1998.
  • [Cho 98] Junghoo Cho, Hector Garcia-Molina, Lawrence Page. Efficient Crawling Through URL Ordering. Seventh International Web Conference (WWW 98). Brisbane, Australia, April 14-18, 1998.
  • [Gravano 94] Luis Gravano, Hector Garcia-Molina, and A. Tomasic. The Effectiveness of GlOSS for the Text-Database Discovery Problem. Proc. of the 1994 ACM SIGMOD International Conference On Management Of Data, 1994.
  • [Kleinberg 98] Jon Kleinberg, Authoritative Sources in a Hyperlinked Environment, Proc. ACM-SIAM Symposium on Discrete Algorithms, 1998.
  • [Marchiori 97] Massimo Marchiori. The Quest for Correct Information on the Web: Hyper Search Engines. The Sixth International WWW Conference (WWW 97). Santa Clara, USA, April 7-11, 1997.
  • [McBryan 94] Oliver A. McBryan. GENVL and WWWW: Tools for Taming the Web. First International Conference on the World Wide Web. CERN, Geneva (Switzerland), May 25-26-27 1994. http://www.cs.colorado.edu/home/mcbryan/mypapers/www94.ps
  • [Page 98] Lawrence Page, Sergey Brin, Rajeev Motwani, Terry Winograd. The PageRank Citation Ranking: Bringing Order to the Web. Manuscript in progress.http://google.stanford.edu/~backrub/pageranksub.ps
  • [Pinkerton 94] Brian Pinkerton, Finding What People Want: Experiences with the WebCrawler. The Second International WWW Conference Chicago, USA, October 17-20, 1994. http://info.webcrawler.com/bp/WWW94.html
  • [Spertus 97] Ellen Spertus. ParaSite: Mining Structural Information on the Web.The Sixth International WWW Conference (WWW 97). Santa Clara, USA, April 7-11, 1997.
  • [TREC 96] Proceedings of the fifth Text REtrieval Conference (TREC-5).Gaithersburg, Maryland, November 20-22, 1996. Publisher: Department of Commerce, National Institute of Standards and Technology. Editors: D. K. Harman and E. M. Voorhees. Full text at: http://trec.nist.gov/
  • [Witten 94] Ian H Witten, Alistair Moffat, and Timothy C. Bell. Managing Gigabytes: Compressing and Indexing Documents and Images. New York: Van Nostrand Reinhold, 1994.
  • [Weiss 96] Ron Weiss, Bienvenido Velez, Mark A. Sheldon, Chanathip Manprempre, Peter Szilagyi, Andrzej Duda, and David K. Gifford. HyPursuit: A Hierarchical Network Search Engine that Exploits Content-Link Hypertext Clustering. Proceedings of the 7th ACM Conference on Hypertext. New York, 1996.

图片附录:

图1  Google系统的工作流程图
(注:原图来自Sergey Brin and Lawrence Page, The Anatomy of a Large-Scale Hypertextual. Web Search Engine, 1998.http://www-db.stanford.edu/%7Ebackrub/Google.html)

①Google使用高速的分布式爬行器(Crawler)系统中的漫游遍历器(Googlebot)定时地遍历网页,将遍历到的网页送到存储服务器(Store Server)中。
② 存储服务器使用zlib格式压缩软件将这些网页进行无损压缩处理后存入数据库Repository中。Repository获得了每个网页的完全Html 代码后,对其压缩后的网页及URL进行分析,记录下网页长度、URL、URL长度和网页内容,并赋予每个网页一个文档号(docID),以便当系统出现故障的时候,可以及时完整地进行网页的数据恢复。
③索引器(Indexer)从Repository中读取数据,以后做以下四步工作:
④(a) 将读取的数据解压缩后进行分析,它将网页中每个有意义的词进行统计后,转化为关键词(wordID)的若干索引项(Hits),生成索引项列表,该列表包括关键词、关键词的位置、关键词的大小和大小写状态等。索引项列表被存入到数据桶(Barrels)中,并生成以文档号(docID)部分排序的顺排档索引。

索引项根据其重要程度分为两种:当索引项中的关键词出现在URL、标题、锚文本(Anchor Text)和标签中时,表示该索引项比较重要,称为特殊索引项(Fancy Hits);其余情况则称为普通索引项(Plain Hits)。在系统中每个Hit用两个字节(byte)存储结构表示:特殊索引项用1位(bit)表示大小写,用二进制代码111(占3位)表示是特殊索引项,其余12位有4位表示特殊索引项的类型(即hit是出现在URL、标题、链接结点还是标签中),剩下8位表示hit在网页中的具体位置;普通索引项是用1位表示大小写,3位表示字体大小,其余12位表示在网页中的具体位置。
顺排档索引和Hit的存储结构如图3所示。

图3 顺排档索引和Hit的存储结构

值得注意的是,当特殊索引项来自Anchor Text时,特殊索引项用来表示位置的信息(8位)将分为两部分:4位表示Anchor Text出现的具体位置,另4位则用来与表示Anchor Text所链接网页的docID相连接,这个docID是由URL Resolver经过转化存入顺排档索引的。
(b)索引器除了对网页中有意义的词进行分析外,还分析网页的所有超文本链接,将其Anchor Text、URL指向等关键信息存入到Anchor文档库中。
(c)索引器生成一个索引词表(Lexicon),它包括两个部分:关键词的列表和指针列表,用于倒排档文档相连接(如图3所示)。
(d) 索引器还将分析过的网页编排成一个与Repository相连接的文档索引(Document Index),并记录下网页的URL和标题,以便可以准确查找出在Repository中存储的原网页内容。而且把没有分析的网页传给URL Server,以便在下一次工作流程中进行索引分析。
⑤URL分析器(URL Resolver)读取Anchor文档中的信息,然后做⑥中的工作。
⑥(a) 将其锚文本(Anchor Text)所指向的URL转换成网页的docID;(b)将该docID与原网页的docID形成“链接对”,存入Link数据库中;(c)将 Anchor Text指向的网页的docID与顺排档特殊索引项Anchor Hits相连接。
⑦数据库Link记录了网页的链接关系,用来计算网页的PageRank值。
⑧文档索引(Document Index)把没有进行索引分析的网页传递给URL Server,URL Server则向Crawler提供待遍历的URL,这样,这些未被索引的网页在下一次工作流程中将被索引分析。
⑨排序器(Sorter)对数据桶(Barrels)的顺排档索引重新进行排序,生成以关键词(wordID)为索引的倒排档索引。倒排档索引结构如图4所示:

图4  倒排档索引结构
⑩ 将生成的倒排档索引与先前由索引器产生的索引词表(Lexicon)相连接产生一个新的索引词表供搜索器(Searcher)使用。搜索器的功能是由网页服务器实现的,根据新产生的索引词表结合上述的文档索引(Document Index)和Link数据库计算的网页PageRank值来匹配检索。
在执行检索时,Google通常遵循以下步骤(以下所指的是单个检索词的情况):
(1)将检索词转化成相应的wordID;
(2)利用Lexicon,检索出包含该wordID的网页的docID;
(3)根据与Lexicon相连的倒排档索引,分析各网页中的相关索引项的情况,计算各网页和检索词的匹配程度,必要时调用顺排档索引;
(4)根据各网页的匹配程度,结合根据Link产生的相应网页的PageRank情况,对检索结果进行排序;
(5)调用Document Index中的docID及其相应的URL,将排序结果生成检索结果的最终列表,提供给检索用户。
用户检索包含多个检索词的情况与以上单个检索词的情况类似:先做单个检索词的检索,然后根据检索式中检索符号的要求进行必要的布尔操作或其他操作。

 

卡尔曼滤波学习,平衡

现在有些疑惑的是 Uk 控制变量 是做什么用的,其值每个时刻都会改变,是否是独立于线性方程的测量值。

另外对于非均匀采样(nonuniform sampling)的时间序列(time series)如何处理,也需要考虑,

OpenCV 文档给出了Kalman滤波的过程,其中提到对于扩展kalman滤波可以在跟着过程中改变A,B,H,这样是不是意味着 可以用该时刻的采样间隔来更新 A,B?

来自:http://blog.zol.com.cn/820/article_819211.html

The structure CvKalman is used to keep Kalman filter state. It is created by cvCreateKalman function, updated by cvKalmanPredict and cvKalmanCorrect functions and released by cvReleaseKalman functions. Normally, the structure is used for standard Kalman filter (notation and the formulae below are borrowed from the excellent Kalman tutorial [Welch95]):

xk=A•xk-1+B•uk+wk
zk=H•xk+vk
,

where:

xk (xk-1) - state of the system at the moment k (k-1)
zk - measurement of the system state at the moment k
uk - external control applied at the moment k
wk and vk are normally-distributed process and measurement noise, respectively:

p(w) ~ N(0,Q)
p(v) ~ N(0,R),

that is,
Q – process noise covariance matrix, constant or variable,
R – measurement noise covariance matrix, constant or variable
In case of standard Kalman filter, all the matrices: A, B, H, Q and R are initialized once after CvKalman structure is allocated via cvCreateKalman. However, the same structure and the same functions may be used to simulate extended Kalman filter by linearizing extended Kalman filter equation in the current system state neighborhood, in this case A, B, H (and, probably, Q and R) should be updated on every step.

 

 

[转帖]卡尔曼滤波器

来这里几个月,发现有些问题很多人都很感兴趣。所以在这里希望能尽自己能力跟大家讨论一些力所能及的算法。现在先讨论一下卡尔曼滤波器,如果时间和能力允许,我还希望能够写写其他的算法,例如遗传算法,傅立叶变换,数字滤波,神经网络,图像处理等等。

因为这里不能写复杂的数学公式,所以也只能形象的描述。希望如果哪位是这方面的专家,欢迎讨论更正。

卡尔曼滤波器 – Kalman Filter

1.    什么是卡尔曼滤波器

(What is the Kalman Filter?)

在学习卡尔曼滤波器之前,首先看看为什么叫“卡尔曼”。跟其他著名的理论(例如傅立叶变换,泰勒级数等等)一样,卡尔曼也是一个人的名字,而跟他们不同的是,他是个现代人!

卡尔曼全名Rudolf Emil Kalman,匈牙利数学家,1930年出生于匈牙利首都布达佩斯。1953,1954年于麻省理工学院分别获得电机工程学士及硕士学位。1957年于哥 伦比亚大学获得博士学位。我们现在要学习的卡尔曼滤波器,正是源于他的博士论文和1960年发表的论文《A New Approach to Linear Filtering and Prediction Problems》(线性滤波与预测问题的新方法)。如果对这编论文有兴趣,可以到这里的地址下载: http://www.cs.unc.edu/~welch/media/pdf/Kalman1960.pdf

简单来说,卡尔曼滤波器是一个“optimal recursive data processing algorithm(最优化自回归数据处理算法)”。对于解决很大部分的问题,他是最优,效率最高甚至是最有用的。他的广泛应用已经超过30年,包括机器 人导航,控制,传感器数据融合甚至在军事方面的雷达系统以及导弹追踪等等。近年来更被应用于计算机图像处理,例如头脸识别,图像分割,图像边缘检测等等。

2.卡尔曼滤波器的介绍

(Introduction to the Kalman Filter)

为了可以更加容易的理解卡尔曼滤波器,这里会应用形象的描述方法来讲解,而不是像大多数参考书那样罗列一大堆的数学公式和数学符号。但是,他的5条公式是其核心内容。结合现代的计算机,其实卡尔曼的程序相当的简单,只要你理解了他的那5条公式。

在介绍他的5条公式之前,先让我们来根据下面的例子一步一步的探索。

假设我们要研究的对象是一个房间的温度。根据你的经验判断,这个房间的温度是恒定的,也就是下一分钟的温度等于现在这一分钟的温度(假设我们用一分 钟来做时间单位)。假设你对你的经验不是100%的相信,可能会有上下偏差几度。我们把这些偏差看成是高斯白噪声(White Gaussian Noise),也就是这些偏差跟前后时间是没有关系的而且符合高斯分配(Gaussian Distribution)。另外,我们在房间里放一个温度计,但是这个温度计也不准确的,测量值会比实际值偏差。我们也把这些偏差看成是高斯白噪声。

好了,现在对于某一分钟我们有两个有关于该房间的温度值:你根据经验的预测值(系统的预测值)和温度计的值(测量值)。下面我们要用这两个值结合他们各自的噪声来估算出房间的实际温度值。

假如我们要估算k时刻的是实际温度值。首先你要根据k-1时刻的温度值,来预测k时刻的温度。因为你相信温度是恒定的,所以你会得到k时刻的温度预 测值是跟k-1时刻一样的,假设是23度,同时该值的高斯噪声的偏差是5度(5是这样得到的:如果k-1时刻估算出的最优温度值的偏差是3,你对自己预测 的不确定度是4度,他们平方相加再开方,就是5)。然后,你从温度计那里得到了k时刻的温度值,假设是25度,同时该值的偏差是4度。

由于我们用于估算k时刻的实际温度有两个温度值,分别是23度和25度。究竟实际温度是多少呢?相信自己还是相信温度计呢?究竟相信谁多一点,我们 可以用他们的covariance来判断。因为Kg^2=5^2/(5^2+4^2),所以Kg=0.78,我们可以估算出k时刻的实际温度值 是:23+0.78*(25-23)=24.56度。可以看出,因为温度计的covariance比较小(比较相信温度计),所以估算出的最优温度值偏向 温度计的值。

现在我们已经得到k时刻的最优温度值了,下一步就是要进入k+1时刻,进行新的最优估算。到现在为止,好像还没看到什么自回归的东西出现。对了,在 进入k+1时刻之前,我们还要算出k时刻那个最优值(24.56度)的偏差。算法如下:((1-Kg)*5^2)^0.5=2.35。这里的5就是上面的 k时刻你预测的那个23度温度值的偏差,得出的2.35就是进入k+1时刻以后k时刻估算出的最优温度值的偏差(对应于上面的3)。

就是这样,卡尔曼滤波器就不断的把covariance递归,从而估算出最优的温度值。他运行的很快,而且它只保留了上一时刻的covariance。上面的Kg,就是卡尔曼增益(Kalman Gain)。他可以随不同的时刻而改变他自己的值,是不是很神奇!

下面就要言归正传,讨论真正工程系统上的卡尔曼。

3.    卡尔曼滤波器算法

(The Kalman Filter Algorithm)

在这一部分,我们就来描述源于Dr Kalman 的卡尔曼滤波器。下面的描述,会涉及一些基本的概念知识,包括概率(Probability),随即变量(Random Variable),高斯或正态分配(Gaussian Distribution)还有State-space Model等等。但对于卡尔曼滤波器的详细证明,这里不能一一描述。

首先,我们先要引入一个离散控制过程的系统。该系统可用一个线性随机微分方程(Linear Stochastic Difference equation)来描述:

X(k)=A X(k-1)+B U(k)+W(k)

再加上系统的测量值:

Z(k)=H X(k)+V(k)

上 两式子中,X(k)是k时刻的系统状态,U(k)是k时刻对系统的控制量。A和B是系统参数,对于多模型系统,他们为矩阵。Z(k)是k时刻的测量值,H 是测量系统的参数,对于多测量系统,H为矩阵。W(k)和V(k)分别表示过程和测量的噪声。他们被假设成高斯白噪声(White Gaussian Noise),他们的covariance 分别是Q,R(这里我们假设他们不随系统状态变化而变化)。

对于满足上面的条件(线性随机微分系统,过程和测量都是高斯白噪声),卡尔曼滤波器是最优的信息处理器。下面我们来用他们结合他们的covariances 来估算系统的最优化输出(类似上一节那个温度的例子)。

首先我们要利用系统的过程模型,来预测下一状态的系统。假设现在的系统状态是k,根据系统的模型,可以基于系统的上一状态而预测出现在状态:

X(k|k-1)=A X(k-1|k-1)+B U(k) ……….. (1)

式(1)中,X(k|k-1)是利用上一状态预测的结果,X(k-1|k-1)是上一状态最优的结果,U(k)为现在状态的控制量,如果没有控制量,它可以为0。

到现在为止,我们的系统结果已经更新了,可是,对应于X(k|k-1)的covariance还没更新。我们用P表示covariance:

P(k|k-1)=A P(k-1|k-1) A’+Q ……… (2)

式 (2)中,P(k|k-1)是X(k|k-1)对应的covariance,P(k-1|k-1)是X(k-1|k-1)对应的 covariance,A’表示A的转置矩阵,Q是系统过程的covariance。式子1,2就是卡尔曼滤波器5个公式当中的前两个,也就是对系统的预 测。

现在我们有了现在状态的预测结果,然后我们再收集现在状态的测量值。结合预测值和测量值,我们可以得到现在状态(k)的最优化估算值X(k|k):

X(k|k)= X(k|k-1)+Kg(k) (Z(k)-H X(k|k-1)) ……… (3)

其中Kg为卡尔曼增益(Kalman Gain):

Kg(k)= P(k|k-1) H’ / (H P(k|k-1) H’ + R) ……… (4)

到现在为止,我们已经得到了k状态下最优的估算值X(k|k)。但是为了要另卡尔曼滤波器不断的运行下去直到系统过程结束,我们还要更新k状态下X(k|k)的covariance:

P(k|k)=(I-Kg(k) H)P(k|k-1) ……… (5)

其中I 为1的矩阵,对于单模型单测量,I=1。当系统进入k+1状态时,P(k|k)就是式子(2)的P(k-1|k-1)。这样,算法就可以自回归的运算下去。

卡尔曼滤波器的原理基本描述了,式子1,2,3,4和5就是他的5 个基本公式。根据这5个公式,可以很容易的实现计算机的程序。

下面,我会用程序举一个实际运行的例子。。。

4.    简单例子

(A Simple Example)

这里我们结合第二第三节,举一个非常简单的例子来说明卡尔曼滤波器的工作过程。所举的例子是进一步描述第二节的例子,而且还会配以程序模拟结果。

根据第二节的描述,把房间看成一个系统,然后对这个系统建模。当然,我们见的模型不需要非常地精确。我们所知道的这个房间的温度是跟前一时刻的温度相同的,所以A=1。没有控制量,所以U(k)=0。因此得出:

X(k|k-1)=X(k-1|k-1) ……….. (6)

式子(2)可以改成:

P(k|k-1)=P(k-1|k-1) +Q ……… (7)

因为测量的值是温度计的,跟温度直接对应,所以H=1。式子3,4,5可以改成以下:

X(k|k)= X(k|k-1)+Kg(k) (Z(k)-X(k|k-1)) ……… (8)

Kg(k)= P(k|k-1) / (P(k|k-1) + R) ……… (9)

P(k|k)=(1-Kg(k))P(k|k-1) ……… (10)

现在我们模拟一组测量值作为输入。假设房间的真实温度为25度,我模拟了200个测量值,这些测量值的平均值为25度,但是加入了标准偏差为几度的高斯白噪声(在图中为蓝线)。

为了令卡尔曼滤波器开始工作,我们需要告诉卡尔曼两个零时刻的初始值,是X(0|0)和P(0|0)。他们的值不用太在意,随便给一个就可以了,因 为随着卡尔曼的工作,X会逐渐的收敛。但是对于P,一般不要取0,因为这样可能会令卡尔曼完全相信你给定的X(0|0)是系统最优的,从而使算法不能收 敛。我选了X(0|0)=1度,P(0|0)=10。

该系统的真实温度为25度,图中用黑线表示。图中红线是卡尔曼滤波器输出的最优化结果(该结果在算法中设置了Q=1e-6,R=1e-1)。

附matlab下面的kalman滤波程序:

clear

N=200;

w(1)=0;

w=randn(1,N)

x(1)=0;

a=1;

for k=2:N;

x(k)=a*x(k-1)+w(k-1);

end

V=randn(1,N);

q1=std(V);

Rvv=q1.^2;

q2=std(x);

Rxx=q2.^2;

q3=std(w);

Rww=q3.^2;

c=0.2;

Y=c*x+V;

p(1)=0;

s(1)=0;

for t=2:N;

p1(t)=a.^2*p(t-1)+Rww;

b(t)=c*p1(t)/(c.^2*p1(t)+Rvv);

s(t)=a*s(t-1)+b(t)*(Y(t)-a*c*s(t-1));

p(t)=p1(t)-c*b(t)*p1(t);

end

t=1:N;

plot(t,s,’r’,t,Y,’g’,t,x,’b’);

JavaScript多线程编程

转:http://www.infoq.com/cn/articles/js_multithread

Concurrent.Thread-full下载

虽然有越来越多的网站在应用AJAX技术进行开发,但是构建一个复杂的AJAX应用仍然是一个难题。造成这些困难的主要原因是什么呢?是与服务器的异步通信问题?还是GUI程序设计问题呢?通常这两项工作都是由桌面程序来完成的,那究竟为何开发一个可以实现同样功能的AJAX应用就这么困难呢?

 

AJAX 开发中的难题

让我们通过一个简单的例子来认识这个问题。假设你要建立一个树形结构的公告栏系统(BBS),它可以根据用户请求与服务器进行交互,动态加载每篇文章的信息,而不是一次性从服务器载入所有文章信息。每篇文章有四个相关属性:系统中可以作为唯一标识的ID、发贴人姓名、文章内容以及包含其所有子文章ID的数组信息。首先假定有一个名为getArticle()的函数可以加载一篇文章信息。该函数接收的参数是要加载文章的ID,通过它可从服务器获取文章信息。它返回的对象包含与文章相关的四条属性:id,name,content和children。例程如下:

function ( id ) {
     var a = getArticle(id);
     document.writeln(a.name + "
" + a.content);
 }

然而你也许会注意到,重复用同一个文章ID调用此函数,需要与服务器之间进行反复且无益的通信。想要解决这个问题,可以考虑使用函数 getArticleWithCache(),它相当于一个带有缓存能力的getArticle()。在这个例子中,getArticle()返回的数据只是作为一个全局变量被保存下来:

var cache = {};
 function getArticleWithCache ( id ) {
     if ( !cache[id] ) {
         cache[id] = getArticle(id);
     }
     return cache[id];
 }

现在已将读入的文章缓存起来,让我们再来考虑一下函数backgroundLoad(),它应用我们上面提到的缓存机制加载所有文章信息。其用途是,当读者在阅读某篇文章时,从后台预加载它所有子文章。因为文章数据是树状结构的,所以很容易写一个递归的算法来遍历树并且加载所有的文章:

function backgroundLoad ( ids ) {
     for ( var i=0; i < ids.length; i++ ) {
         var a = getArticleWithCache(ids[i]);
         backgroundLoad(a.children);
     }
 }

backgroundLoad ()函数接收一个ID数组作为参数,然后通过每个ID调用前面定义过的getArticldWithCache()方法,这样就把每个ID对应的文章缓存起来。之后再通过已加载文章的子文章ID数组递归调用backgroundLoad()方法,如此整个文章树就被缓存起来。

到目前为止,一切似乎看起来都很完美。然而,只要你有过开发AJAX应用的经验,你就应该知晓这种幼稚的实现方法根本不会成功,这个例子成立的基础是默认 getArticle()用的是同步通信。可是,作为一条基本原则,JavaScript要求在与服务器进行交互时要用异步通信,因为它是单线程的。就简单性而言,把每一件事情(包括GUI事件和渲染)都放在一个线程里来处理是一个很好的程序模型,因为这样就无需再考虑线程同步这些复杂问题。另一方面,他也暴露了应用开发中的一个严重问题,单线程环境看起来对用户请求响应迅速,但是当线程忙于处理其它事情时(比如说调用getArticle()),就不能对用户的鼠标点击和键盘操作做出响应。

如果在这个单线程环境里进行同步通信会发生什么事情呢?同步通信会中断浏览器的执行直至获得通信结果。在等待通信结果的过程中,由于服务器的调用还没有完成,线程会停止响应用户并保持锁定状态直到调用返回。因为这个原因,当浏览器在等待服务器响应时它不能对用户行为作出响应,所以看起来像是冻结了。当执行 getArticleWithCache()和backgroundLoad()会有同样的问题,因为它们都是基于getArticle()函数的。由于下载所有的文章可能会耗费很可观的一段时间,因此对于backgroundLoad()函数来说,浏览器在此段时间内的冻结就是一个很严重的问题——既然浏览器都已经冻结,当用户正在阅读文章时就不可能首先去执行后台预加载数据,如果这样做连当前的文章都没办法读。

如上所述,既然同步通信在使用中会造成如此严重的问题,JavaScript就把异步通信作为一条基本原则。因此,我们可以基于异步通信改写上面的程序。 JavaScript要求以一种事件驱动的程序设计方式来写异步通信程序。在很多场合中,你都必须指定一个回调程序,一旦收到通信响应,这个函数就会被调用。例如,上面定义的getArticleWithCache()可以写成这样:

var cache = {};
 function getArticleWithCache ( id, callback ) {
     if ( !cache[id] ) {
         callback(cache[id]);
     } else {
         getArticle(id, function( a ){
             cache[id] = a;
             callback(a);
         });
     }
 }

这个程序也在内部调用了getArticle()函数。然而需要注意的是,为异步通信设计的这版getArticle()函数要接收一个函数作为第二个参数。当调用这个getArticle()函数时,与从前一样要给服务器发送一个请求,不同的是,现在函数会迅速返回而非等待服务器的响应。这意味着,当执行权交回给调用程序时,还没有得到服务器的响应。如此一来,线程就可以去执行其它任务直至获得服务器响应,并在此时调用回调函数。一旦得到服务器响应, getArticle()的第二个参数作为预先定义的回调函数就要被调用,服务器的返回值即为其参数。同样的,getArticleWithCache ()也要做些改变,定义一个回调参数作为其第二个参数。这个回调函数将在被传给getArticle()的回调函数中调用,因而它可以在服务器通信结束后被执行。

单是上面这些改动你可能已经认为相当复杂了,但是对backgroundLoad()函数做得改动将会更复杂,它也要被改写成可以处理回调函数的形式:

function backgroundLoad ( ids, callback ) {
     var i = 0;
     function l ( ) {
         if ( i < ids.length ) {
             getArticleWithCache(ids[i++], function( a ){
                 backgroundLoad(a.children, l);
             });
         } else {
             callback();
         }
     }
     l();
 }

改动后的backgroundLoad()函数看上去和我们以前的那个函数已经相去甚远,不过他们所实现的功能并无二致。这意味着这两个函数都接受ID数组作为参数,对于数组里的每个元素都要调用getArticleWithCache(),再应用已经获得子文章ID递归调用backgroundLoad ()。不过同样是对数组的循环访问,新函数中的就不太好辨认了,以前的程序中是用一个for循环语句完成的。为什么实现同样功能的两套函数是如此的大相径庭呢?

这个差异源于一个事实:任何函数在遇到有需要同服务器进行通信情况后,都必须立刻返回,例如getArticleWithCache()。除非原来的函数不在执行当中,否则应当接受服务器响应的回调函数都不能被调用。对于JavaScript,在循环过程中中断程序并在稍后从这个断点继续开始执行程序是不可能的,例如一个for语句。因此,本例利用递归传递回调函数实现循环结构而非一个传统循环语句。对那些熟悉连续传送风格(CPS)的人来说,这就是一个 CPS的手动实现,因为不能使用循环语法,所以即便如前面提到的遍历树那么简单的程序也得写得很复杂。与事件驱动程序设计相关的问题是控制流问题:循环和其它控制流表达式可能比较难理解。

这里还有另外一个问题:如果你把一个没有应用异步通信的函数转换为一个使用异步通信的函数,那么重写的函数将需要一个回调函数作为新增参数,这为已经存在的APIs造成了很大问题,因为内在的改变没有把影响限于内部,而是导致整体混乱的APIs以及API的其它使用者的改变。

造成这些问题目的根本原因是什么呢?没错,正是JavaScript单线程机制导致了这些问题。在单线程里执行异步通信需要事件驱动程序设计和复杂的语句。如果当程序在等待服务器的响应时,有另外一个线程可以来处理用户请求,那么上述复杂技术就不需要了。

试试多线程编程

让我来介绍一下Concurrent.Thread,它是一个允许JavaScript进行多线程编程的库,应用它可以大大缓解上文提及的在AJAX开发中与异步通信相关的困难。这是一个用JavaScript写成的免费的软件库,使用它的前提是遵守Mozilla Public License和GNU General Public License这两个协议。你可以从他们的网站 下载源代码。

马上来下载和使用源码吧!假定你已经将下载的源码保存到一个名为Concurrent.Thread.js的文件夹里,在进行任何操作之前,先运行如下程序,这是一个很简单的功能实现:

<script type="text/javascript" src="Concurrent.Thread.js"></script>
<script type="text/javascript">
Concurrent.Thread.create(function(){ var i = 0; while ( 1 ) {
 document.body.innerHTML += i++ + "<br>";
} });
</script>

执行这个程序将会顺序显示从0开始的数字,它们一个接一个出现,你可以滚屏来看它。现在让我们来仔细研究一下代码,他应用while(1)条件制造了一个不会中止的循环,通常情况下,象这样不断使用一个并且是唯一一个线程的JavaScript程序会导致浏览器看起来象冻结了一样,自然也就不会允许你滚屏。那么为什么上面的这段程序允许你这么做呢?关键之处在于while(1)上面的那条Concurrent.Thread.create()语句,这是这个库提供的一个方法,它可以创建一个新线程。被当做参数传入的函数在这个新线程里执行,让我们对程序做如下微调:

<script type="text/javascript" src="Concurrent.Thread.js"></script>
<script type="text/javascript">
function f ( i ){ while ( 1 ) {
 document.body.innerHTML += i++ + "<br>";
} }
Concurrent.Thread.create(f, 0);
Concurrent.Thread.create(f, 100000);
</script> 

在这个程序里有个新函数f()可以重复显示数字,它是在程序段起始定义的,接着以f()为参数调用了两次create()方法,传给create()方法的第二个参数将会不加修改地传给f()。执行这个程序,先会看到一些从0开始的小数,接着是一些从100,000开始的大数,然后又是接着前面小数顺序的数字。你可以观察到程序在交替显示小数和大数,这说明两个线程在同时运行。

让我来展示Concurrent.Thread的另外一个用法。上面的例子调用create()方法来创建新线程。不调用库里的任何APIs也有可能实现这个目的。例如,前面那个例子可以这样写:

<script type="text/javascript" src="Concurrent.Thread.js"></script>
<script type="text/x-script.multithreaded-js">
var i = 1; while ( 1 ) { document.body.innerHTML += i++ + "<br>"; }
</script> 

在script 标签内,很简单地用JavaScript写了一个无穷循环。你应该注意到标签内的type属性,那里是一个很陌生的值(text/x- script.multithreaded-js),如果这个属性被放在script标签内,那么Concurrent.Thread就会在一个新的线程内执行标签之间的程序。你应当记住一点,在本例一样,必须将Concurrent.Thread库包含进来。

有了Concurrent.Thread,就有可能自如的将执行环境在线程之间进行切换,即使你的程序很长、连续性很强。我们可以简要地讨论下如何执行这种操作。简言之,需要进行代码转换。粗略地讲,首先要把传递给create()的函数转换成一个字符串,接着改写直至它可以被分批分次执行。然后这些程序可以依照调度程序逐步执行。调度程序负责协调多线程,换句话说,它可以在适当的时候做出调整以便每一个修改后的函数都会得到同等机会运行。 Concurrent.Thread实际上并没有创建新的线程,仅仅是在原本单线程的基础上模拟了一个多线程环境。

虽然转换后的函数看起来是运行在不同的线程内,但是实际上只有一个线程在做这所有的事情。在转换后的函数内执行同步通信仍然会造成浏览器冻结,你也许会认为以前的那些问题根本就没有解决。不过你不必耽心,Concurrent.Thread提供了一个应用JavaScript 的异步通信方式实现的定制通信库,它被设计成当一个线程在等待服务器的响应时允许其它线程运行。这个通信库存于 Concurrent.Thread.Http下。它的用法如下所示:

<script type="text/javascript" src="Concurrent.Thread.js"></script>
<script type="text/x-script.multithreaded-js">
var req = Concurrent.Thread.Http.get(url, ["Accept", "*"]);
if (req.status == 200) { alert(req.responseText); }
else { alert(req.statusText); }
</script> 

get()方法,就像它的名字暗示的那样,可以通过HTTP的GET方法获得指定URL的内容,它将目标URL作为第一个参数,将一个代表HTTP请求头的数组作为可选的第二个参数。get()方法与服务器交互,当得到服务器的响应后就返回一个XMLHttpRequest对象作为返回值。当get()方法返回时,已经收到了服务器响应,所以就没必要再用回调函数接收结果。自然,也不必再耽心当程序等待服务器的响应时浏览器冻结的情况了。另外,还有一个 post()方法可以用来发送数据到服务器:

<script type="text/javascript" src="Concurrent.Thread.js"></script>
<script type="text/x-script.multithreaded-js">
var req = Concurrent.Thread.Http.post(url, "key1=val1&key2=val2");
alert(req.statusText);
</script>

post()方法将目的URL作为第一个参数,要发送的内容作为第二个参数。像get()方法那样,你也可以将请求头作为可选的第三个参数。

如果你用这个通信库实现了第一个例子当中的getArticle()方法,那么你很快就能应用文章开头示例的那种简单的方法写出getArticleWithCache(),backgroundLoad ()以及其它调用了getArticle()方法的函数了。即使是那版backgroundLoad()正在读文章数据,照例还有另外一个线程可以对用户请求做出响应,浏览器因此也不会冻结。现在,你能理解在JavaScript中应用多线程有多实用了?

想了解更多

我向你介绍了一个可以在JavaScript中应用多线程的库:Concurrent.Thread。这篇文章的内容只是很初级的东西,如果你想更深入的了解,我推荐您去看the tutorial。它提供有关Concurrent.Thread用法的更多内容,并列出了可供高级用户使用的文档,是最适合起步的材料。访问他们的网站也不错,那里提供更多信息。

有关作者

Daisuke Maki:从International Christian大学文科学院自然科学分部毕业后(取得文学学士学位),又在Electro-Communications大学的研究生院信息专业攻读硕士学位。擅长Web开发和应用JavaScript的AJAX。他开发了Concurrent.Thread。2006财政年度在日本信息技术促进机构(IPA)指导的项目Explatory Software Project中应用了这个设计。

目前已经拥有一个工学硕士学位的他正在Electro-Communications大学的研究生院注册攻读博士学位。

 

 

直流电机驱动电路设计

转: http://www.dzjs.net/html/zonghejishu/2007/0423/1981_2.html

一、 直流电机驱动电路的设计目标
在直流电机驱动电路的设计中,主要考虑一下几点:

  1. 功能:电机是单向还是双向转动?需不需要调速?对于单向的电机驱动,只要用一个大功率三极管或场效应管或继电器直接带动电机即可,当电机需要双向转动时,可以使用由4个功率元件组成的H桥电路或者使用一个双刀双掷的继电器。如果不需要调速,只要使用继电器即可;但如果需要调速,可以使用三极管,场效应管等开关元件实现PWM(脉冲宽度调制)调速。
  2. 性能:对于PWM调速的电机驱动电路,主要有以下性能指标。
    1)输出电流和电压范围,它决定着电路能驱动多大功率的电机。
    2)效率,高的效率不仅意味着节省电源,也会减少驱动电路的发热。要提高电路的效率,可以从保证功率器件的开关工作状态和防止共态导通(H桥或推挽电路可能出现的一个问题,即两个功率器件同时导通使电源短路)入手。
    3)对控制输入端的影响。功率电路对其输入端应有良好的信号隔离,防止有高电压大电流进入主控电路,这可以用高的输入阻抗或者光电耦合器实现隔离。
    4)对电源的影响。共态导通可以引起电源电压的瞬间下降造成高频电源污染;大的电流可能导致地线电位浮动。
    5)可靠性。电机驱动电路应该尽可能做到,无论加上何种控制信号,何种无源负载,电路都是安全的。

二、 三极管-电阻作栅极驱动

1.输入与电平转换部分:
输入信号线由DATA引入,1脚是地线,其余是信号线。注意1脚对地连接了一个2K欧的电阻。当驱动板与单片机分别供电时,这个电阻可以提供信号电流回流的通路。当驱动板与单片机共用一组电源时,这个电阻可以防止大电流沿着连线流入单片机主板的地线造成干扰。或者说,相当于把驱动板的地线与单片机的地线隔开,实现“一点接地”。
高速运放KF347(也可以用TL084)的作用是比较器,把输入逻辑信号同来自指示灯和一个二极管的2.7V基准电压比较,转换成接近功率电源电压幅度的方波信号。KF347的输入电压范围不能接近负电源电压,否则会出错。因此在运放输入端增加了防止电压范围溢出的二极管。输入端的两个电阻一个用来限流,一个用来在输入悬空时把输入端拉到低电平。
不能用LM339或其他任何开路输出的比较器代替运放,因为开路输出的高电平状态输出阻抗在1千欧以上,压降较大,后面一级的三极管将无法截止。

2.栅极驱动部分:
    后面三极管和电阻,稳压管组成的电路进一步放大信号,驱动场效应管的栅极并利用场效应管本身的栅极电容(大约1000pF)进行延时,防止H桥上下两臂的场效应管同时导通(“共态导通”)造成电源短路。
当运放输出端为低电平(约为1V至2V,不能完全达到零)时,下面的三极管截止,场效应管导通。上面的三极管导通,场效应管截止,输出为高电平。当运放输出端为高电平(约为VCC-(1V至2V),不能完全达到VCC)时,下面的三极管导通,场效应管截止。上面的三极管截止,场效应管导通,输出为低电平。
上面的分析是静态的,下面讨论开关转换的动态过程:三极管导通电阻远小于2千欧,因此三极管由截止转换到导通时场效应管栅极电容上的电荷可以迅速释放,场效应管迅速截止。但是三极管由导通转换到截止时场效应管栅极通过2千欧电阻充电却需要一定的时间。相应的,场效应管由导通转换到截止的速度要比由截止转换到导通的速度快。假如两个三极管的开关动作是同时发生的,这个电路可以让上下两臂的场效应管先断后通,消除共态导通现象。
实际上,运放输出电压变化需要一定的时间,这段时间内运放输出电压处于正负电源电压之间的中间值。这时两个三极管同时导通,场效应管就同时截止了。所以实际的电路比这种理想情况还要安全一些。
场效应管栅极的12V稳压二极管用于防止场效应管栅极过压击穿。一般的场效应管栅极的耐压是18V或20V,直接加上24V电压将会击穿,因此这个稳压二极管不能用普通的二极管代替,但是可以用2千欧的电阻代替,同样能得到12V的分压。

3.场效应管输出部分:
大功率场效应管内部在源极和漏极之间反向并联有二极管,接成H桥使用时,相当于输出端已经并联了消除电压尖峰用的四个二极管,因此这里就没有外接二极管。输出端并联一个小电容(out1和out2之间)对降低电机产生的尖峰电压有一定的好处,但是在使用PWM时有产生尖峰电流的副作用,因此容量不宜过大。在使用小功率电机时这个电容可以略去。如果加这个电容的话,一定要用高耐压的,普通的瓷片电容可能会出现击穿短路的故障。
输出端并联的由电阻和发光二极管,电容组成的电路指示电机的转动方向.

4.性能指标:
电源电压15~30 V,最大持续输出电流5A/每个电机,短时间(10秒)可以达到10A,PWM频率最高可以用到30KHz(一般用1到10KHz)。电路板包含4个逻辑上独立的,输出端两两接成H桥的功率放大单元,可以直接用单片机控制。实现电机的双向转动和调速。

5.布线:
     大电流线路要尽量的短粗,并且尽量避免经过过孔,一定要经过过孔的话要把过孔做大一些(>1mm)并且在焊盘上做一圈小的过孔,在焊接时用焊锡填满,否则可能会烧断。另外,如果使用了稳压管,场效应管源极对电源和地的导线要尽可能的短粗,否则在大电流时,这段导线上的压降可能会经过正偏的稳压管和导通的三极管将其烧毁。在一开始的设计中,NMOS管的源极于地之间曾经接入一个0.15欧的电阻用来检测电流,这个电阻就成了不断烧毁板子的罪魁祸首。当然如果把稳压管换成电阻就不存在这个问题了。
在2004年的Robocon比赛中,我们主要采用了这个电路用以电机驱动。

三、 低压驱动电路的简易栅极驱动
一般功率场效应管的最高栅源电压为20V左右,所以在24V应用中要保证栅源电压不能超过20V,增加了电路的复杂程度。但在12V或更低电压的应用中,电路就可以大大简化。

左图就是一个12V驱动桥的一边,上面电路的三极管部分被两个二极管和两个电阻代替。(注意,跟上图逻辑是反的)由于场效应管栅极电容的存在,通过R3,R4向栅极电容充电使场效应管延缓导通;而通过二极管直接将栅极电容放电使场效应管立即截止,从而避免了共态导通。
这个电路要求在IN端输入的是边缘陡峭的方波脉冲,因此控制信号从单片机或者其他开路输出的设备接入后,要经过施密特触发器(比如555)或者推挽输出的高速比较器才能接到IN端。如果输入边缘过缓,二极管延时电路也就失去了作用。
R3,R4的选取与IN信号边沿升降速度有关,信号边缘越陡峭,R3,R4可以选的越小,开关速度也就可以做的越快。Robocon比赛使用的升压电路(原理相似)中,IN前用的是555。

四、 边沿延时驱动电路
     在前级逻辑电路里,有意地对控制PMOS的下降沿和控制NMOS的上升沿进行延时,再整形成方波,也可以避免场效应管的共态导通。另外,这样做可以使后级的栅极驱动电路简化,可以是低阻推挽驱动栅极,不必考虑栅极电容,可以较好的适应不同的场效应管。2003年Robocon比赛采用的就是这种驱动电路。下图是两种边沿的延时电路:
 
下图是对应的NMOS,PMOS栅极驱动电路:
 
这个栅极驱动电路由两级三极管组成:前级提供驱动场效应管栅极所需的正确电压,后级是一级射极跟随器,降低输出阻抗,消除栅极电容的影响。为了保证不共态导通,输入的边沿要比较陡,上述先延时再整形的电路就可以做到。

五、 其它几种驱动电路
1. 继电器+半导体功率器件的想法
继电器有着电流大,工作稳定的优点,可以大大简化驱动电路的设计。在需要实现调速的电机驱动电路中,也可以充分利用继电器。有一个方案就是利用继电器来控制电流方向来改变电机转向,而用单个的特大电流场效应管(比如IRF3205,一般只有N型特大电流的管子)来实现PWM调速,如下右图所示。这样是实现特别大电流驱动的一个方法。换向的继电器要使用双刀双掷型的,接线如下左图,线圈接线如下中图:
      
2. 几种驱动芯片
1) L298  参考http://www.21icsearch.com/searchpdf/st/L298N.pdf
2) A3952 参考http://www.allegromicro.com/datafile/archive/3952.pdf
3) A3940 参考http://www.allegromicro.com/datafile/3940.pdf
4) L6203 参考http://www.21icsearch.com/searchpdf/st/L6203.pdf

六、 PWM调速的实现
1. 使用定时器的算法
//butcher补充一下吧
//算法原理
//编程实现要点
//优缺点

2. 使用循环移位的算法
产生PWM信号可以由定时器来完成,但是由于51内部只提供了两个定时器,因此如果要向三个或更多的直流电机输出不同占空比的信号要反复设置定时器,实现较为复杂,我们采用一种比较简单的方法不仅可以实现对更多的直流电机提供不同的占空比输入信号,而且只占用一个定时器资源。这种方法可以简单表述如下:
在内存的某段空间内存放各个直流电机所需的输入信号占空比信息,如果占空比为1则保存0FFH(11111111B);占空比为0.5则保存0F0H(11110000B)或任何2进制数中包括4个0和4个1。即
占空比=1的个数/8
具体选取什么样的二进制数要看输出频率的要求。若要对此直流电机输出PWM信号,只要每个时间片移位一次取出其中固定的一位(可以用位寻址或进位标志C实现)送到电机端口上即可。另外,移位算法是一种对以前结果依赖的算法,所以最好定期检查或重置被移位的数,防止移错导致一直错下去。
这种算法的优点是独立进程,可以实现对多个电机的控制,缺点是占用资源较大,PWM频率较低。

3. 模拟电路PWM的实现

上图为一个使用游戏手柄或者航模摇杆上的线性电位器(或线性霍尔元件)控制两个底盘驱动电机的PWM生成电路。J1是手柄的插座,123和456分别是x,y两个方向的电位器。U1B提供半电源电压,U1A是电压跟随。x,y分量经过合成成为控制左右轮两个电机转速的电压信号。在使用中,让L=(x+1)y/(x+1.4),R=(x-1)y/(x-0.6),经过试验有不错的效果(数字只是单位,不是电压值)。经过U1C和U1D组成的施密特振荡器把电压转换为相应的PWM信号,用来控制功率驱动电路。以U1D为例,R1,R2组成有回差的施密特电路,上下门限受输入电压影响,C1和R3组成延时回路,如此形成振荡的脉宽受输入电压控制。Q1,Q2是三极管,组成反相器,提供差分的控制信号。具体振荡过程参见对555振荡器的分析。

七、 步进电机驱动
1. 小功率4相步进电机的驱动
下面是一种驱动电路框图:

达林顿管阵列ULN2803分别从锁存器取出第0,2,4,6位和1,3,5,7位去驱动两个步进电机.四相步进电机的通电顺序可以有几种:A,B,C,D(4相4拍);AB,BC,CD,DA(4相双4拍);A,AB,B,BC,C,CD,D,DA(4相8拍).为了兼顾稳定性,转矩和功耗,一般采用4相8拍方式.所有这些方式都可以通过循环移位实现(也要有定期监控),为了使4相8拍容易实现,锁存器与驱动部分采用了交叉连接.
步进电机工作在四相八拍模式(即正转的输入信号为1000→1100→0100→0110→0010→0011→0001→1001→1000),对应每个步进电机要有四个信号输入端,理论上向端口输出信号可以控制两个步进电机的工作。寄存器循环移位奇偶位分别作两个步进电机的驱动端的做法,其思想如下:
LOOP: MOV A,#1110000B ;在A寄存器中置入11100000
RR A    ;右移位
AJMP LOOP   ;循环右移位
这样在寄存器A中存储的值会有如下循环11100000→01110000→00111000→00011100→00001110→00000111→10000011→11000001→11100000,其奇数位有如下循环1000→1100→0100→0110→0010→0011→0001→1001→1000,其偶数位有如下循环1100→0100→0110→0010→0011→0001→1001→1000→1100.将A输出到P0端口,则奇数位和偶数位正是我们所需要的步进电机输入信号。
而事实上每个电机的动作是不同的,为此我们在RAM中为每个电机开辟一个byte的状态字节用以循环移位.在每一个电机周期里,根据需要对每个电机的byte进行移位,并用ANL指令将两个电机的状态合成到一个字节里输出此时的A同时可以控制两个电机了
步进电机的速度由驱动脉冲的频率决定,移位的周期不同,电机的速度也就不同了.前面提到的电机周期,应该取各种可能的周期的最大公约数.换句话说,一旦电机周期取定,每个电机移位的周期应该是它的倍数.在程序中,对每个电机的相应时刻设定相应的分频比值,同时用一个变量进行加一计数:每到一个电机周期若计数变量<分频比值,则计数变量加1;若相等,则移位,计数变量清零.这样就实现了分频调速,可以让多个电机同时以不同的速度运转.
另外,也可以采用传统的查表方式进行驱动,程序稍长,但也比较稳定,这种方法非常适合三相步进电机。
UCN5804B/LB是Allegro公司生产的4相步进电机驱动专用芯片,它集成了控制逻辑,脉冲分配和功率推动,通过几个管脚的电平来设定转动方式,方向,通过改变外时钟频率来改变转动速度,这给完成复杂的动作和测试步进电机参数带来了极大的方便.

2. 步进电机的智能驱动方案
步进电机有可以精确控制的优点,但是功耗大,效率低,力矩小。如果选用大功率步进电机,为了降低功耗,可以采取PWM恒流控制的方法。基本思路是,用带反馈的高频PWM根据输出功率的要求对每相恒流驱动,总体电流顺序又符合转动顺序。需要力矩小的时候应及时减小电流,以降低功耗。该方案实现的电路,可以采用独立的单片机或CPLD加场效应管驱动电路以及电流采样反馈电路。

八、 附录:几种IRF场效应管的参数

型号
极性
电压(V)
电流(A)
导通电阻(Ohm)
IRF540N
N
100
33
0.040
IRF9540
P
-100
-19
0.200
IRF840
N
500
8
0.850
IRF3205
N
55
110
0.008
IRF530N
N
100
17
0.090
IRF9530
P
-100
-12
0.300

 

 

比较对 NodeList 类型三种循环写法性能

关于这个问题是因为前几天突然看到一种牛逼的循环写法, 当时自以为很牛逼

var alist = document.getElementsByTagName(“a”);

for (var i = 0, item; item = item[i];)  { }

当时不知道是吃了药导致还是怎么了的, 异常兴奋, 居然省略去长度, 那效率肯定会更高.

决定去公司后对比下性能

我很二的在公司开始了测试.

第一种

var alist = document.getElementsByTagName(“a”), temp = [];

for (var i = 0;i < alist.length;i++)  { temp.push( alist[i]); }

第二种, 一直使用的

var alist = document.getElementsByTagName(“a”), temp = [];

for (var i = 0,len = alist.length, item = alist[i];i < len;i++, item = alist[i])  { temp.push( item); }

然后是第三种我当时看着很牛逼的一种写法

var alist = document.getElementsByTagName(“a”), temp = [];

for (var i = 0, item;item = alist[i++];)  { temp.push( item); }

 

测试环境:

循环 10000次

1200个a标签

第一种方法 2652毫秒   2742毫秒    2703毫秒

第二种方法1754毫秒    1809毫秒     1763毫秒

第三种方法1724毫秒    1805毫秒     1817毫秒

 

由此可以发现, 第二种与第三种方法之间的基本又没差异.

可以推测对象的转换不需要消耗多少时间.

第三种方法短小精悍,推荐.

 

写这篇文章的时候,本以为第三种方法会是一种效率极低的方式.

因为看着每次循环时,还需把对象转换.真是奇了怪.

 

理解并解决IE的内存泄漏模式[转]

http://damoqiongqiu.iteye.com/blog/794468

Web开发者的进化
过去,内存溢出并没有给Web开发者造成太大问题。页面之间的关系保持简单,并且在同一站点的不同地址之间进行导航可以快速地清理任何内存泄漏问题。如果存在泄漏,也小到足以忽略。
新的Web应用需要实践更高的标准。一个页面可能运行数小时,而不会被导航或者通过Web服务端动态刷新信息。通过绑定事件配置、面向对象的Jscript和闭包来创建整个应用,语言特性被推到了一个转折点。通过这些以及其它一些变化,此类内存泄漏模式变得更突出,特别是那些先前被浏览器隐蔽的部分。
好消息是,如果你知道想要找什么,内存泄漏模式可以被简单地发现。你面对的大部分的总所周知的麻烦模式都已经有了应对方法,为了你的利益,仅仅需要少量额外工作。一些页面可能仍然因为小的内存泄漏而性能下降,大部分显而易见的页面可以被简单地删除。

泄漏模式
      以下部分将会讨论内存泄漏模式,并且为每种模式指出一些通用的例子。一个极好的模式示例是Jscript的闭包特性,其它一些例子是使用闭包绑定事件。如果你熟悉绑定事件的例子,你就有能力查找并修复大量内存泄漏,但是其它一些与闭包相关的例子可能不那么引人注意。
现在,让我们看看如下模式:
1、循环引用—在IE的COM结构和任何脚本引擎中的对象之间存在交叉引用都会引起内存泄漏。这是最广泛的模式。
2、闭包—闭包是一种特殊的循环引用形式,这是现有Web应用技术中引起泄漏的最大模式。
3、跨页面泄漏—跨页面泄漏通常是,当你从一个站点移动到另一个站点时,由于内部记录对象引起的非常小的泄漏。我们将会检测DOM插入顺序问题,连同一个方法,显示了需要对你的代码进行多么小的变更就可以阻止创建这些记录对象。
4、秀逗模式(假泄漏)—这些不是真的泄漏,但是却非常讨厌,如果你不知道你的内存去了哪里。我们将会检测脚本元素重写操作,当操作必须执行时,它如何表现得像是泄漏了一大块内存。

循环引用
      循环引用几乎是所有泄漏的根源。一般来说,脚本引擎通过它们的垃圾收集器处理循环引用,但是一些未知因素会阻止触发它们恰当地工作。在IE中的未知因素是部分脚本访问的一些DOM元素状态。
这个模式引起泄漏的原因是由于COM引用计数。脚本引擎对象(译者注:指obj1)将会持有一个到DOM元素的引用,在清理并释放DOM元素指针之前,会等待所有外部引用被删除。在我们的例子中,我们在脚本引擎中有两个引用:脚本引擎作用域和DOM元素的自定义属性。当终止时,脚本引擎将会释放第一个引用,而DOM元素引用永远不会被释放,因为它在等待脚本引擎对象(译者注:指obj1)释放它!你可能会想,很容易检测这种情况并修正此问题,但是在实际的应用中这种基本情况只不过是冰山一角。你可能在一个有30个对象链条的尾端获得一个循环引用,这种情况很难检测。
如果你想知道这种模式在HTML中看起来是啥样,你可以通过使用一个全局脚本引擎变量和一个DOM元素构造一次内存泄漏,就像如下展示。

Html代码  收藏代码
  1. <html>
  2.     <head>
  3.         <script language=”JScript”>
  4.         var myGlobalObject;
  5.         function SetupLeak()
  6.         {
  7.             // 第一步设置脚本域到元素的引用
  8.             myGlobalObject = document.getElementById(“LeakedDiv”);
  9.             // 下一步设置元素到脚本域的引用
  10.             document.getElementById(“LeakedDiv”).expandoProperty = myGlobalObject;
  11.         }
  12.         function BreakLeak()
  13.         {
  14.             document.getElementById(“LeakedDiv”).expandoProperty =
  15.                 null;
  16.         }
  17.         </script>
  18.     </head>
  19.     <body onload=”SetupLeak()” onunload=”BreakLeak()”>
  20.         <div id=”LeakedDiv”></div>
  21.     </body>
  22. </html>

为了打破泄漏模式,你可以显式指定一个null值。通过在document卸载之前指定null值,你就是在告诉脚本引擎,在元素和引擎中的对象之间不再存在任何关联。现在它就可以正确地清理引用并释放DOM元素。这种情况下,你作为Web开发者,对你的对象之间的关系所知道的比脚本引擎更多。
这是基本的模式,指出更复杂的情景比较困难。面向对象的Jscript的一个通常的用法是,通过把它们封装到一个Jscript对象中,从而实现对DOM元素的继承。在构造过程中,你一般传入一个你需要的DOM元素,然后在新建的对象上存储一个到DOM元素的引用,同时在DOM元素上存储一个新建对象引用。使用这种方法,你的应用就可以访问任何所需的东西。问题是,这正是典型的循环引用,但是因为使用了不同的语言形式,它可能不会被注意到。打破这类模式可能会变得更加复杂,你可以使用以上讨论的简单方法。

Html代码  收藏代码
  1. <html>
  2.     <head>
  3.         <script language=”JScript”>
  4.         function Encapsulator(element)
  5.         {
  6.             // 设置我们的元素
  7.             this.elementReference = element;
  8.             // 创造我们的循环引用
  9.             element.expandoProperty = this;
  10.         }
  11.         function SetupLeak()
  12.         {
  13.             // 泄漏突然产生
  14.             new Encapsulator(document.getElementById(“LeakedDiv”));
  15.         }
  16.         function BreakLeak()
  17.         {
  18.             document.getElementById(“LeakedDiv”).expandoProperty = null;
  19.         }
  20.         </script>
  21.     </head>
  22.     <body onload=”SetupLeak()” onunload=”BreakLeak()”>
  23.         <div id=”LeakedDiv”></div>
  24.     </body>
  25. </html>

对此问题更复杂的解决方案涉及注册机制,它被用来标注哪些元素/属性需要被解除挂钩、让元素解除与事件的挂钩,这样它可以在document卸载之前被清理掉,但是你经常又会陷入其它泄漏模式而没有真正解决问题。
闭包
      闭包通常需要承担内存泄漏的责任,因为它们会在程序员没有注意到的时候创建循环引用。并不显而易见的是,父函数的参数和局部变量会及时被冻结并持有,直到闭包自身被释放。实际上,这变成了一个常见的编程策略,并且让用户频繁陷入麻烦,对这一点,已经有很多资料可用。因为它们描述了闭包背后的历史细节,连同一些我们需要找出的闭包引起泄漏的特定实例—在把闭包模型应用到我们的循环引用图之后,并且指出了这些额外的引用来自何方。

在普通的循环引用中,有两个固定的对象互相持有对方的引用,但是闭包的情况不同。与直接创建引用不同的是,它们通过从父函数作用域中导入信息而被创建。一般情况下,当调用函数时,一个函数的局部变量和所使用的参数仅仅在函数自身的生命周期中存在。在使用闭包时,这些变量和参数会继续会被外部引用,只要闭包还处于活动状态,既然闭包可以在它们父函数的生命周期之外存活,那么那个函数中的任何变量和参数也可以。在示例中,Parameter 1在函数调用结束之后被正常地释放。因为我们添加了一个闭包,第二个引用被创建,并且第二个引用不会被释放,直到闭包自身被释放掉。如果你恰好把这个闭包绑定到一个事件上,那么你就必须把它从事件上删除。如果你恰好把这个闭包设置到一个自定义属性上,那么你就必须把此自定义属性设置为null。
每次调用都会创建闭包,所以,调用这个函数两次将会创建两个独立的闭包,每个都持用那次所传递进来的参数引用。由于这个显然的特性,闭包非常容易引起泄漏。以下例子提供了使用闭包时引起泄漏的最基本的例子:

 

 

Html代码  收藏代码
  1. <html>
  2.     <head>
  3.         <script language=”JScript”>
  4.         function AttachEvents(element)
  5.         {
  6.             // 此结构导致element引用ClickEventHandler
  7.             element.attachEvent(“onclick”, ClickEventHandler);
  8.             function ClickEventHandler()
  9.             {
  10.        // 闭包引用了element(译者注:由于element是父函数的局部变量,它会被闭包的作用域引用,这种循环非常隐蔽。)
  11.             }
  12.         }
  13.         function SetupLeak()
  14.         {
  15.             // 泄漏瞬间发生
  16.             AttachEvents(document.getElementById(“LeakedDiv”));
  17.         }
  18.         function BreakLeak()
  19.         {
  20.         }
  21.         </script>
  22.     </head>
  23.     <body onload=”SetupLeak()” onunload=”BreakLeak()”>
  24.         <div id=”LeakedDiv”></div>
  25.     </body>
  26. </html>

在一篇基础知识文章中,我们实际上建议你不要使用闭包,除非有必要。在这个例子中,我给出了不要在事件处理函数中使用闭包,作为替代,我们可以把闭包移到全局作用域中去。当闭包变成了一个普通函数之后,它不再从它的父函数继承参数和局部变量,所以我们完全不用担心因为闭包引起的循环引用。在没有必要的地方,大部分代码可以通过不使用闭包的机制被修正。
最后,Eric Lippert,脚本引擎的开发者之一,有一篇极好的关于闭包的文章。他的最终忠告也是只有当真的必要时才使用闭包。他的文章没有提到闭包模式的任何解决方法,希望我们在这里已经为你的入门铺垫了足够多的例子。

跨页面泄漏

跨页面泄漏 
由于插入顺序问题引起的泄漏几乎总是由于创建中间对象而没有被适当清除而引起的。这就是创建动态元素然后把它们插入到DOM中的这种情况。基本的模式是把两个动态创建的对象临时插入到一起,这将创建一个从子元素到父元素的作用域。之后,当你把这两个元素树插入到主树中时,它们都会继承document作用域,然后临时对象就被泄漏了。以下图表展示了使用两种方法把动态创建的元素插入到树中。在第一种模式中,把每个子元素插入到父元素中,最终把整个子树插入到主树中。如果其它条件相同,这种方法会因为临时对象导致泄漏。在第二种模式中,我们从最顶层开始动态创建元素,一直向下遍历所有孩子,将元素插入到主树中。因为每个插入的节点都继承了主document的作用域,我们从未产生过临时对象。这种方法对于避免潜在的内存泄漏更有效。

下一步,我们打算铺垫一个大多泄漏检测算法常用的例子。因为我们没有泄漏任何公开可见的元素,并且我们泄漏的对象非常小,你可能从来没有注意到这个问题。为了例子能工作,动态创建的元素必须包含一个脚本指针,形式为一个内嵌的函数。这将允许我们泄漏一个内部脚本对象,当我们把元素插入到一起时它被临时创建。因为泄漏很小,我们必须运行上千个实例。事实上,泄漏的对象只有几个字节。通过运行示例并导航到一个空页面,你可以查看两个版本之间内存使用量的不同。当我们使用第一个DOM模型把子节点插入到父节点、然后把父节点插入到主树中时,我们的内存使用量上升了一点。这是一个导航泄漏,并且内存不会被回收,直到你重启IE进程。如果你多次运行示例,使用第二种DOM模型,先把父节点插入到主树中,然后再把子节点插入到父节点中,你的内存不会持续攀升,你会发现你已经修正了跨页导航泄漏。

Html代码  收藏代码
  1. <html>
  2.     <head>
  3.         <script language=”JScript”>
  4.         function LeakMemory()
  5.         {
  6.             var hostElement = document.getElementById(“hostElement”);
  7.             //多运行几次,观察任务管理器中的内存反应
  8.             for(i = 0; i < 5000; i++)
  9.             {
  10.                 var parentDiv = document.createElement(“<div onClick=’foo()’>”);
  11.                 var childDiv = document.createElement(“<div onClick=’foo()’>”);
  12.                 // 这将泄漏一个临时对象
  13.            parentDiv.appendChild(childDiv);
  14.                 hostElement.appendChild(parentDiv);
  15.                 hostElement.removeChild(parentDiv);
  16.                 parentDiv.removeChild(childDiv);
  17.                 parentDiv = null;
  18.                 childDiv = null;
  19.             }
  20.             hostElement = null;
  21.         }
  22.         function CleanMemory()
  23.         {
  24.             var hostElement = document.getElementById(“hostElement”);
  25.             //多运行几次,观察任务管理器中的内存反应
  26.             for(i = 0; i < 5000; i++)
  27.             {
  28.                 var parentDiv = document.createElement(“<div onClick=’foo()’>”);
  29.                 var childDiv = document.createElement(“<div onClick=’foo()’>”);
  30.                 //改变次序很重要,这不会泄漏
  31.            hostElement.appendChild(parentDiv);
  32.                 parentDiv.appendChild(childDiv);
  33.                 hostElement.removeChild(parentDiv);
  34.                 parentDiv.removeChild(childDiv);
  35.                 parentDiv = null;
  36.                 childDiv = null;
  37.             }
  38.             hostElement = null;
  39.         }
  40.         </script>
  41.     </head>
  42.     <body>
  43.         <button onclick=”LeakMemory()”>Memory Leaking Insert</button>
  44.         <button onclick=”CleanMemory()”>Clean Insert</button>
  45.         <div id=”hostElement”></div>
  46.     </body>
  47. </html>

这种泄漏需要澄清,因为我们的方法与IE中的最佳方法相反。理解泄漏的关键点是使用脚本创建的元素已经被插入了。事实上,这对于泄漏至关紧要,因为如果我们创建不含有任何脚本的DOM元素,并且我们同样把它们插入到一起,我们并不会引起内存泄漏问题。这就产生了第二个变通方法,对于更大的子树来说可能更好(在示例中我们仅有两个元素,所以在主DOM之外构建树并不会过多影响性能)。第二种方法将会创建你的元素而不在初始化时插入脚本,这样你就可以安全地构建你的子树。在你把你的子树插入到主DOM中之后, 回过头来再为此节点设置脚本事件。记得遵守循环引用和闭包的原则,这样你就不会在你挂钩事件的代码中导致一个不同的泄漏。
我非常想指出这个问题,因为它显示了不是所有的内存泄漏都能轻松地找出来。在一个小的模式变得可观测之前可能需要经历上千次迭代,并且可能是非常小的一件事,就像插入DOM元素的顺序导致问题显现一样。如果你注意使用最佳编程方法,然后你觉得你是安全的,但是这个泄漏的例子显示出即使是最佳方法也会导致泄漏。我们这里的解决方案是改进最佳方法,甚至介绍了一种新的最佳方法,用来去除泄漏的情况。

秀逗模式(假泄漏)
很多时候,某些API的实际表现和期望的表现可能会导致你误判内存泄漏。假泄漏几乎总是出现在同一个页面的动态脚本操作中,并且在从一个页面导航到一个空页面之后变得很少可观察到。这就是你如何消除跨页面泄漏问题的方法,然后开始观察内存用量是否符合预期。我们将会用使用脚本文本重写作为秀逗模式的例子。
和DOM插入顺序问题一样,此问题也依赖创建临时对象而泄漏内存。通过一次又一次地重写一个脚本元素中的脚本文本,慢慢地你开始泄漏大量脚本引擎对象,它们是被插入在原先的内容中的。特别地,与调试脚本相关的对象会被抛弃,就像完全形成的脚本元素。

 

Html代码  收藏代码
  1. <html>
  2.     <head>
  3.         <script language=”JScript”>
  4.         function LeakMemory()
  5.         {
  6.             //多做几次,查看任务管理器中的内存反应
  7.             for(i = 0; i < 5000; i++)
  8.             {
  9.                 hostElement.text = “function foo() { }”;
  10.             }
  11.         }
  12.         </script>
  13.     </head>
  14.     <body>
  15.         <button onclick=”LeakMemory()”>Memory Leaking Insert</button>
  16.         <script id=”hostElement”>function foo() { }</script>
  17.     </body>
  18. </html>

如果你运行以上代码并再次使用任务管理器技巧,当在“泄漏页面”和空白页面进行导航时,你不会注意到脚本泄漏。这种脚本泄漏完全处于一个页面中,并且当你导航出去时你又会拿回你的内存。这个例子之所以有毛病的原因是由于期望的行为。你期望在重写一些脚本之后原先的脚本不会保留下来。但实际上它保留下来了,因为它可能已经用来绑定事件,并且可能存在外部引用计数。像你能看到的一样,这是一种秀逗模式。表面上看,内存使用量看起来非常糟糕,但是却有一个完全正确的理由。

结论
每一个Web开发者都创建了一个个人的代码案例列表,当他们在代码中看到它们的时候,他们知道存在泄漏并且设法绕过这些泄漏。这非常方便,并且也是如今的Web相对无泄漏的原因。按照模式的方法考虑泄漏,替代代码案例,你可以开始发展更好的战略来处理它们。方法是在设计阶段就把它们考虑进来,并且确定你有对付任何潜在泄漏的计划。使用防御式编码技巧,并且假定你必须清理你自己的所有内存。虽然这是对此问题的夸大,你很少必须清理自己的内存;哪些变量和自定义属性存在潜在泄漏就变得显而易见。
对于模式和设计有兴趣者,我强烈推荐Scott’s short blog entry ,这里示范了删除所有基于闭包的内存泄漏的通用示例。它确实需要更多代码,但是行之有效,并且改进的模式更容易在代码中发现并调试。类似的注册表也可以用来解决基于自定义属性的循环引用,只要注意注册方法自身不会出现泄漏的漏洞(特别是那些使用闭包的地方)!

关于作者
      Justin Rogers最近以Object Model开发者的身份加入了IE团队,参与扩展应用。他先前参与一些著名的项目,例如.NET QuickStart Tutorials, .NET Terrarium, 和SQL Server 2005中的 SQL Reporting Services Management Studio。

 

AGPS各运营商服务器IP地址

联通提供:

10.1.101.63:7275

移动提供:

221.176.0.55:7275

 

其他:

suplcn.sirf.com 114.80.208.5:7275
suplcn.sirf.com 114.80.208.5:7276
sls1.sirf.com 66.230.192.56:7275
supl.google.com 74.125.113.192:72756
supl.google.com 74.125.113.192:7276
sls2.sirf.com 84.40.33.25:7276
supl.nokia.com 64.14.59.165:7275

PHP写法细节优化

转,榨干 PHP,不得不转的一篇PHP使用技巧!这篇杂文翻译整理自网络各路文档资料(见最末的参考资料),尤其是 Ilia Alshanetsky (佩服之至) 在多个 PHP 会议上的演讲,主要是各类提高 PHP 性能的技巧。为求精准,很多部分都有详细的效率数据,以及对应的版本等等。偷懒,数据就不一一给出了,直接给结论,如果需要看原文档,请到文末「参考资料」部分。橙色标题为推荐部分。

========================================================

静态调用的成员一定要定义成 static (PHP5 ONLY)

贴士:PHP 5 引入了静态成员的概念,作用和 PHP 4 的函数内部静态变量一致,但前者是作为类的成员来使用。静态变量和 Ruby 的类变量(class variable)差不多,所有类的实例共享同一个静态变量。

QUOTE:
// PHP CODE Highliting for CU by dZ902

bar();

// static way

foo::bar();
?>

静态地调用非 static 成员,效率会比静态地调用 static 成员慢 50-60%。主要是因为前者会产生 E_STRICT 警告,内部也需要做转换。

使用类常量 (PHP5 ONLY)

贴士:PHP 5 新功能,类似于 C++ 的 const。

使用类常量的好处是:

– 编译时解析,没有额外开销
– 杂凑表更小,所以内部查找更快
– 类常量仅存在于特定「命名空间」,所以杂凑名更短
– 代码更干净,使除错更方便

(暂时)不要使用 require/include_once

require/include_once 每次被调用的时候都会打开目标文件!

– 如果用绝对路径的话,PHP 5.2/6.0 不存在这个问题
– 新版的 APC 缓存系统已经解决这个问题

文件 I/O 增加 => 效率降低

如果需要,可以自行检查文件是否已被 require/include。

不要调用毫无意义的函数

有对应的常量的时候,不要使用函数。

QUOTE:
// PHP CODE Highliting for CU by dZ902


虽然使用不多,但是效率提升大概在 3500% 左右。

最快的 Win32 检查

QUOTE:
// PHP CODE Highliting for CU by dZ902

 

– 不用函数
– Win98/NT/2000/XP/Vista/Longhorn/Shorthorn/Whistler…通用
– 一直可用

时间问题 (PHP>5.1.0 ONLY)

你如何在你的软件中得知现在的时间?简单,「time() time() again, you ask me…」。

不过总归会调用函数,慢。

现在好了,用 $_SERVER[‘REQUEST_TIME’],不用调用函数,又省了。

加速 PCRE

– 对于不用保存的结果,不用 (),一律用 (?

这样 PHP 不用为符合的内容分配内存,省。效率提升 15% 左右。

– 能不用正则,就不用正则,在分析的时候仔细阅读手册「字符串函数」部分。有没有你漏掉的好用的函数?

例如:

strpbrk()
strncasecmp()
strpos()/strrpos()/stripos()/strripos()

加速 strtr

如果需要转换的全是单个字符的时候,用字符串而不是数组来做 strtr:

QUOTE:
// PHP CODE Highliting for CU by dZ902

‘e’,
// …
)); // bad
?>

效率提升:10 倍。

不要做无谓的替换

即使没有替换,str_replace 也会为其参数分配内存。很慢!解决办法:

– 用 strpos 先查找(非常快),看是否需要替换,如果需要,再替换

效率:

– 如果需要替换:效率几乎相等,差别在 0.1% 左右。
– 如果不需要替换:用 strpos 快 200%。

邪恶的 @ 操作符

不要滥用 @ 操作符。虽然 @ 看上去很简单,但是实际上后台有很多操作。用 @ 比起不用 @,效率差距:3 倍。

特别不要在循环中使用 @,在 5 次循环的测试中,即使是先用 error_reporting(0) 关掉错误,在循环完成后再打开,都比用 @ 快。

善用 strncmp

当需要对比「前 n 个字符」是否一样的时候,用 strncmp/strncasecmp,而不是 substr/strtolower,更不是 PCRE,更千万别提 ereg。strncmp/strncasecmp 效率最高(虽然高得不多)。

慎用 substr_compare (PHP5 ONLY)

按照上面的道理,substr_compare 应该比先 substr 再比较快咯。答案是否定的,除非:

– 无视大小写的比较
– 比较较大的字符串

不要用常量代替字符串

为什么:

– 需要查询杂凑表两次
– 需要把常量名转换为小写(进行第二次查询的时候)
– 生成 E_NOTICE 警告
– 会建立临时字符串

效率差别:700%。

不要把 count/strlen/sizeof 放到 for 循环的条件语句中

贴士:我的个人做法

QUOTE:
// PHP CODE Highliting for CU by dZ902

for ($i = 0, $max = count($array);$i < $max; ++$i); ?>

效率提升相对于:

– count 50%
– strlen 75%

短的代码不一定快

QUOTE:
// PHP CODE Highliting for CU by dZ902

 

你觉得哪个快?

效率比较:

– longest: 4.27
– longer: 4.43
– short: 4.76

不可思议?再来一个:

QUOTE:
// PHP CODE Highliting for CU by dZ902

read()) !== false) {
if ($entry == ‘.’ || $entry == ‘..’) {
continue;
}
}

// versus
glob(‘./*’);

// versus (include . and ..)
scandir(‘.’);
?>

哪个快?

效率比较:

– original: 3.37
– glob: 6.28
– scandir: 3.42
– original without OO: 3.14
– SPL (PHP5): 3.95

画外音:从此也可以看出来 PHP5 的面向对象效率提高了很多,效率已经和纯函数差得不太多了。

提高 PHP 文件访问效率

需要包含其他 PHP 文件的时候,使用完整路径,或者容易转换的相对路径。

QUOTE:
// PHP CODE Highliting for CU by dZ902

 

物尽其用

PHP 有很多扩展和函数可用,在实现一个功能的之前,应该看看 PHP 是否有了这个功能?是否有更简单的实现?

QUOTE:
// PHP CODE Highliting for CU by dZ902

 

关于引用的技巧

引用可以:

– 简化对复杂结构数据的访问
– 优化内存使用

QUOTE:
// PHP CODE Highliting for CU by dZ902

$a[‘b’][‘c’] = array();

// slow 2 extra hash lookups per access
for ($i = 0; $i < 5; ++$i)
$a[‘b’][‘c’][$i] = $i;

// much faster reference based approach
$ref =& $a[‘b’][‘c’];
for ($i = 0; $i < 5; ++$i) $ref[$i] = $i; ?>

QUOTE:
// PHP CODE Highliting for CU by dZ902

 

==============================================
参考资料

http://ilia.ws

Ilia 的个人网站,Blog,他参与的开发以及出版的一些稿物链接等等。

http://ez.no

eZ components 官方网站,eZ comp 是针对 PHP5 的开源通用库,以效率为己任,Ilia 也参与了开发。

http://phparch.com

php|architect,不错的 php 出版商/培训组织。买不起或者买不到的话,网上可以下到很多经典的盗版。

http://talks.php.net

js写法优化

转自百度文章

1.使用局部变量避免使用全局变量
比如
function test(){
var s = document.getElementById(‘aaa’);
s.innerHTML = document.body.clientHeight;
}
改成
function test(){
var d = document,
s = d.getElementById(‘aaa’);
s.innerHTML = d.body.clientHeight;
}
局部变量的好处就是减少了作用域链的查找
我建议要是有两次的引用就用局部变量

2.避免使用with(这个估计地球人都知道)
我理解原因就是with会创建自己的作用域,这样就加长了原来的作用域链,使得在with块中执行的代码反而变慢了,在书写上好像省了代码,其实在访问上反而变长变繁琐了,性能下降了
例子
使用with
function test(){
with(document.body){
clientHeight = ‘200px’;
clientWidth = ‘200px’
}
}
其实都可以写成
function test(){
var ds = document.body;
ds.clientHeight = ‘200px’;
ds.clientWidth = ‘200px’
}
3. 遍历nodelist的方式
一般的方式都是
var as = document.getElementsByTagName(‘div’);
for(var i=0,l 我的方式一次都不用
for(var i=0,ci;ci=as[i++];){}当nodeList完结时就为false就结束了
好处,没计算长度,省了在循环里赋值,代码更加少了,i++到了判断里
(注意:这个方式用在nodelist里可以,如果你用到array里,可会有问题的,数组里有个0后者null什么的就瞎了)

4.别用那么多个var,一个加逗号就搞定了
var a =1;
var b = 1;
var c =1;
代码长,性能差
拆成
var a=1,
b=1,
c=1;

5.innerHTML是最好的选择
往元素添加元素时,最好用innerHTML

6.ie的removeChild不好用
一般咱们删除一个元素会用
elm.removeChild(subElm)
这个在ie下不好用,因为在ie下这个只是把这个元素从dom树中断开了,但并没用真正删除,它现在变成了孤立的节点了,
要想真正删除,可以这样
var ryc = document.createElement(‘div’);
div.appendChild(subElm);
div.innerHTML = ”;
div = null;
这样就真的删除了,除了ie外别的都可以用removeChild达到效果

7.为多个同级元素绑定事件时,不用为每个都绑定,为他们的父级绑定就行了
比如

  • sdf
  • sdf
  • sdf
  • sdf
  • sdf
  • sdf

可能你要为每个li添加click
为每个添加那可繁琐且容易出现溢出(ie)
其实只要为 ul一个添加就行了,因为事件是冒泡向上的
var ul = document.getElementById(‘a’);
ul.onclick = function (e){
!e&&(e=event);
var target = e.srcElement||e.target;
if(target.tagName==’LI’){
//your code
}
}

8.尽量用原生的方法,因为原生的都是用c/c++编译而成的他们执行的要比用js写的方法快多了

9.appendChild用的多时一定要用docuemntfragment
比如
for(var i=0;i var o = document.createElement(‘div’);
document.body.appendChild(o);
}
用documentFragment
var f = document.createDocumentFragment();
for(var i=0;i var o = document.createElement(‘div’);
f.appendChild(o);
}
document.body.appendChild(f);

10. if else用的>=3个了,那用switch吧,好阅读,性能好

11. if

12. if==1,if改&&
if(a==1)a=2

a==1&&(a=2);

13.计算元素位置,while()offsetParent
这个方式是老方式了,现在的浏览器ie6以上,ff3.1以上,chrome,opera(我只测了最新的)都支持这个
el.getBoundingClientRect返回一个对像,分别是top,left,right,bottom的值

14.正则的查找没有indexOf快
var s= ‘sdfsdfsdfAAAsdfdsfs’;
for(var i=0;i s.indexOf(‘AAA’)
}
比这个快
var s= ‘sdfsdfsdfAAAsdfdsfs’;
for(var i=0;i /AAA/.test(s)
}

15.在正则中多用非捕获(?:)这样快

16.设置某个元素的style时用cssText简单些
el.style.cssText +=”;postion:absolute;”
(注意:position前;不能去了,因为ie没有这个;position认不出来了就,比的浏览器没这个毛病)

17.在new 时,没有参数时函数名后边的括号可以去了
new fn()==>new fn
new Image()==>new Image

首先,与其他语言不同,JS的效率很大程度是取决于JS engine的效率。除了引擎实现的优劣外,引擎自己也会为一些特殊的代码模式采取一些优化的策略。例如FF、Opera和Safari的JS引擎,都对 字符串的拼接运算(+)做了特别优化。显然,要获得最大效率,就必须要了解引擎的脾气,尽量迎合引擎的口味。所以对于不同的引擎,所作的优化极有可能是背 道而驰的。

而如果做跨浏览器的web编程,则最大的问题是在于IE6(JScript 5.6)!因为在不打hotfix的情况下,JScript引擎的垃圾回收的bug,会导致其在真实应用中的performance跟其他浏览器根本不在一个数量级上。因此在这种场合做优化,实际上就是为JScript做优化!

所以第一原则就是只需要为IE6(未打补丁的JScript 5.6或更早版本)做优化!

如果你的程序已经优化到在IE6下可以接受的性能,那基本上在其他浏览器上性能就完全没有问题。

因此,注意我下面讲的许多问题在其他引擎上可能完全不同,例如在循环中进行字符串拼接,通常认为需要用Array.join的方式,但是由于 SpiderMonkey等引擎对字符串的“+”运算做了优化,结果使用Array.join的效率反而不如直接用“+”!但是如果考虑IE6,则其他浏 览器上的这种效率的差别根本不值一提。

JS优化与其他语言的优化也仍然有相同之处。比如说,不要一上来就急吼吼的做优化,那样毫无意义。优化的关键,仍然是要把精力放在最关键的地方, 也就是瓶颈上。一般来说,瓶颈总是出现在大规模循环的地方。这倒不是说循环本身有性能问题,而是循环会迅速放大可能存在的性能问题。

所以第二原则就是以大规模循环体为最主要优化对象。

以下的优化原则,只在大规模循环中才有意义,在循环体之外做此类优化基本上是没有意义的。

目前绝大多数JS引擎都是解释执行的,而解释执行的情况下,在所有操作中,函数调用的效率是较低的。此外,过深的prototype继承链或者多 级引用也会降低效率。JScript中,10级引用的开销大体是一次空函数调用开销的1/2。这两者的开销都远远大于简单操作(如四则运算)。

所以第三原则就是尽量避免过多的引用层级和不必要的多次方法调用。

特别要注意的是,有些情况下看似是属性访问,实际上是方法调用。例如所有DOM的属性,实际上都是方法。在遍历一个NodeList的时候,循环 条件对于nodes.length的访问,看似属性读取,实际上是等价于函数调用的。而且IE DOM的实现上,childNodes.length每次是要通过内部遍历重新计数的。(My god,但是这是真的!因为我测过,childNodes.length的访问时间与childNodes.length的值成正比!)这非常耗费。所以 预先把nodes.length保存到js变量,当然可以提高遍历的性能。

同样是函数调用,用户自定义函数的效率又远远低于语言内建函数,因为后者是对引擎本地方法的包装,而引擎通常是c,c++,java写的。进一步,同样的功能,语言内建构造的开销通常又比内建函数调用要效率高,因为前者在JS代码的parse阶段就可以确定和优化。

所以第四原则就是尽量使用语言本身的构造和内建函数。

这里有一个例子是高性能的String.format方法。 String.format传统的实现方式是用String.replace(regex, func),在pattern包含n个占位符(包括重复的)时,自定义函数func就被调用n次。而这个高性能实现中,每次format调用所作的只是一 次Array.join然后一次String.replace(regex, string)的操作,两者都是引擎内建方法,而不会有任何自定义函数调用。两次内建方法调用和n次的自定义方法调用,这就是性能上的差别。

同样是内建特性,性能上也还是有差别的。例如在JScript中对于arguments的访问性能就很差,几乎赶上一次函数调用了。因此如果一个 可变参数的简单函数成为性能瓶颈的时候,可以将其内部做一些改变,不要访问arguments,而是通过对参数的显式判断来处理。

比如:
Java代码
1. function sum() {
2. var r = 0;
3. for (var i = 0; i < arguments.length; i++) {
4. r += arguments[i];
5. }
6. return r;
7. }

这个sum通常调用的时候个数是较少的,我们希望改进它在参数较少时的性能。如果改成:
Java代码
1. function sum() {
2. switch (arguments.length) {
3. case 1: return arguments[0];
4. case 2: return arguments[0] + arguments[1];
5. case 3: return arguments[0] + arguments[1] + arguments[2];
6. case 4: return arguments[0] + arguments[1] + arguments[2] + arguments[3];
7. default:
8. var r = 0;
9. for (var i = 0; i < arguments.length; i++) {
10. r += arguments[i];
11. }
12. return r;
13. }
14. }

其实并不会有多少提高,但是如果改成:
Java代码
1. function sum(a, b, c, d, e, f, g) {
2. var r = a ? b ? c ? d ? e ? f ? a + b + c + d + e + f : a + b + c + d + e : a + b + c + d : a + b + c : a + b : a : 0;
3. if (g === undefined) return r;
4. for (var i = 6; i < arguments.length; i++) {
5. r += arguments[i];
6. }
7. return r;
8. }

就会提高很多(至少快1倍)。

最后是第五原则,也往往是真实应用中最重要的性能障碍,那就是尽量减少不必要的对象创建。

本身创建对象是有一定的代价的,但是这个代价其实并不大。最根本的问题是由于JScript愚蠢之极的垃圾回收调度算法,导致随着对象个数的增加,性能严重下降(据微软的人自己说复杂度是O(n^2))。

比如我们常见的字符串拼接问题,经过我的测试验证,单纯的多次创建字符串对象其实根本不是性能差的原因。要命的是在对象创建期间的无谓的垃圾回收的开销。而Array.join的方式,不会创建中间字符串对象,因此就减少了那该死的垃圾回收的开销。

因此,如果我们能把大规模对象创建转化为单一语句,则其性能会得到极大的提高!例如通过构造代码然后eval——实际上PIES项目中正在根据这个想法来做一个专门的大规模对象产生器……

好了上面就是偶总结的JS优化五大原则。

一、正则表达式的创建代码
---
这样的创建代码实在冗余:
var fnRE = /functor_[0-9A-Za-z]{8}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{12}/i;
var objRE = /object_[0-9A-Za-z]{8}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{12}$/i;
var objRE_r = /radio_[0-9A-Za-z]{8}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{12}_r/i;
var objRE_a = /object_[0-9A-Za-z]{8}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{12}_a/i;
var objRE_m = /radio_[0-9A-Za-z]{8}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{12}_m/i;
var objRE_d = /radio_[0-9A-Za-z]{8}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{12}_d/i;

仔细读来,其实就是一个添加前后缀的GUID。那么可否写成如下:
var GUID = ‘([0-9A-Za-z]{8}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{12})’;
var fnRE = new RegExp(‘(functor_)’+ GUID, ‘i’);
var objRE = new RegExp(‘(object_)’ + GUID + ‘$’, ‘i’);
var objRE_r = new RegExp(‘(radio_)’ + GUID + ‘_(r)’, ‘i’);
var objRE_a = new RegExp(‘(object_)’ + GUID + ‘_(a)’, ‘i’);
var objRE_m = new RegExp(‘(radio_)’ + GUID + ‘_(m)’, ‘i’);
var objRE_d = new RegExp(‘(radio_)’ + GUID + ‘_(d)’, ‘i’);

这里看起来是用了字符串连接运算,但由于变量声明只运算一次,因此对效率没什么影响。而且可读性强了,修改起来也方便。
读注意这里用到了正则表达式中的分组'( )’,这在后面是会很有用的。
二、正则使用中的分组
---
代码总是通过
aryAList[_match[0].split(“_”)[1]] = “a_”;
这样的形式来从匹配中分离GUID,但如果使用上面的分组,那么这项运算就不必要了。简单的使用
aryAList[_match[2]] = “a_”;
就可以得到结果。
三、应注意DOM引用的耗时
---
代码中,在循环内不断地访问DOM对象的成员,然而DOM对象的成员存取是耗时的。更细的说,每一个成员
都会通过COM组件封装,因此效率是差的。所以下面的代码:
else if ((_match = _obj.name.match(objRE_m)) != null) {
}
else if ((_match = _obj.name.match(objRE_d)) != null) {
}
应当被改作:
var name = _obj.name;
else if ((_match = name.match(objRE_m)) != null) {
}
else if ((_match = name.match(objRE_d)) != null) {
}
四、过于复杂的逻辑
---
代码过于依赖其它语言的编程经验,而忽略了JavaScript的自身特性。因此实现的逻辑中规中矩,但是难以
扩展,而且复杂。例如循环中的大量if..else if …连用。后文单独给出这部分的优化。
五、从StringBuilder()接口来看,优化程度不够
---
文章提到StringBuilder是一个字符串构建的高效对象。我留意到它的使用是:
objectListEx.append(_id + “:” + _r + “:” + _a + “:” + _m + “:” + _d + “;”);
那么可以说这个对象的优化是不够的。因为这里传入一个字符串参数,而传入参数又用字符串连接运算,
效率提升很有限。
如果StringBuilder是用array.join来实现字符串拼接的话,那么更加良好的方式是允许在append中使用多
个参数。例如:
objectListEx.append = function() {
this.push.apply(this, arguments);
}
objectListEx.toString = function() {
return this.join(”);
}
那么,上例的添加就可以写成:
objectListEx.append(_id , “:” , _r , “:” , _a , “:” , _m , “:” , _d , “;”);
这就避免了多余的字符串连接运算。
六、新的优化后版本
---
// optimized version
var functorListEx = new StringBuilder();
var objectListEx = new StringBuilder();
var coll = document.getElementsByTagName(“INPUT”);

// regular expression for matching
var GUID = ‘([0-9A-Za-z]{8}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{12})’;
var fnRE = new RegExp(‘(functor_)’+ GUID, ‘i’);
var objRE = new RegExp(‘(object_)’ + GUID + ‘$’, ‘i’);
var objRE_r = new RegExp(‘(radio_)’ + GUID + ‘_(r)’, ‘i’);
var objRE_a = new RegExp(‘(object_)’ + GUID + ‘_(a)’, ‘i’);
var objRE_m = new RegExp(‘(radio_)’ + GUID + ‘_(m)’, ‘i’);
var objRE_d = new RegExp(‘(radio_)’ + GUID + ‘_(d)’, ‘i’);
// helper data structures used by optimized algorithm
var aryObjList = new Array();
var aryRList = new Array();
var aryAList = new Array();
var aryMList = new Array();
var aryDList = new Array();
var aryList = {
r: aryRList,
a: aryAList,
m: aryMList,
d: aryDList
}
// one pass scan to traverse the nodes collection (coll) to build functorListEx
// and intermediate arrays
for (var i=0,imax=coll.length; i var _obj = coll[i];
if (!_obj.checked) continue; // if (_obj.type != “checkbox” && _obj.type != “radio”) continue;
var id = _obj.id, name = _obj.name;
var _match = id.match(fnRE) || name.match(objRE_r) || id.match(objRE_a) ||
name.match(objRE_m) || name.match(objRE_d) || id.match(objRE);
if (!_match) continue;
var tag = _match[3], tag2 = tag+’_’, guid = _match[2];
switch (tag) {
‘a’: aryList[tag][guid] = tag2; break;
‘r’, ‘m’, ‘d’:
aryList[tag][guid] = tag2 + _obj.value; break;
default :
if (_match[1]==’functor_’) {
functorListEx.append(guid, “;”)
}
else { // for _match[1]==’object_’
aryObjList.push(guid)
}
}
}
// further process to build objectListEx from the intermediate arrays
for (var i=0, imax=aryObjList.length; i var id = aryObjList[i];
var r = aryRList[id] || “”;
var a = aryAList[id] || “”;
var m = aryMList[id] || “”;
var d = aryDList[id] || “”;
objectListEx.append(id , “:” , r , “:” , a , “:” , m , “:” , d , “;”);
}
七、又一处小的优化
---
刚才想了想,其实上面代码中的switch还是啰嗦了。下面做一下下小的优化:
switch (_match[1] + tag) {
‘functor_undefined’: functorListEx.append(guid, “;”); break;

‘object_undefined’: aryObjList.push(guid); break;

‘object_a’: aryList[tag][guid] = tag2 ; break;

default: // for r,m,d
aryList[tag][guid] = tag2 + _obj.value;