<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>搜索研发部官方博客</title>
	<atom:link href="http://stblog.baidu-tech.com/?feed=rss2" rel="self" type="application/rss+xml" />
	<link>http://stblog.baidu-tech.com</link>
	<description>又一个 WordPress 博客</description>
	<lastBuildDate>Tue, 14 Feb 2012 07:54:49 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>流量低峰也烦人-lighttpd耗时长问题追查</title>
		<link>http://stblog.baidu-tech.com/?p=1444</link>
		<comments>http://stblog.baidu-tech.com/?p=1444#comments</comments>
		<pubDate>Tue, 07 Feb 2012 10:37:26 +0000</pubDate>
		<dc:creator>editor</dc:creator>
				<category><![CDATA[前端技术]]></category>
		<category><![CDATA[Lighttpd]]></category>
		<category><![CDATA[Web服务器]]></category>

		<guid isPermaLink="false">http://stblog.baidu-tech.com/?p=1444</guid>
		<description><![CDATA[结论
　　如果你用lighttpd1.5(以下lighttpd均指1.5)做静态文件服务器，或者你虽然用lighttpd处理php请求，但是用到$PHYSICAL作为mod_proxy_core的条件， 且某个时候你的单机流量很低(几个/s), 或许你也有类似的问题，但是影响程度或许不会引起你的注意！
1.Lighttpd的mod_proxy_core不建议用$PHYSICAL作为条件；
2.Lighttpd的stat cache机制没有节省任何开销；
3.Lighttpd子线程和主线程通过管道+epoll的通信机制，存在event丢失问题；

现象
用户反馈凌晨的时候访问百度某页面，某些模块的数据出不来；
其它依赖于我们的前端接口的产品线反馈访问时间有时候超过1s；
我们自己的QA环境偶尔也会出现请求超过1s的问题；
　　因此我们打开lighttpd的日志的%D配置，打印ms级别的处理时间，发现晚上1点到凌晨8点有很多处理时间超过1s的请求，500ms以上的也有很多，并且流量越低， 比例越大；
1点-8点是流量低峰时期，流量越低，性能越差？
这个现象每到高峰时期就正常了，因为是流量低峰才会出现这样的慢请求，占总比例非常之少，对整体的性能和稳定性影响极小，所以性能和稳定性监控报表中没有发现这个问题。
追查过程
由于这个页面对性能要求相对比较严格，虽然性能和稳定性衡量数据已经非常好， 但是这个问题一直是一个阴影，不解决终归不爽，所以开始了下面的追查过程：
由于处理路径是lighttpd-&#62;php-cgi-&#62;框架+逻辑, 首先的怀疑是框架+逻辑问题，但是通过查看php的处理时间，流量低峰高峰都非常正常，极少超过100ms，所以排除是框架+逻辑问题；那究竟是php-cgi的问题还是lighttpd本身的问题呢？为了排除php-cgi的问题，我们尝试了从线下复现这个问题，看访问静态文件是否也有类似的问题。但是悲剧的是线下就是复现不了这个问题。那再对比和线上环境的不同，会不会是先要经过一段时间的大流量，然后再小流量才会出现这个问题呢？于是用ab 30qps压了2个小时后停止，然后再手动访问试了一下，果然如此！通过访问静态文件，发现静态文件也是如此，处理时间超过1s, 因此基本排除php-cgi本身的原因，问题应该是出在lighttpd本身；
通过这个线下实验，我们还发现了如下规律：
1.前期用ab压的时间不定，有时候压2个小时后然后低流量访问还不会出现这个问题，有一定的随机性；
2.手动低流量访问的时候，并不是每次都慢，对同一个url, 紧接着的两次访问(访问第一次后马上访问第二次)，第一次会慢，但第二次会很快，然后再过个1-2s钟再访问第三次，又会很慢；
3.手动请求的时候，如果慢，总是慢1s, 但是线上有慢1s的，也有不是慢1s的，最多1s;
4.重启lighttpd后，所有请求会恢复正常，需要重新压；
于是产生两个最大的疑问， lighttpd在公司使用这么广，处理静态资源和php请求的都有用到， 别的产品线为什么不报？ 为什么是1s？ 对于第一个疑问，觉得可能是因为这种情况影响的平均性能非常少，可能其他产品线不会这么敏感，或者是流量低峰的单机请求量也很高，没有频繁的触发这个问题，这个时候还对比了其它产品线的lighttpd.conf, 这个时候是没有发现有什么问题的。于是就从第二个问题开始着手追查：为什么是1s?
带着问题，开始读lighttpd的源代码了。。。
该页面lighttpd event-handler用的是linux-sysepoll；
首先发现lighttpd代码中有各个地方和1s有关的代码：
源文件server.c

第一个1000ms是lighttpd的epoll的超时时间，也就是如果没有任何句柄有事件发生，epoll最多等待1000ms后即会返回，如果有事件发生，epoll会马上返回有事件发生的所有句柄，然后lighttpd会处理joblist中已经准备好的connection，重新进入状态机；第二个1s是lighttpd有个一个trigger机制，每隔1s会触发一次SIGALRM, 然后lighttpd处理超时的请求，清理stat_cache等；因此，通过修改这两个1s, 发现当改成fdevent_poll(srv-&#62;ev, 500);  后，慢请求都变成500了, 所以慢请求是因为的那个connection已经放在joblist, 但是没有成功触发epoll返回， epoll只有等待超时后返回，该connection才会被处理，这也就解释了为什么流量高峰的时候没有这个问题，因为高峰的时候epoll返回得相当频繁，也可以解释为什么线上的慢请求慢100,200ms的都有，但是最多不超过1s了，线下手工访问的时候总是慢1s, 这也是因为每秒的请求量的原因， 这其实也是类似epoll这种异步事件处理模型所带来的通病，用延迟换吞吐量；
问题进一步，那为什么重启lighttpd后，就算流量低也没问题呢，所以进一步看代码，通过把lighttpd所有debug日志打开，发现这个问题和lighttpd­的stat cache机制有关， 为了避免反复的调用stat来获取文件信息，lighttpd用了一个全局的hash表保存了每个物理路径的所对应文件的stat结果，这个机制和server. stat-cache-engine这个配置有关，我们用的默认配置“simple”, cache结果会缓存1s, 如果没有命中或者失效，lighttpd会把这个stat任务放在一个队列里面， 然后告诉状态机HANDLER_WAIT_FOR_EVENT， 暂时退出状态机，由另外几个线程来异步处理这个stat任务，处理完这个任务后，会重新把这个任务关联的connection加到joblist_queue中，然后通过管道通知主线程，让epoll返回， 相关代码如下：
源文件joblist.c

上面的代码可以看出, lighttpd是通过判断一个全局的变量srv-&#62;did_wakeup，如果是0，就把它改成1，然后往这个管道发生一个空格字符串，触发主线程的epoll返回，如果这个变量不是0，就不会通知主进程。
下面的代码是主线程epoll返回后，和这个管道句柄对于的处理函数，可以看出主线程又把srv-&#62;did_wakeup初始化成0了， 这样下次还会wakeup主线程；
源文件server.c

这就引发一个思考，如果因为某种原因srv-&#62;did_wakeup被修改成1了，但是主线程由于某种原因没有收到这个write事件，导致srv-&#62;did_wakeup没有被改成0，那不是后面都不会通过管道通知主线程了，为了证明这个假设，我加了下trace代码，发现确实是这样的，设置srv-&#62;did_wakeup =1 做了2456次，但是设置srv-&#62;did_wakeup = 0只做了2455次，只差一次，并且后续都没有做这个操作了，另外还发现子线程每次write管道都是成功的，但是最后一次主线程没有收到这个事件，至于为什么没有收到，就没有继续查了。
但是，还是有个疑问，我访问的php请求，lighttpd应该把请求路径发给php-fpm,自己应该不关心物理路径的啊，就不用搞什么stat cache吧，这个时候想起了当时为了解决某扩展能够正确获取到PATH_INFO的问题，把mod_proxy_core的条件配置从$HTTP["url"] =~ &#8220;\.php$&#8221;改成了$PHYSICAL["existing-path"] =~ &#8220;\.php$&#8221;。马上修改配置，再测试，问题果然没有了， 通过查看lighttpd代码，发现如果配置成$PHYSICAL这种形式，会导致lighttpd去stat这个物理文件，这个操作在mod_proxy_core之前执行，如果用$HTTP[“url”]就不会引发这个问题，到此，一切都清楚了，我看到的其它老的产品线都是配的$HTTP[“url”], 只有少数的几个产品线不是用的$HTTP[“url”]，也只是单机流量非常低的情况才会出现这个问题，很难会让人觉察到!
另外，在追查问题的过程中还发现lighttpd stat_cache机制的两个问题，第一个问题就是处理stat任务的子线程，在stat之后，并没有更新这个stat cache的状态为FINISHED, 下次来查的时候还是没有命中cache, 等于是白干了。如下代码所示：
源文件stat_cache.c

第二个问题是就是命中了stat cache, 其实还是需要调用stat判断改cache有没有过期， 所以觉得stat cache本身这个机制也是白搞了，比较没有节省stat的开销，还多搞了，如下代码所示：
源文件stat_cache.c

遗留问题
子线程和主线程通过管道+epoll的机制来通信，为什么会有一定的概率失败呢？write管道其实是成功的，由于精力有限，这个问题没有继续追查；
管道其实是成功的，由于精力有限，这个问题没有继续追查；
对lighttpd配置的建议
如果利用mod_proxy_core做php处理，还是用$HTTP[“url”]做条件吧，例如：
$HTTP["url"] [...]]]></description>
			<content:encoded><![CDATA[<h2>结论</h2>
<p>　　如果你用lighttpd1.5(以下lighttpd均指1.5)做静态文件服务器，或者你虽然用lighttpd处理php请求，但是用到$PHYSICAL作为mod_proxy_core的条件， 且某个时候你的单机流量很低(几个/s), 或许你也有类似的问题，但是影响程度或许不会引起你的注意！</p>
<p>1.Lighttpd的mod_proxy_core不建议用$PHYSICAL作为条件；<br />
2.Lighttpd的stat cache机制没有节省任何开销；<br />
3.Lighttpd子线程和主线程通过管道+epoll的通信机制，存在event丢失问题；</p>
<p><span id="more-1444"></span></p>
<h2>现象</h2>
<p>用户反馈凌晨的时候访问百度某页面，某些模块的数据出不来；</p>
<p>其它依赖于我们的前端接口的产品线反馈访问时间有时候超过1s；</p>
<p>我们自己的QA环境偶尔也会出现请求超过1s的问题；</p>
<p>　　因此我们打开lighttpd的日志的%D配置，打印ms级别的处理时间，发现晚上1点到凌晨8点有很多处理时间超过1s的请求，500ms以上的也有很多，并且流量越低， 比例越大；</p>
<p>1点-8点是流量低峰时期，<strong>流量越低，性能越差</strong>？</p>
<p>这个现象每到高峰时期就正常了，因为是流量低峰才会出现这样的慢请求，占总比例非常之少，对整体的性能和稳定性影响极小，所以性能和稳定性监控报表中没有发现这个问题。</p>
<h2>追查过程</h2>
<p>由于这个页面对性能要求相对比较严格，虽然性能和稳定性衡量数据已经非常好， 但是这个问题一直是一个阴影，不解决终归不爽，所以开始了下面的追查过程：</p>
<p>由于处理路径是lighttpd-&gt;php-cgi-&gt;框架+逻辑, 首先的怀疑是框架+逻辑问题，但是通过查看php的处理时间，流量低峰高峰都非常正常，极少超过100ms，所以排除是框架+逻辑问题；那究竟是php-cgi的问题还是lighttpd本身的问题呢？为了排除php-cgi的问题，我们尝试了从线下复现这个问题，看访问静态文件是否也有类似的问题。但是悲剧的是线下就是复现不了这个问题。那再对比和线上环境的不同，会不会是先要经过一段时间的大流量，然后再小流量才会出现这个问题呢？于是用ab 30qps压了2个小时后停止，然后再手动访问试了一下，果然如此！通过访问静态文件，发现静态文件也是如此，处理时间超过1s, 因此基本排除php-cgi本身的原因，问题应该是<strong>出在lighttpd本身</strong>；</p>
<p>通过这个线下实验，我们还发现了如下规律：</p>
<p style="padding-left: 30px;">1.前期用ab压的时间不定，有时候压2个小时后然后低流量访问还不会出现这个问题，有一定的随机性；</p>
<p style="padding-left: 30px;">2.手动低流量访问的时候，并不是每次都慢，对同一个url, 紧接着的两次访问(访问第一次后马上访问第二次)，第一次会慢，但第二次会很快，然后再过个1-2s钟再访问第三次，又会很慢；</p>
<p style="padding-left: 30px;">3.手动请求的时候，如果慢，总是慢1s, 但是线上有慢1s的，也有不是慢1s的，最多1s;</p>
<p style="padding-left: 30px;">4.重启lighttpd后，所有请求会恢复正常，需要重新压；</p>
<p>于是产生两个最大的疑问， lighttpd在公司使用这么广，处理静态资源和php请求的都有用到， 别的产品线为什么不报？ 为什么是1s？ 对于第一个疑问，觉得可能是因为这种情况影响的平均性能非常少，可能其他产品线不会这么敏感，或者是流量低峰的单机请求量也很高，没有频繁的触发这个问题，这个时候还对比了其它产品线的lighttpd.conf, 这个时候是没有发现有什么问题的。于是就从第二个问题开始着手追查：为什么是1s?</p>
<p>带着问题，开始读lighttpd的源代码了。。。</p>
<p>该页面lighttpd event-handler用的是linux-sysepoll；</p>
<p>首先发现lighttpd代码中有各个地方和1s有关的代码：</p>
<p><em>源文件server.c</em></p>
<p><em><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11328610415.jpg&amp;type=image%2Fjpeg&amp;width=380&amp;height=21"><img class="aligncenter size-full wp-image-1446" title="1" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11328610415.jpg&amp;type=image%2Fjpeg&amp;width=380&amp;height=21" alt="" width="380" height="21" /></a><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=21328610451.jpg&amp;type=image%2Fjpeg&amp;width=363&amp;height=140"><img class="aligncenter size-full wp-image-1447" title="2" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=21328610451.jpg&amp;type=image%2Fjpeg&amp;width=363&amp;height=140" alt="" width="363" height="140" /></a></em></p>
<p>第一个1000ms是lighttpd的epoll的超时时间，也就是如果没有任何句柄有事件发生，epoll最多等待1000ms后即会返回，如果有事件发生，epoll会马上返回有事件发生的所有句柄，然后lighttpd会处理joblist中已经准备好的connection，重新进入状态机；第二个1s是lighttpd有个一个trigger机制，每隔1s会触发一次SIGALRM, 然后lighttpd处理超时的请求，清理stat_cache等；因此，通过修改这两个1s, 发现当改成fdevent_poll(srv-&gt;ev, 500);  后，慢请求都变成500了, 所以慢请求是因为的那个connection已经放在joblist, 但是没有成功触发epoll返回， epoll只有等待超时后返回，该connection才会被处理，这也就解释了为什么流量高峰的时候没有这个问题，因为高峰的时候epoll返回得相当频繁，也可以解释为什么线上的慢请求慢100,200ms的都有，但是最多不超过1s了，线下手工访问的时候总是慢1s, 这也是因为每秒的请求量的原因， 这其实也是类似epoll这种异步事件处理模型所带来的通病，用延迟换吞吐量；</p>
<p>问题进一步，那为什么重启lighttpd后，就算流量低也没问题呢，所以进一步看代码，通过把lighttpd所有debug日志打开，发现这个问题和lighttpd­的stat cache机制有关， 为了避免反复的调用stat来获取文件信息，lighttpd用了一个全局的hash表保存了每个物理路径的所对应文件的stat结果，这个机制和server. stat-cache-engine这个配置有关，我们用的默认配置“simple”, cache结果会缓存1s, 如果没有命中或者失效，lighttpd会把这个stat任务放在一个队列里面， 然后告诉状态机HANDLER_WAIT_FOR_EVENT， 暂时退出状态机，由另外几个线程来异步处理这个stat任务，处理完这个任务后，会重新把这个任务关联的connection加到joblist_queue中，然后通过管道通知主线程，让epoll返回， 相关代码如下：</p>
<p><em>源文件joblist.c</em></p>
<p><em><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=31328610494.jpg&amp;type=image%2Fjpeg&amp;width=558&amp;height=69"><img class="aligncenter size-full wp-image-1448" title="3" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=31328610494.jpg&amp;type=image%2Fjpeg&amp;width=558&amp;height=69" alt="" width="558" height="69" /></a></em></p>
<p>上面的代码可以看出, lighttpd是通过判断一个全局的变量srv-&gt;did_wakeup，如果是0，就把它改成1，然后往这个管道发生一个空格字符串，触发主线程的epoll返回，如果这个变量不是0，就不会通知主进程。</p>
<p>下面的代码是主线程epoll返回后，和这个管道句柄对于的处理函数，可以看出主线程又把srv-&gt;did_wakeup初始化成0了， 这样下次还会wakeup主线程；</p>
<p><em>源文件server.c</em></p>
<p><em><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=41328610532.jpg&amp;type=image%2Fjpeg&amp;width=558&amp;height=212"><img class="aligncenter size-full wp-image-1449" title="4" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=41328610532.jpg&amp;type=image%2Fjpeg&amp;width=558&amp;height=212" alt="" width="558" height="212" /></a></em></p>
<p>这就引发一个思考，如果因为某种原因srv-&gt;did_wakeup被修改成1了，但是主线程由于某种原因没有收到这个write事件，导致srv-&gt;did_wakeup没有被改成0，那不是后面都不会通过管道通知主线程了，为了证明这个假设，我加了下trace代码，发现确实是这样的，设置srv-&gt;did_wakeup =1 做了2456次，但是设置srv-&gt;did_wakeup = 0只做了2455次，只差一次，并且后续都没有做这个操作了，另外还发现子线程每次write管道都是成功的，但是最后一次主线程没有收到这个事件，至于为什么没有收到，就没有继续查了。</p>
<p>但是，还是有个疑问，我访问的php请求，lighttpd应该把请求路径发给php-fpm,自己应该不关心物理路径的啊，就不用搞什么stat cache吧，这个时候想起了当时为了解决某扩展能够正确获取到PATH_INFO的问题，把mod_proxy_core的条件配置从$HTTP["url"] =~ &#8220;\.php$&#8221;改成了$PHYSICAL["existing-path"] =~ &#8220;\.php$&#8221;。马上修改配置，再测试，问题果然没有了， 通过查看lighttpd代码，发现如果配置成$PHYSICAL这种形式，会导致lighttpd去stat这个物理文件，这个操作在mod_proxy_core之前执行，如果用$HTTP[“url”]就不会引发这个问题，到此，一切都清楚了，我看到的其它老的产品线都是配的$HTTP[“url”], 只有少数的几个产品线不是用的$HTTP[“url”]，也只是单机流量非常低的情况才会出现这个问题，很难会让人觉察到!</p>
<p>另外，在追查问题的过程中还发现lighttpd stat_cache机制的两个问题，第一个问题就是处理stat任务的子线程，在stat之后，并没有更新这个stat cache的状态为FINISHED, 下次来查的时候还是没有命中cache, 等于是白干了。如下代码所示：</p>
<p><em>源文件stat_cache.c</em></p>
<p><em><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=51328610777.jpg&amp;type=image%2Fjpeg&amp;width=558&amp;height=170"><img class="aligncenter size-full wp-image-1450" title="5" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=51328610777.jpg&amp;type=image%2Fjpeg&amp;width=558&amp;height=170" alt="" width="558" height="170" /></a></em></p>
<p>第二个问题是就是命中了stat cache, 其实还是需要调用stat判断改cache有没有过期， 所以觉得stat cache本身这个机制也是白搞了，比较没有节省stat的开销，还多搞了，如下代码所示：</p>
<p><em>源文件stat_cache.c</em></p>
<p><em><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=61328610832.jpg&amp;type=image%2Fjpeg&amp;width=588&amp;height=308"><img class="aligncenter size-full wp-image-1451" title="6" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=61328610832.jpg&amp;type=image%2Fjpeg&amp;width=588&amp;height=308" alt="" width="588" height="308" /></a></em></p>
<h2>遗留问题</h2>
<p>子线程和主线程通过管道+epoll的机制来通信，为什么会有一定的概率失败呢？write管道其实是成功的，由于精力有限，这个问题没有继续追查；</p>
<p>管道其实是成功的，由于精力有限，这个问题没有继续追查；</p>
<h2>对lighttpd配置的建议</h2>
<p>如果利用mod_proxy_core做php处理，还是用$HTTP[“url”]做条件吧，例如：</p>
<p><em>$HTTP["url"] =~ &#8220;\.php&#8221; {</em></p>
<p><em> proxy-core.balancer = &#8220;static&#8221;</em></p>
<p><em> proxy-core.protocol = &#8220;fastcgi&#8221;</em></p>
<p><em> proxy-core.allow-x-sendfile = &#8220;enable&#8221;</em></p>
<p><em> proxy-core.backends = ( &#8220;unix:/home/super/php/var/php-cgi.sock&#8221; )</em></p>
<p><em> proxy-core.rewrite-request = (</em></p>
<p><em> &#8220;_pathinfo&#8221; =&gt; ( &#8220;(/[^\?]*)/index\.php(/[^\?]*)&#8221; =&gt; &#8220;$2&#8243; ),</em></p>
<p><em> &#8220;_scriptname&#8221; =&gt; ( &#8220;(.*\.php)&#8221; =&gt; &#8220;$1&#8243; )</em></p>
<p><em> )</em></p>
<p>注意，为了让php-cgi取到正确的PATH_IFNO, 请注意添加“_pathinfo” rewrite规则！</p>
<h2>对lighttpd代码优化的建议</h2>
<p style="padding-left: 30px;">1.在每隔1s的trigger操作中，新增一个操作：将srv-&gt;did_wakeup重置为0，防止这个变量变成1以后永远便不会0的情况发生；<br />
2.stat_cache_thread处理完stat_job之后，要更新源stat_cache_entry的状态为FINISHED, 否则就白搞了；<br />
3.命中stat cache后，不用再通过stat判断该cache是不是最新的，因为最多缓存1s钟；</p>
<p style="padding-left: 30px;">
<p style="padding-left: 30px; text-align: right;">by he_wei</p>
]]></content:encoded>
			<wfw:commentRss>http://stblog.baidu-tech.com/?feed=rss2&amp;p=1444</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>基站轨迹定位算法</title>
		<link>http://stblog.baidu-tech.com/?p=1433</link>
		<comments>http://stblog.baidu-tech.com/?p=1433#comments</comments>
		<pubDate>Fri, 02 Dec 2011 12:58:45 +0000</pubDate>
		<dc:creator>editor</dc:creator>
				<category><![CDATA[lbs技术]]></category>
		<category><![CDATA[Viterbi算法]]></category>
		<category><![CDATA[基站定位]]></category>
		<category><![CDATA[轨迹定位]]></category>

		<guid isPermaLink="false">http://stblog.baidu-tech.com/?p=1433</guid>
		<description><![CDATA[前言
我在哪？是LBS领域首先要解决的问题。因为技术限制，传统的GPS卫星定位只有室外的空旷地区才能够准确定位，对于室内环境来说，GPS定位往往会因“搜星”失败而无法定位。正因为GPS定位的天然缺陷，基于手机基站的定位技术正在蓬勃发展。然而因为基站的覆盖范围大，很难以取得高精度的效果，本文利用基站轨迹，提出了一个提高基站定位精度的方法。
关键字：基站定位，轨迹定位，Viterbi算法

绪论
对于单基站定位，如果仅根据用户当前的基站ID进行定位，精度必定有限。用户可能出现在基站覆盖范围内的任意一个地方，基站的覆盖范围越大，推测出来的用户位置就越不准。
如果我们还知道用户之前一段时间内经过的基站ID序列（称为基站轨迹），此时即可大致判定用户行动轨迹，可借此提高精度。
例举一个简单例子:

如上图所示，假设用户在一瞬间，基站ID从A切换到了B，此时用户属于B基站，单单从B这一个基站考虑的话，很难从其巨大的覆盖圆内取出精准位置。
但是从假设条件我们知道，用户之前一直是在A基站范围内，切换到B基站只是刚刚发生的事情，通过这个条件，人很容易就想到用户很大可能在两圆相交的位置（图中手机的位置）。当然，对于这种特殊情况来说，不光是人，程序也很容易去模拟，但是如果用户继续行走呢？比如一直走到B基站的右侧，还有算法可以继续推算吗？
本算法便是提出一种模型，用以解决此问题。
隐马尔可夫链模型(HMM)与Viterbi算法介绍
隐马尔可夫链作为一个常见的序列预测模型，其具体定义在这里就不细细展开了，有兴趣的用户可以到wiki上搜索相关资料。此模型已经在多个计算机场景中得到了成功的应用，比如拼音输入法中，我们输入”wo zai bai du da sha”，实际想输入的是“我在百度大厦”，也就是说其中有两个序列、一个是拼音字母序列（明序列），一个是汉字序列（隐序列）。两个序列之间有一定的相互关系，如何通过一个已知的“明序列”来推测另一个未知的“隐序列”便是隐马尔可夫链模型要解决的问题。本文的重点也是如何用隐马尔可夫链模型解决轨迹基站定位。
解决隐马尔可夫链问题中的一个著名算法是Viterbi算法。建议读者先去阅读wiki中的Viterbi算法介绍，里面有一个简单的天气的例子详细说明了该算法的大体过程。：
http://en.wikipedia.org/wiki/Viterbi_algorithm
算法细节
隐马尔可夫链模型
我们知道隐马尔可夫链中有两个序列，一个是明序列（A1、A2、A3……），一个是隐序列（B1、B2、B3……）。在本模型之中，明序列（A1、A2、A3……）代表了用户经过的基站序列，隐序列是用户的实际位置序列。我们要做的是通过基站序列，来推测用户的实际位置序列。
首先，我们做如下假设：
● 用户行使在道路上。
●用户在匀速的行驶。
有了这两个假设，再把道路分解成一个一个的路段，我们便可以用路段序列来代替用户的位置序列。也就是说，我们需要通过观察到的用户基站序列，来推测用户的路段序列。如下图所示，我们观察到的是用户所经过的基站覆盖圆情况，需要求得的是用户的路段行驶轨迹。假设观察到基站轨迹是图中的绿色基站，那很明显用户最大可能是沿着红色箭头在行走。

Viterbi算法
如果要用Viterbi算法来解决本应用，只要知道如下几个问题即可。
路段的定义：将路网数据每隔10m抽象成一个有向线段，并且假设用户在这些路段之间离散的跳跃，每一个有向线段，即为一个路段。并且在这里假设用户将要跳跃到的路段只和当前路段相关，与过去的路段无关。
基站序列的定义：每秒钟检测其所处的基站，并且做记录。比如记录信息为（基站A、基站B、基站B、基站C），则表示用户在四秒钟的时间内分别属于基站ABBC三个基站，并且在B基站待了2秒。
路段序列定义：如果用户能够每秒钟记录其所属的路段，这个序列便是路段序列，这是用户的真实位置序列。
路段从属于基站的概率：Viterbi算法中需要知道隐状态从属于明状态的概率，拿到本应用中来看，便是要知道路段从属于基站的概率。解决此问题有如下两个方法：
1.根据路段在基站覆盖圆的具体位置来调整概率，比如，距离基站覆盖圆中心越近，则从属于此基站的概率越大。
2.根据真实的用户数据来推算概率。此方法最准确，其原理也简单，根据用户的真实位置（GPS点）变可以知道用户处于的哪个路段，也就知道了这个路段曾经属于过哪些基站，以及其概率。
如果有用户的真实数据，那么采用方案2无疑是最好的，但是没有数据的情况下，用方案1也可以最大可能的模拟。
路段之间的转移概率：路段之间的转移概率是本算法的重点，在这里做如下定义：假设路段A指向了路段B和C，那么从路段A转移到B和C的概率分别是33%，也就是概率等分，请注意路段A能转移到自身，其到自身的概率也是33%。

Viterbi算法大体过程
Viterbi算法过程是在各个路段之间进行概率转移。如下图所示，用户的基站序列为ABBC,则需要进行三(n-1)次概率转移过程。

对于基站序列ABBC来说，需要分成以下几步：
1.求得每一个路段的初始概率，即各个路段属于基站A的概率。
2.进行第一次转移，从基站A到基站B。比如路段A转移到路段B的概率为：路段A的概率×两个路段的转移概率×路段B属于基站的概率。
3.重复过程2，再转移两次，分别为 基站Bà基站B  基站Bà基站C。
4.最后取出概率最大的路段即为用户位置。
有心的读者可能已经看到，路段之间转移概率的定义是一个路段只能转移与其相连接的路段，这样有一个问题便是，按照上述算法可能所有路段的最终概率均为0。这是因为我们假设在时间间隔内，用户只能从一个路段行驶到相邻的路段，然而实际情况下，用户的速度可能较快，在我们扫描基站的间隔内，用户能跨越多个路段。对于这个问题，我们需要根据用户的速度来对用户扫描到的基站序列进行修复，比如速度是我们假定速度两倍的用户可以将基站序列ABBC变换成AABBBBCC。用户的速度可以通过速度传感器或者基站轨迹进行大概的推算，比如用户经过了10个基站，将这10个基站的覆盖圆中心连接起来，用总长除以时间，即为大概的速度。
算法效果
作者在实现本算法之后，对于匀速运动的定位用户的定位精度提升了一倍之多。对于一些特定用户，更是能明显的起到优化作用，比如高速公路上的用户，基站覆盖半径大，没有用本算法的话，只能返回巨大基站覆盖圆的中心给用户，定位精度极差。采用本算法之后定位精度会得到极大的提高，甚至用户的定位点丝毫不差的跟随用户的真实走动而走动，这也是因为在高速公路上路网比较简单，此算法效果会更突出。
by gaoyunxiang
]]></description>
			<content:encoded><![CDATA[<h1 style="font-size: 20px;">前言</h1>
<p>我在哪？是LBS领域首先要解决的问题。因为技术限制，传统的GPS卫星定位只有室外的空旷地区才能够准确定位，对于室内环境来说，GPS定位往往会因“搜星”失败而无法定位。正因为GPS定位的天然缺陷，基于手机基站的定位技术正在蓬勃发展。然而因为基站的覆盖范围大，很难以取得高精度的效果，本文利用基站轨迹，提出了一个提高基站定位精度的方法。</p>
<p>关键字：基站定位，轨迹定位，Viterbi算法</p>
<p><span id="more-1433"></span></p>
<h1 style="font-size: 20px;">绪论</h1>
<p>对于单基站定位，如果仅根据用户当前的基站ID进行定位，精度必定有限。用户可能出现在基站覆盖范围内的任意一个地方，基站的覆盖范围越大，推测出来的用户位置就越不准。<br />
如果我们还知道用户之前一段时间内经过的基站ID序列（称为基站轨迹），此时即可大致判定用户行动轨迹，可借此提高精度。<br />
例举一个简单例子:<br />
<a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322830304.jpg&amp;type=image%2Fjpeg&amp;width=553&amp;height=308"><img class="aligncenter size-full wp-image-1434" title="1" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322830304.jpg&amp;type=image%2Fjpeg&amp;width=553&amp;height=308" alt="" width="553" height="308" /></a></p>
<p>如上图所示，假设用户在一瞬间，基站ID从A切换到了B，此时用户属于B基站，单单从B这一个基站考虑的话，很难从其巨大的覆盖圆内取出精准位置。<br />
但是从假设条件我们知道，用户之前一直是在A基站范围内，切换到B基站只是刚刚发生的事情，通过这个条件，人很容易就想到用户很大可能在两圆相交的位置（图中手机的位置）。当然，对于这种特殊情况来说，不光是人，程序也很容易去模拟，但是如果用户继续行走呢？比如一直走到B基站的右侧，还有算法可以继续推算吗？<br />
本算法便是提出一种模型，用以解决此问题。</p>
<h1 style="font-size: 20px;">隐马尔可夫链模型(HMM)与Viterbi算法介绍</h1>
<p>隐马尔可夫链作为一个常见的序列预测模型，其具体定义在这里就不细细展开了，有兴趣的用户可以到wiki上搜索相关资料。此模型已经在多个计算机场景中得到了成功的应用，比如拼音输入法中，我们输入”wo zai bai du da sha”，实际想输入的是“我在百度大厦”，也就是说其中有两个序列、一个是拼音字母序列（明序列），一个是汉字序列（隐序列）。两个序列之间有一定的相互关系，如何通过一个已知的“明序列”来推测另一个未知的“隐序列”便是隐马尔可夫链模型要解决的问题。本文的重点也是如何用隐马尔可夫链模型解决轨迹基站定位。</p>
<p>解决隐马尔可夫链问题中的一个著名算法是Viterbi算法。建议读者先去阅读wiki中的Viterbi算法介绍，里面有一个简单的天气的例子详细说明了该算法的大体过程。：</p>
<p><a href="http://en.wikipedia.org/wiki/Viterbi_algorithm">http://en.wikipedia.org/wiki/Viterbi_algorithm</a></p>
<h1 style="font-size: 20px;">算法细节</h1>
<h2>隐马尔可夫链模型</h2>
<p>我们知道隐马尔可夫链中有两个序列，一个是明序列（A1、A2、A3……），一个是隐序列（B1、B2、B3……）。在本模型之中，明序列（A1、A2、A3……）代表了用户经过的基站序列，隐序列是用户的实际位置序列。我们要做的是通过基站序列，来推测用户的实际位置序列。</p>
<p>首先，我们做如下假设：</p>
<p>● 用户行使在道路上。</p>
<p>●用户在匀速的行驶。</p>
<p>有了这两个假设，再把道路分解成一个一个的路段，我们便可以用路段序列来代替用户的位置序列。也就是说，我们需要通过观察到的用户基站序列，来推测用户的路段序列。如下图所示，我们观察到的是用户所经过的基站覆盖圆情况，需要求得的是用户的路段行驶轨迹。假设观察到基站轨迹是图中的绿色基站，那很明显用户最大可能是沿着红色箭头在行走。</p>
<p><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322830448.jpg&amp;type=image%2Fjpeg&amp;width=503&amp;height=318"><img class="aligncenter size-full wp-image-1435" title="1" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322830448.jpg&amp;type=image%2Fjpeg&amp;width=503&amp;height=318" alt="" width="503" height="318" /></a></p>
<h2>Viterbi算法</h2>
<p>如果要用Viterbi算法来解决本应用，只要知道如下几个问题即可。</p>
<p><strong>路段的定义：</strong>将路网数据每隔10m抽象成一个有向线段，并且假设用户在这些路段之间离散的跳跃，每一个有向线段，即为一个路段。并且在这里假设用户将要跳跃到的路段只和当前路段相关，与过去的路段无关。</p>
<p><strong>基站序列的定义：</strong>每秒钟检测其所处的基站，并且做记录。比如记录信息为（基站A、基站B、基站B、基站C），则表示用户在四秒钟的时间内分别属于基站ABBC三个基站，并且在B基站待了2秒。</p>
<p><strong>路段序列定义：</strong>如果用户能够每秒钟记录其所属的路段，这个序列便是路段序列，这是用户的真实位置序列。</p>
<p><strong>路段从属于基站的概率：</strong>Viterbi算法中需要知道隐状态从属于明状态的概率，拿到本应用中来看，便是要知道路段从属于基站的概率。解决此问题有如下两个方法：</p>
<p style="padding-left: 30px;">1.根据路段在基站覆盖圆的具体位置来调整概率，比如，距离基站覆盖圆中心越近，则从属于此基站的概率越大。</p>
<p style="padding-left: 30px;">2.根据真实的用户数据来推算概率。此方法最准确，其原理也简单，根据用户的真实位置（GPS点）变可以知道用户处于的哪个路段，也就知道了这个路段曾经属于过哪些基站，以及其概率。</p>
<p>如果有用户的真实数据，那么采用方案2无疑是最好的，但是没有数据的情况下，用方案1也可以最大可能的模拟。</p>
<p><strong>路段之间的转移概率：</strong>路段之间的转移概率是本算法的重点，在这里做如下定义：假设路段A指向了路段B和C，那么从路段A转移到B和C的概率分别是33%，也就是概率等分，请注意路段A能转移到自身，其到自身的概率也是33%。<br />
<a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322830531.jpg&amp;type=image%2Fjpeg&amp;width=130&amp;height=189"><img class="aligncenter size-full wp-image-1436" title="1" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322830531.jpg&amp;type=image%2Fjpeg&amp;width=130&amp;height=189" alt="" width="130" height="189" /></a></p>
<p><strong>Viterbi</strong><strong>算法大体过程</strong></p>
<p>Viterbi算法过程是在各个路段之间进行概率转移。如下图所示，用户的基站序列为ABBC,则需要进行三(n-1)次概率转移过程。</p>
<p><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322830574.jpg&amp;type=image%2Fjpeg&amp;width=470&amp;height=332"><img class="aligncenter size-full wp-image-1437" title="1" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322830574.jpg&amp;type=image%2Fjpeg&amp;width=470&amp;height=332" alt="" width="470" height="332" /></a></p>
<p>对于基站序列ABBC来说，需要分成以下几步：</p>
<p style="padding-left: 30px;">1.求得每一个路段的初始概率，即各个路段属于基站A的概率。</p>
<p style="padding-left: 30px;">2.进行第一次转移，从基站A到基站B。比如路段A转移到路段B的概率为：路段A的概率×两个路段的转移概率×路段B属于基站的概率。</p>
<p style="padding-left: 30px;">3.重复过程2，再转移两次，分别为 基站Bà基站B  基站Bà基站C。</p>
<p style="padding-left: 30px;">4.最后取出概率最大的路段即为用户位置。</p>
<p>有心的读者可能已经看到，路段之间转移概率的定义是一个路段只能转移与其相连接的路段，这样有一个问题便是，按照上述算法可能所有路段的最终概率均为0。这是因为我们假设在时间间隔内，用户只能从一个路段行驶到相邻的路段，然而实际情况下，用户的速度可能较快，在我们扫描基站的间隔内，用户能跨越多个路段。对于这个问题，我们需要根据用户的速度来对用户扫描到的基站序列进行修复，比如速度是我们假定速度两倍的用户可以将基站序列ABBC变换成AABBBBCC。用户的速度可以通过速度传感器或者基站轨迹进行大概的推算，比如用户经过了10个基站，将这10个基站的覆盖圆中心连接起来，用总长除以时间，即为大概的速度。</p>
<h1 style="font-size: 20px;">算法效果</h1>
<p>作者在实现本算法之后，对于匀速运动的定位用户的定位精度提升了一倍之多。对于一些特定用户，更是能明显的起到优化作用，比如高速公路上的用户，基站覆盖半径大，没有用本算法的话，只能返回巨大基站覆盖圆的中心给用户，定位精度极差。采用本算法之后定位精度会得到极大的提高，甚至用户的定位点丝毫不差的跟随用户的真实走动而走动，这也是因为在高速公路上路网比较简单，此算法效果会更突出。</p>
<p style="padding-left: 30px; text-align: right;">by gaoyunxiang</p>
]]></content:encoded>
			<wfw:commentRss>http://stblog.baidu-tech.com/?feed=rss2&amp;p=1433</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
		<item>
		<title>搜索引擎中的粒度问题</title>
		<link>http://stblog.baidu-tech.com/?p=1429</link>
		<comments>http://stblog.baidu-tech.com/?p=1429#comments</comments>
		<pubDate>Thu, 01 Dec 2011 03:21:49 +0000</pubDate>
		<dc:creator>editor</dc:creator>
				<category><![CDATA[搜索技术]]></category>
		<category><![CDATA[搜索引擎]]></category>
		<category><![CDATA[文本粒度]]></category>

		<guid isPermaLink="false">http://stblog.baidu-tech.com/?p=1429</guid>
		<description><![CDATA[一.前言
传统的搜索引擎的定义，是指一种对于指定的查询（Query），能够返回与之相关的文档集合（Documents）的系统。而百度将这个定义更加丰富化，即搜索引擎能够帮助人们更方便的找到所求。这里的“所求”，比“文档”更加宽泛和丰富，比如一个关于天气的查询，直接返回一个天气预报的窗口，而非一篇关于天气的文档；再如一个关于小游戏的查询，直接返回这个小游戏的Flash页面而非简单的介绍性的文字。
百度对Query深刻的理解，源于自然语言处理技术在其中发挥的巨大作用。对搜索引擎而言，文本切分是最基础也是最重要的自然语言问题之一。今天，我们就来谈谈文本切分粒度与搜索引擎的关系。
本文后续章节组织如下：第二节介绍什么是文本的粒度，第三节讲述搜索引擎的基本原理与文本切分粒度的关系，第四节深入探讨粒度的属性与检索相关性计算，第五节小结。

二.文本粒度
什么是文本的粒度？我们用什么来衡量文本粒度？在回答这些问题前，让我们先看看以下几组词汇：
缠绵、崎岖、葡萄、乒乓
绿茶、篮球、红色、鼠标垫、起重机
打球、跳绳、炒菜、登山
笔记本电脑、高清机顶盒、IP电视
但是、然后、如果、非常
步步惊心、家的n次方、一个人的精彩
百度在线网络技术（北京）有限公司、清华大学
张学友、赵传、工藤新一、里奥内尔·安德雷斯·梅西
……
这几组词汇中，哪些的粒度大，哪些的粒度小？
不管在传统的语言学领域，还是在自然语言处理领域，都没有对粒度下一个清晰准确的定义。但是就搜索引擎而言，我们不妨这样定义：粒度是衡量文本所含信息量的大小。文本含信息量多，粒度就大，反之就小。有了这个原则，我们就很容易判断文本粒度大小了。像“缠绵”，“崎岖”，“葡萄”这些词，虽然有两个字组成，但是仅表达一个意思，这些词的粒度是小的。而“篮球”，“鼠标垫”等词，是由简单词合成的，虽然也只有一个意思，但还可以拆分，如“篮”和“球”，“鼠标”和“垫”。这类词，粒度稍微大一些。而“笔记本电脑”，“高清机顶盒”这样的词，粒度就更大了。
专名是一类比较特殊的词，尽管所含字数很多，但其实只表达一个意思，如“步步惊心”，“家的n次方”这样的电影、电视剧的名称，粒度是很小的。机构名、人名等属于有内部结构的专名，比电影名的粒度稍大一些。
显然易见，我们在讨论文本粒度时，理想的方式是从语义角度出发，合理的分析和判断。然而以上我们仅对粒度做了定性的分析，为粒度找一个合适的度量单位和计算方法，是百度人一直追求的目标。
三.搜索引擎的基本原理与词汇切分关系
3.1 搜索引擎的基本原理
文本检索系统，是搜索引擎最简单的实现方式。通过返回包含关键字的页面，来满足用户的检索需求。形式化的表达就是给定一系列关键字集合K,要求返回所有包含关键字的文档D,对D中的任意一个文档d，包含K中的任意一个关键字k。
一般我们采用倒排索引的方式来实现这个系统。所谓倒排索引，就是对关键字建立索引，记录包含这个关键字的文档集合D。对于请求的关键字集合，找出所有关键字对应的索引，并对索引求交，最后返回同时存在于所有索引中的文档。
在百度，我们不仅允许用户输入关键字，也可以输入任何长度在一定范围内的文本。此时我们需要对文本做一定处理，切分成一系列关键字，从而能够从倒排索引中找出对应的文档。
那么为什么要对输入文本做切分，如果不切分会有什么问题？
我们可以想象一下，如果不对输入文本做切分，直接用输入文本去做匹配，会怎么样？首先，得到的结果会非常少，因为直接用全部文本匹配，就失去了灵活性，对结果限制的非常死，必须完全匹配才能满足要求；其次，系统性能会非常差，因为需要对所有长度的文本都建立索引，这是指数级的，在实际系统中根本不可能实现。再考虑一下另一个极端？我们对输入文本做单字切分，结果又是怎样？我们会得到大量无关的页面，不仅浪费系统性能，对相关性计算也造成了巨大的压力。
所以，我们需要对文本做一个合适的切分。
3.2 用户满意度与粒度关系
无论是建立倒排索引、还是处理输入文本，我们都需要对文本做切分，切出合适的关键字出来。为了能够使用户对查询结果满意，搜索引擎需要什么样的粒度？让我们先看一下下面几个例子：
1. Q:“北京地图” P1:“北京市地图” P2：“北京城市地图”
2. Q:“闹太套是神马意思”, P:”A：神呐，我骑不了这烈马。B：闹太套！”
3. Q:“兽兽门” P:“兽兽艳照门”
4. Q1:“工业园” Q2:“园区” P:“工业园区”
5. Q：“ip电视” P1:“ip电视的历史” P2:“电视销售…您的IP是xxx”
注：Q表示query，P表示页面中包含Q的内容
Case1，要求query能找到P1和P2这样的结果，就必须对P1和P2都切出“北京”这个词来。Case2，必须把”神马”切为一个词，否则会召回P这样不相关的结果。Case3，不能把Q中的“兽兽门“切为一个词，而需要切除“兽兽”，否则就召不回”兽兽艳照门”这个结果。Case4中，对“工业园区”这样的页面，必须同时切出“工业园”和“园区”这两个重叠的词汇，才能保证Q1和Q2都能召回。Case5与Case2类似，如果把ip和电视分开切分，将召回P2这样不相关的结果。
以上几个case，基本上概括了搜索引擎对切分粒度的要求，我们可以从两方面来描述：1）影响召回 2）影响相关性

以上从用户满意度的角度，讨论了搜索引擎与粒度的关系，当然，这是最基本的要求，在第四节我们还会对文本的粒度问题做更深入的分析。
3.3 搜索系统性能与粒度的关系
显而易见，粒度越小，召回就越多，建立倒排索引时，索引的长度就越长；粒度的层次越多，索引的数量就越多。一个多，一个长，就对搜索系统的性能构成了极大的考验。
一般而言，大型搜索引擎的索引都采用分布式系统。不同文本的索引，被某种hash算法“分配”到了某台机器。理论上讲，索引的数量的增长，只会造成所需机器的增长，而对整体系统性能的消耗影响比较小。所以一般搜索引擎会从性价比的角度来考虑索引数量与机器数量的折衷，也就是召回与硬件投入的折衷。粒度分析对于折衷的性价比也有一定的贡献，在粒度层次里，当粒度逐渐变小的过程中，我们并不一定对所有小粒度词都建索引，而是选择“更有可能召回相关结果”的小粒度词。词汇的什么性质决定了“更有可能召回相关结果”？我们同样会在第四节讨论。
四.深入分析粒度的性质
在第三节中我们反复提到：一般情况下，粒度越大，相关性越好，召回越差；粒度越小，相关性越差，召回越好。在搜索引擎中，如果做到折衷呢?基本的原则是，在系统性能可接受的前提下，尽量多召回有效结果，计算相关性时，将最相关的排在前面。
我们如何做到将合理减小粒度，增加有效召回，又如何做到将最好的排在最前呢？这里涉及到两个问题：紧密度与重要性。
既然粒度是衡量文本所含信息量的大小，那么紧密度就是描述文本所含信息紧密程度的量。再说的通俗一些，紧密度就是信息被人们表达和接受的稳定程度。稳定有两种解释，第一，稳定是相对于临时而言的。一般来说，如果信息是因为某些因素临时组合在一起，那就是不稳定的，即不紧密。比如许多动宾结构的短语（“过马路”，“踢足球”），定中结构的短语（“红苹果”，“豪华轿车”）。第二，稳定是相对于顺序不固定而言的。如果同样一个信息，内部的子信息顺序可以互换，那么这个词汇就不稳定，即不紧密。比如一些大粒度的词汇“鼠标护腕垫”、“护腕鼠标垫”。
由此可见，我们根据词汇的紧密程度，可以将结果中表述与查询表述的一致程度联系起来，作为计算相关性的一个因素。同样，我们也可以将紧密度作为减小粒度的依据之一，词汇越不紧密，我们就有理由将其拆分为更小的粒度。
短语的重要性，其实是短语子成分的重要性，有很多定义。其中一种被普遍接受的定义为其占短语完整含义的比例。一般情况下，偏正结构短语中，“正”的部分比较重要，比如“绿茶”中的“茶”，但也有例外，如“珊瑚虫”中的“珊瑚”。而主谓、动宾短语一般来说，都比较重要，如“打球”，“你说”。所以，短语的子成分重要性，不能仅靠语法来识别，而应综合各种因素来确定。
假设有了词汇的子成分重要性，那么就可以帮助判断将词汇粒度变小后的语义损失风险程度（注意，这里使用了“语义损失”，而不是“转义”，想一想为什么）。这也就回答了第四节末尾的问题：语义损失越小，越有可能召回相关结果。
五.结束语
本文介绍了搜索引擎中的粒度问题，重点讨论了搜索引擎与短语切分粒度的关系，并进一步探讨了短语的两个重要性质——紧密度和重要性。通过本文，读者应该能够大致明白搜索引擎中关于粒度的种种。当然，本文只是对搜索引擎的粒度问题开了一个头，怎么合理的处理好粒度、在不同场合使用何种粒度，都是需要我们继续深入研究的。
by xinzhou
]]></description>
			<content:encoded><![CDATA[<h2>一.前言</h2>
<p>传统的搜索引擎的定义，是指一种对于指定的<strong>查询</strong>（Query），能够返回与之相关的<strong>文档集合</strong>（Documents）的系统。而百度将这个定义更加丰富化，即搜索引擎能够帮助人们更方便的找到所求。这里的“所求”，比“文档”更加宽泛和丰富，比如一个关于天气的查询，直接返回一个天气预报的窗口，而非一篇关于天气的文档；再如一个关于小游戏的查询，直接返回这个小游戏的Flash页面而非简单的介绍性的文字。</p>
<p>百度对Query深刻的理解，源于自然语言处理技术在其中发挥的巨大作用。对搜索引擎而言，文本切分是最基础也是最重要的自然语言问题之一。今天，我们就来谈谈文本切分粒度与搜索引擎的关系。</p>
<p>本文后续章节组织如下：第二节介绍什么是文本的粒度，第三节讲述搜索引擎的基本原理与文本切分粒度的关系，第四节深入探讨粒度的属性与检索相关性计算，第五节小结。</p>
<p><span id="more-1429"></span></p>
<h2>二.文本粒度</h2>
<p>什么是<strong>文本的粒度</strong>？我们用什么来衡量文本粒度？在回答这些问题前，让我们先看看以下几组词汇：</p>
<p style="padding-left: 30px;">缠绵、崎岖、葡萄、乒乓</p>
<p style="padding-left: 30px;">绿茶、篮球、红色、鼠标垫、起重机</p>
<p style="padding-left: 30px;">打球、跳绳、炒菜、登山</p>
<p style="padding-left: 30px;">笔记本电脑、高清机顶盒、IP电视</p>
<p style="padding-left: 30px;">但是、然后、如果、非常</p>
<p style="padding-left: 30px;">步步惊心、家的n次方、一个人的精彩</p>
<p style="padding-left: 30px;">百度在线网络技术（北京）有限公司、清华大学</p>
<p style="padding-left: 30px;">张学友、赵传、工藤新一、里奥内尔·安德雷斯·梅西</p>
<p style="padding-left: 30px;">……</p>
<p>这几组词汇中，哪些的粒度大，哪些的粒度小？</p>
<p>不管在传统的语言学领域，还是在自然语言处理领域，都没有对粒度下一个清晰准确的定义。但是就搜索引擎而言，我们不妨这样定义：<strong>粒度是衡量文本所含信息量的大小</strong>。文本含信息量多，粒度就大，反之就小。有了这个原则，我们就很容易判断文本粒度大小了。像“缠绵”，“崎岖”，“葡萄”这些词，虽然有两个字组成，但是仅表达一个意思，这些词的粒度是小的。而“篮球”，“鼠标垫”等词，是由简单词合成的，虽然也只有一个意思，但还可以拆分，如“篮”和“球”，“鼠标”和“垫”。这类词，粒度稍微大一些。而“笔记本电脑”，“高清机顶盒”这样的词，粒度就更大了。</p>
<p>专名是一类比较特殊的词，尽管所含字数很多，但其实只表达一个意思，如“步步惊心”，“家的n次方”这样的电影、电视剧的名称，粒度是很小的。机构名、人名等属于有内部结构的专名，比电影名的粒度稍大一些。</p>
<p>显然易见，我们在讨论文本粒度时，理想的方式是从语义角度出发，合理的分析和判断。然而以上我们仅对粒度做了定性的分析，为粒度找一个合适的度量单位和计算方法，是百度人一直追求的目标。</p>
<h2>三.搜索引擎的基本原理与词汇切分关系</h2>
<h3>3.1 搜索引擎的基本原理</h3>
<p style="padding-left: 30px;">文本检索系统，是搜索引擎最简单的实现方式。通过返回包含关键字的页面，来满足用户的检索需求。形式化的表达就是给定一系列关键字集合K,要求返回所有包含关键字的文档D,对D中的任意一个文档d，包含K中的任意一个关键字k。</p>
<p style="padding-left: 30px;">一般我们采用<strong>倒排索引</strong>的方式来实现这个系统。所谓倒排索引，就是对关键字建立索引，记录包含这个关键字的文档集合D。对于请求的关键字集合，找出所有关键字对应的索引，并对索引求交，最后返回同时存在于所有索引中的文档。</p>
<p style="padding-left: 30px;">在百度，我们不仅允许用户输入关键字，也可以输入任何长度在一定范围内的文本。此时我们需要对文本做一定处理，切分成一系列关键字，从而能够从倒排索引中找出对应的文档。</p>
<p style="padding-left: 30px;">那么为什么要对输入文本做切分，如果不切分会有什么问题？</p>
<p style="padding-left: 30px;">我们可以想象一下，如果不对输入文本做切分，直接用输入文本去做匹配，会怎么样？首先，得到的结果会非常少，因为直接用全部文本匹配，就失去了灵活性，对结果限制的非常死，必须完全匹配才能满足要求；其次，系统性能会非常差，因为需要对所有长度的文本都建立索引，这是指数级的，在实际系统中根本不可能实现。再考虑一下另一个极端？我们对输入文本做单字切分，结果又是怎样？我们会得到大量无关的页面，不仅浪费系统性能，对相关性计算也造成了巨大的压力。</p>
<p style="padding-left: 30px;">所以，我们需要对文本做一个<strong>合适</strong>的切分。</p>
<h3>3.2 用户满意度与粒度关系</h3>
<p style="padding-left: 30px;">无论是建立倒排索引、还是处理输入文本，我们都需要对文本做切分，切出合适的关键字出来。为了能够使用户对查询结果满意，搜索引擎需要什么样的粒度？让我们先看一下下面几个例子：</p>
<p style="padding-left: 30px;">1. Q:“北京地图” P1:“北京市地图” P2：“北京城市地图”</p>
<p style="padding-left: 30px;">2. Q:“闹太套是神马意思”, P:”A：神呐，我骑不了这烈马。B：闹太套！”</p>
<p style="padding-left: 30px;">3. Q:“兽兽门” P:“兽兽艳照门”</p>
<p style="padding-left: 30px;">4. Q1:“工业园” Q2:“园区” P:“工业园区”</p>
<p style="padding-left: 30px;">5. Q：“ip电视” P1:“ip电视的历史” P2:“电视销售…您的IP是xxx”</p>
<p style="padding-left: 30px;">注：Q表示query，P表示页面中包含Q的内容</p>
<p style="padding-left: 30px;">Case1，要求query能找到P1和P2这样的结果，就必须对P1和P2都切出“北京”这个词来。Case2，必须把”神马”切为一个词，否则会召回P这样不相关的结果。Case3，不能把Q中的“兽兽门“切为一个词，而需要切除“兽兽”，否则就召不回”兽兽艳照门”这个结果。Case4中，对“工业园区”这样的页面，必须同时切出“工业园”和“园区”这两个重叠的词汇，才能保证Q1和Q2都能召回。Case5与Case2类似，如果把ip和电视分开切分，将召回P2这样不相关的结果。</p>
<p style="padding-left: 30px;">以上几个case，基本上概括了搜索引擎对切分粒度的要求，我们可以从两方面来描述：1）影响召回 2）影响相关性</p>
<p style="padding-left: 30px;"><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-12-1-11-09-081322708969.jpg&amp;type=image%2Fjpeg&amp;width=572&amp;height=154"><img class="aligncenter size-full wp-image-1430" title="2011-12-1 11-09-08" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-12-1-11-09-081322708969.jpg&amp;type=image%2Fjpeg&amp;width=572&amp;height=154" alt="" width="572" height="154" /></a></p>
<p style="padding-left: 30px;">以上从用户满意度的角度，讨论了搜索引擎与粒度的关系，当然，这是最基本的要求，在第四节我们还会对文本的粒度问题做更深入的分析。</p>
<h3>3.3 搜索系统性能与粒度的关系</h3>
<p style="padding-left: 30px;">显而易见，粒度越小，召回就越多，建立倒排索引时，索引的长度就越长；粒度的层次越多，索引的数量就越多。一个多，一个长，就对搜索系统的性能构成了极大的考验。</p>
<p style="padding-left: 30px;">一般而言，大型搜索引擎的索引都采用分布式系统。不同文本的索引，被某种hash算法“分配”到了某台机器。理论上讲，索引的数量的增长，只会造成所需机器的增长，而对整体系统性能的消耗影响比较小。所以一般搜索引擎会从性价比的角度来考虑索引数量与机器数量的折衷，也就是召回与硬件投入的折衷。粒度分析对于折衷的性价比也有一定的贡献，在粒度层次里，当粒度逐渐变小的过程中，我们并不一定对所有小粒度词都建索引，而是选择“更有可能召回相关结果”的小粒度词。词汇的什么性质决定了“更有可能召回相关结果”？我们同样会在第四节讨论。</p>
<h2>四.深入分析粒度的性质</h2>
<p>在第三节中我们反复提到：一般情况下，粒度越大，相关性越好，召回越差；粒度越小，相关性越差，召回越好。在搜索引擎中，如果做到折衷呢?基本的原则是，在系统性能可接受的前提下，尽量多召回有效结果，计算相关性时，将最相关的排在前面。</p>
<p>我们如何做到将合理减小粒度，增加有效召回，又如何做到将最好的排在最前呢？这里涉及到两个问题：紧密度与重要性。</p>
<p>既然粒度是衡量文本所含信息量的大小，那么紧密度就是描述文本所含信息紧密程度的量。再说的通俗一些，紧密度就是信息被人们表达和接受的稳定程度。稳定有两种解释，第一，稳定是相对于临时而言的。一般来说，如果信息是因为某些因素临时组合在一起，那就是不稳定的，即不紧密。比如许多动宾结构的短语（“过马路”，“踢足球”），定中结构的短语（“红苹果”，“豪华轿车”）。第二，稳定是相对于顺序不固定而言的。如果同样一个信息，内部的子信息顺序可以互换，那么这个词汇就不稳定，即不紧密。比如一些大粒度的词汇“鼠标护腕垫”、“护腕鼠标垫”。</p>
<p>由此可见，我们根据词汇的紧密程度，可以将结果中表述与查询表述的一致程度联系起来，作为计算相关性的一个因素。同样，我们也可以将紧密度作为减小粒度的依据之一，词汇越不紧密，我们就有理由将其拆分为更小的粒度。</p>
<p>短语的重要性，其实是短语子成分的重要性，有很多定义。其中一种被普遍接受的定义为其占短语完整含义的比例。一般情况下，偏正结构短语中，“正”的部分比较重要，比如“绿茶”中的“茶”，但也有例外，如“珊瑚虫”中的“珊瑚”。而主谓、动宾短语一般来说，都比较重要，如“打球”，“你说”。所以，短语的子成分重要性，不能仅靠语法来识别，而应综合各种因素来确定。</p>
<p>假设有了词汇的子成分重要性，那么就可以帮助判断将词汇粒度变小后的语义损失风险程度（注意，这里使用了“语义损失”，而不是“转义”，想一想为什么）。这也就回答了第四节末尾的问题：语义损失越小，越有可能召回相关结果。</p>
<h2>五.结束语</h2>
<p>本文介绍了搜索引擎中的粒度问题，重点讨论了搜索引擎与短语切分粒度的关系，并进一步探讨了短语的两个重要性质——紧密度和重要性。通过本文，读者应该能够大致明白搜索引擎中关于粒度的种种。当然，本文只是对搜索引擎的粒度问题开了一个头，怎么合理的处理好粒度、在不同场合使用何种粒度，都是需要我们继续深入研究的。</p>
<p style="text-align: right;">by xinzhou</p>
]]></content:encoded>
			<wfw:commentRss>http://stblog.baidu-tech.com/?feed=rss2&amp;p=1429</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>基于主特征空间相似度计算的切分算法及切分框架</title>
		<link>http://stblog.baidu-tech.com/?p=1383</link>
		<comments>http://stblog.baidu-tech.com/?p=1383#comments</comments>
		<pubDate>Tue, 29 Nov 2011 11:49:20 +0000</pubDate>
		<dc:creator>editor</dc:creator>
				<category><![CDATA[搜索技术]]></category>
		<category><![CDATA[自然语言处理]]></category>
		<category><![CDATA[Query Segmentation]]></category>
		<category><![CDATA[中文分词]]></category>
		<category><![CDATA[无监督]]></category>

		<guid isPermaLink="false">http://stblog.baidu-tech.com/?p=1383</guid>
		<description><![CDATA[摘要: 本文从切分的需求、作用、难点等方面谈起，介绍分析了目前主流的各种切分方法以及其优缺点，并介绍了一个新型的无监督切分方法，并在此基础上对切分在工程需求上进行了相应的分析和讨论，在最后在此算法基础上给出一个融合各种优点的切分框架。
关键词: 中文分词， Query Segmentation，无监督
技术领域: 自然语言处理

我们为什么要切分？
说到切分(segmentation)，大多数人最容易想到的就是中文分词。作为没有天然空格区分的语言，切词可以帮助计算机去索引文章，从而便于信息检索等方面。该部分主要用到了分词的一个方面：降低搜索引擎的性能消耗。我们常用的汉字有5000多个，常用词组是几十万个。在倒排索引中，如果用每个字做索引的话，那么会造成每个字对应的拉链非常长。所以我们一般会用词组来代替单个汉字建立索引。除此，切词更重要的一个功能是帮助计算机理解文字，在这个层次上，切词是不分语言的，任何一个语言，涉及到计算机去“理解”的时候，首先要做的，就是先去切分并在一定程度上消除歧义。这是因为，我们知道计算机本身擅长做的工作就是匹配计算。假设我们可以把每个字词都指向一个语义，当输入一个句子的时候，每个字对应语义的累加要弱于词组语义的累加(因为单独用字语义累加的时候，有个潜在的假设是字和字之间是独立的)，现在引入切分目的就是勾勒出字与字之间的关系，从而让计算机更好的理解。
切分的难点在哪里？
简单的讲，评价切分效果可以从三个层次来判定：切分边界，切分片段，整个句子切分结果是否正确。切分边界是指：相邻的token(在中文切分中token可以认为是汉字，在英文中可以认为是单词)之间是否应该被切开；句子级别是指，整个句子的切分结果是不是完全准确。切分片段是介于二者之间一种评估策略： 1. 切分结果片段中是否召回了需要切出的片段(recall); 2. 切分的结果中是否有错误的切分结果(precision)。下面我们从切分算法两个重要的考量标准来阐述切分的难点，即新词识别和歧义性的处理。
新词：切分算法在召回方向上的难题主要为歧义现象和新词的出现。如果一个切分算法无法识别新词从而导致其未召回，最后会影响计算机对该切分句子的理解。前面我们有讲，字到词的过程可以让计算机“假装”理解这个词的意思。比如最近的一个人名新词“位菊月”，如果被切分算法切散后，计算机很难理解这个片段的含义，从而导致在诸如机器翻译等应用中无法准确进行处理。
歧义性：切分算法要求解决切分片段歧义性，切分结果合理。汉字作为表示中文信息的载体，假设每个字/词表示的信息有个上限,假设每种语言总体的信息量接近，由于常用字数有限，这些汉字之间就要有较多的组合形式来成词并表达不同的语义。如果一个汉字可以同时作为2个词的部分，当这2个词按序出现时，就潜在包含了歧义。目前歧义主要分为2种：交叉型歧义，即相邻歧义片段之间有若干token重复，比如“长春市长春药店”，“长春市”与“市长”“长春”与“春药”都是交叉型歧义片段。该歧义现象存在于任何语言的切分过程，比如针对英文，“new york times square”中的”new york times” 和”times square”；还有一种歧义为覆盖型歧义，即token序列在不同语义下需要拆分开或合并在一起，比如“他马上就来”和“他从马上下来”，对后者来讲，切分为“马上”时则导致“从马背上”的意思被“立刻的”意思所覆盖。
除此，切分算法在应用中还要具备不错的性能，在引入统计学习算法时，还要考虑人力在标注语料上面的成本。随着时间的发展，语言也会进行相应的变化，只是在不同的领域会按照不同的速度演变着。因此，切分算法同样需要与时俱进的优化。比如添加更多的词进入词典，更新重建语言模型(Language Model), 对于某些基于判别式(Discriminative model)切分的方法，比如CRFs，需要不定期更新人工标注语料来使得切分算法适应处理当前语料等等。
切分算法作为一个基础的研究方向一直是很多科研人员努力奋斗方向，并产生出大量优秀的算法。在下面的章节中，我们简单的介绍一些主流的、在工程中有着一定应用的切分方法。
切分的主流方法简介
在介绍我们的切分方法之前，我们先从2个方面来简单介绍现有主流切分算法：即基于规则的切分方法和一些统计切分模型。
1.基于规则的切分方法。
基于规则的的切分方法主要表现为基于词典匹配，如：正向最大匹配(Forward Maximum Matching, FMM)，逆向最大匹配，最少切分（使每一句中切出的词数最小）等等。
以正向最大匹配为例，其基本思想是：对于待处理文本，从左到右尽量匹配词典中的最长词，匹配到的词即该处理文本的一个切分片段。假设词典中有{百度，百度公司，中文，切分，算法}5个词，则句子“百度公司的中文分词算法”的正确切分结果为“百度公司&#124;的&#124;中文&#124;分词&#124;算法”。
基于规则匹配的切分算法，缺点主要有2点：(1).无法很好的解决切分歧义问题。上述提到的三种方法都是从不同的角度尝试去解决歧义问题，但是它们对于歧义消除的效果不显著，特别当词典词增多的时候，词与词之间交叉现象加剧，该方法的歧义处理能力就会相应的减弱。(2).该方法无法识别新词。在该方法下，线下挖掘大量的新词加入词典的收益和整体效果并非线性关系，词典膨胀的同时，切分的歧义现象会更加严重。
由于该方法简单快捷，因此针对上述缺点也有一些工作是将统计方法用在FMM上，该类方法主要运用贝叶斯模型(Naïve Bayes)、互信息(Mutual Information)以及t-test chi-2等检验手段对有切分歧义的相邻片段进行消岐。这方面可以参考“基于无指导学习策略的无词表条件下的汉语自动分词”等文献。
2.统计切分模型
统计切分算法主要利用语言模型、标注数据等资源，根据切分假设建立模型并利用其对应的资源进行模型参数优化，借助模型代替规则完成切分。
2.1 基于语言模型、Markov链的切分方法
对于一个待处理的句子其每个处理的token(t_i,在中文分词中可以认为是汉字，在英文中可以认为是单词等)构成一个观察序列，各种可能的切分片段即为隐含的状态。该方法的目的即为观察序列找一个最有可能发生的隐含状态序列其中每个状态status(s_i)即为词典词。整个切分过程即为了寻找一个可行的切分结果利用markov假设，使得达到maximum likelihood：

如果有了词典词的各种概率分布（可以通过利用语言模型进行极大似然估计，利用EM算法进行参数优化等得到），根据viterbi解码算法，很容易就得到了切分结果。随着语言模型的广泛应用，以及各种learning算法的发展，该方法也具有广泛的应用场景。深入阅读的可以参考以下两篇文章：《Self-supervised Chinese word segmentation》，《unsupervised query segmentation using generative language models and wikipedia》。
现在说说该方法的不足：1. 在计算序列的概率时，我们依据markov假设，即：当前状态仅仅和前面一个状态相关。而在我们实际应用中，当前的状态可以和前面状态有关系，也可以和前面的前面状态有关系，也可以不和前面的状态有关系等等(这里面的是否有关系是在一定阀值条件下说的)。2. 在估算词典词之间的概率分布时，EM作为一个常用的算法也有自身的不足。
2.2. 条件随机场模型(Conditional Random Fields )
CRFs是一个无向图模型，它的目标是寻在在条件概率最大情况下的一种组合[Conditional Random Fields: Probabilistic Models for Segmenting and Labeling Sequence Data.]。在NLP技术领域中主要用于文本标注，应用场景主要为：分词（标注字的词位信息，由字构词），词性标注（Pos-Tagging，标注分词的词性，例如：名词，动词，助词），命名实体识别（Named [...]]]></description>
			<content:encoded><![CDATA[<p>摘要: 本文从切分的需求、作用、难点等方面谈起，介绍分析了目前主流的各种切分方法以及其优缺点，并介绍了一个新型的无监督切分方法，并在此基础上对切分在工程需求上进行了相应的分析和讨论，在最后在此算法基础上给出一个融合各种优点的切分框架。</p>
<p>关键词: 中文分词， Query Segmentation，无监督</p>
<p>技术领域: 自然语言处理</p>
<p><span id="more-1383"></span></p>
<h2>我们为什么要切分？</h2>
<p style="padding-left: 30px;">说到切分(segmentation)，大多数人最容易想到的就是中文分词。作为没有天然空格区分的语言，切词可以帮助计算机去索引文章，从而便于信息检索等方面。该部分主要用到了分词的一个方面：降低搜索引擎的性能消耗。我们常用的汉字有5000多个，常用词组是几十万个。在倒排索引中，如果用每个字做索引的话，那么会造成每个字对应的拉链非常长。所以我们一般会用词组来代替单个汉字建立索引。除此，切词更重要的一个功能是帮助计算机理解文字，在这个层次上，切词是不分语言的，任何一个语言，涉及到计算机去“理解”的时候，首先要做的，就是先去切分并在一定程度上消除歧义。这是因为，我们知道计算机本身擅长做的工作就是匹配计算。假设我们可以把每个字词都指向一个语义，当输入一个句子的时候，每个字对应语义的累加要弱于词组语义的累加(因为单独用字语义累加的时候，有个潜在的假设是字和字之间是独立的)，现在引入切分目的就是勾勒出字与字之间的关系，从而让计算机更好的理解。</p>
<h2>切分的难点在哪里？</h2>
<p style="padding-left: 30px;">简单的讲，评价切分效果可以从三个层次来判定：切分边界，切分片段，整个句子切分结果是否正确。切分边界是指：相邻的token(在中文切分中token可以认为是汉字，在英文中可以认为是单词)之间是否应该被切开；句子级别是指，整个句子的切分结果是不是完全准确。切分片段是介于二者之间一种评估策略： 1. 切分结果片段中是否召回了需要切出的片段(recall); 2. 切分的结果中是否有错误的切分结果(precision)。下面我们从切分算法两个重要的考量标准来阐述切分的难点，即新词识别和歧义性的处理。</p>
<p style="padding-left: 30px;"><strong>新词：</strong>切分算法在召回方向上的难题主要为歧义现象和新词的出现。如果一个切分算法无法识别新词从而导致其未召回，最后会影响计算机对该切分句子的理解。前面我们有讲，字到词的过程可以让计算机“假装”理解这个词的意思。比如最近的一个人名新词“位菊月”，如果被切分算法切散后，计算机很难理解这个片段的含义，从而导致在诸如机器翻译等应用中无法准确进行处理。</p>
<p style="padding-left: 30px;"><strong>歧义性：</strong>切分算法要求解决切分片段歧义性，切分结果合理。汉字作为表示中文信息的载体，假设每个字/词表示的信息有个上限,假设每种语言总体的信息量接近，由于常用字数有限，这些汉字之间就要有较多的组合形式来成词并表达不同的语义。如果一个汉字可以同时作为2个词的部分，当这2个词按序出现时，就潜在包含了歧义。目前歧义主要分为2种：交叉型歧义，即相邻歧义片段之间有若干token重复，比如“长春市长春药店”，“长春市”与“市长”“长春”与“春药”都是交叉型歧义片段。该歧义现象存在于任何语言的切分过程，比如针对英文，“new york times square”中的”new york times” 和”times square”；还有一种歧义为覆盖型歧义，即token序列在不同语义下需要拆分开或合并在一起，比如“他马上就来”和“他从马上下来”，对后者来讲，切分为“马上”时则导致“从马背上”的意思被“立刻的”意思所覆盖。</p>
<p style="padding-left: 30px;">除此，切分算法在应用中还要具备不错的性能，在引入统计学习算法时，还要考虑人力在标注语料上面的成本。随着时间的发展，语言也会进行相应的变化，只是在不同的领域会按照不同的速度演变着。因此，切分算法同样需要与时俱进的优化。比如添加更多的词进入词典，更新重建语言模型(Language Model), 对于某些基于判别式(Discriminative model)切分的方法，比如CRFs，需要不定期更新人工标注语料来使得切分算法适应处理当前语料等等。</p>
<p style="padding-left: 30px;">切分算法作为一个基础的研究方向一直是很多科研人员努力奋斗方向，并产生出大量优秀的算法。在下面的章节中，我们简单的介绍一些主流的、在工程中有着一定应用的切分方法。</p>
<h2>切分的主流方法简介</h2>
<p style="padding-left: 30px;">在介绍我们的切分方法之前，我们先从2个方面来简单介绍现有主流切分算法：即基于规则的切分方法和一些统计切分模型。</p>
<p style="padding-left: 60px;">1.基于规则的切分方法。</p>
<p style="padding-left: 90px;">基于规则的的切分方法主要表现为基于词典匹配，如：正向最大匹配(Forward Maximum Matching, FMM)，逆向最大匹配，最少切分（使每一句中切出的词数最小）等等。</p>
<p style="padding-left: 90px;">以正向最大匹配为例，其基本思想是：对于待处理文本，从左到右尽量匹配词典中的最长词，匹配到的词即该处理文本的一个切分片段。假设词典中有{百度，百度公司，中文，切分，算法}5个词，则句子“百度公司的中文分词算法”的正确切分结果为“百度公司|的|中文|分词|算法”。</p>
<p style="padding-left: 90px;">基于规则匹配的切分算法，缺点主要有2点：(1).无法很好的解决切分歧义问题。上述提到的三种方法都是从不同的角度尝试去解决歧义问题，但是它们对于歧义消除的效果不显著，特别当词典词增多的时候，词与词之间交叉现象加剧，该方法的歧义处理能力就会相应的减弱。(2).该方法无法识别新词。在该方法下，线下挖掘大量的新词加入词典的收益和整体效果并非线性关系，词典膨胀的同时，切分的歧义现象会更加严重。</p>
<p style="padding-left: 90px;">由于该方法简单快捷，因此针对上述缺点也有一些工作是将统计方法用在FMM上，该类方法主要运用贝叶斯模型(Naïve Bayes)、互信息(Mutual Information)以及t-test chi-2等检验手段对有切分歧义的相邻片段进行消岐。这方面可以参考“基于无指导学习策略的无词表条件下的汉语自动分词”等文献。</p>
<p style="padding-left: 60px;">2.统计切分模型</p>
<p style="padding-left: 90px;">统计切分算法主要利用语言模型、标注数据等资源，根据切分假设建立模型并利用其对应的资源进行模型参数优化，借助模型代替规则完成切分。</p>
<p style="padding-left: 60px;"><strong>2.1 </strong><strong>基于语言模型、Markov</strong><strong>链的切分方法</strong></p>
<p style="padding-left: 90px;">对于一个待处理的句子<a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-18-42-291322563358.jpg&amp;type=image%2Fjpeg&amp;width=124&amp;height=28"><img class="alignleft size-full wp-image-1391" title="2011-11-29 18-42-29" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-18-42-291322563358.jpg&amp;type=image%2Fjpeg&amp;width=124&amp;height=28" alt="" width="124" height="28" /></a>其每个处理的token(t_i,在中文分词中可以认为是汉字，在英文中可以认为是单词等)构成一个观察序列，各种可能的切分片段即为隐含的状态。该方法的目的即为观察序列找一个最有可能发生的隐含状态序列<a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-18-03-151322561005.jpg&amp;type=image%2Fjpeg&amp;width=127&amp;height=33"><img class="alignleft size-full wp-image-1387" title="2011-11-29 18-03-15" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-18-03-151322561005.jpg&amp;type=image%2Fjpeg&amp;width=127&amp;height=33" alt="" width="127" height="33" /></a>其中每个状态status(s_i)即为词典词。整个切分过程即为了寻找一个可行的切分结果<a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-18-04-151322561074.jpg&amp;type=image%2Fjpeg&amp;width=31&amp;height=32"><img class="alignleft size-full wp-image-1388" title="2011-11-29 18-04-15" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-18-04-151322561074.jpg&amp;type=image%2Fjpeg&amp;width=31&amp;height=32" alt="" width="31" height="32" /></a>利用markov假设，使得达到maximum likelihood：<br />
<a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-18-05-261322563107.jpg&amp;type=image%2Fjpeg&amp;width=501&amp;height=54"><img class="aligncenter size-full wp-image-1389" title="2011-11-29 18-05-26" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-18-05-261322563107.jpg&amp;type=image%2Fjpeg&amp;width=501&amp;height=54" alt="" width="501" height="54" /></a><br />
如果有了词典词的各种概率分布（可以通过利用语言模型进行极大似然估计，利用EM算法进行参数优化等得到），根据viterbi解码算法，很容易就得到了切分结果。随着语言模型的广泛应用，以及各种learning算法的发展，该方法也具有广泛的应用场景。深入阅读的可以参考以下两篇文章：《Self-supervised Chinese word segmentation》，《unsupervised query segmentation using generative language models and wikipedia》。<br />
现在说说该方法的不足：1. 在计算序列<a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-18-40-271322563236.jpg&amp;type=image%2Fjpeg&amp;width=117&amp;height=33"><img class="alignleft size-full wp-image-1390" title="2011-11-29 18-40-27" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-18-40-271322563236.jpg&amp;type=image%2Fjpeg&amp;width=117&amp;height=33" alt="" width="117" height="33" /></a>的概率时，我们依据markov假设，即：当前状态仅仅和前面一个状态相关。而在我们实际应用中，当前的状态可以和前面状态有关系，也可以和前面的前面状态有关系，也可以不和前面的状态有关系等等(这里面的是否有关系是在一定阀值条件下说的)。2. 在估算词典词之间的概率分布时，EM作为一个常用的算法也有自身的不足。</p>
<p style="padding-left: 60px;"><strong>2.2. </strong><strong>条件随机场模型(Conditional Random Fields )</strong></p>
<p style="padding-left: 90px;">CRFs是一个无向图模型，它的目标是寻在在条件概率最大情况下的一种组合[Conditional Random Fields: Probabilistic Models for Segmenting and Labeling Sequence Data.]。在NLP技术领域中主要用于文本标注，应用场景主要为：分词（标注字的词位信息，由字构词），词性标注（Pos-Tagging，标注分词的词性，例如：名词，动词，助词），命名实体识别（Named Entities Recognition,识别人名，地名，机构名，商品名等具有一定内在规律的实体名词）。</p>
<p style="padding-left: 90px;">它把切分的过程看作一个标注的过程，即对一个观察序列中的token进行标注，比如标记为4中状态:词首(Begin)， 词中间部分(Middle)，词尾(End)和单独存在(Single)。对于一个输入序列<a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-18-42-291322563358.jpg&amp;type=image%2Fjpeg&amp;width=124&amp;height=28"><img class="alignleft size-full wp-image-1391" title="2011-11-29 18-42-29" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-18-42-291322563358.jpg&amp;type=image%2Fjpeg&amp;width=124&amp;height=28" alt="" width="124" height="28" /></a>其标注序列为<a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-18-43-051322563397.jpg&amp;type=image%2Fjpeg&amp;width=119&amp;height=31"><img class="alignleft size-full wp-image-1392" title="2011-11-29 18-43-05" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-18-43-051322563397.jpg&amp;type=image%2Fjpeg&amp;width=119&amp;height=31" alt="" width="119" height="31" /></a>在输入序列下的条件概率：<br />
<a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-18-43-451322563438.jpg&amp;type=image%2Fjpeg&amp;width=353&amp;height=72"><img class="aligncenter size-full wp-image-1393" title="2011-11-29 18-43-45" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-18-43-451322563438.jpg&amp;type=image%2Fjpeg&amp;width=353&amp;height=72" alt="" width="353" height="72" /></a><br />
其中Z为归一化函数，它等于所有可行标注序列条件概率的总和。<a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-18-44-261322563477.jpg&amp;type=image%2Fjpeg&amp;width=130&amp;height=32"><img class="alignleft size-full wp-image-1394" title="2011-11-29 18-44-26" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-18-44-261322563477.jpg&amp;type=image%2Fjpeg&amp;width=130&amp;height=32" alt="" width="130" height="32" /></a>为特征函数(feature function)，<a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-18-45-581322563567.jpg&amp;type=image%2Fjpeg&amp;width=22&amp;height=36"><img class="alignleft size-full wp-image-1395" title="2011-11-29 18-45-58" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-18-45-581322563567.jpg&amp;type=image%2Fjpeg&amp;width=22&amp;height=36" alt="" width="22" height="36" /></a>是其对应的权重。该特征函数用来在观察序列T下，当前计算阶段i下状态序列<a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-18-46-321322563607.jpg&amp;type=image%2Fjpeg&amp;width=71&amp;height=26"><img class="alignleft size-full wp-image-1396" title="2011-11-29 18-46-32" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-18-46-321322563607.jpg&amp;type=image%2Fjpeg&amp;width=71&amp;height=26" alt="" width="71" height="26" /></a>的情况。我们的目标是找到一个标注序列，使得上式达到最大值：<br />
<a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-18-48-341322563730.jpg&amp;type=image%2Fjpeg&amp;width=184&amp;height=49"><img class="aligncenter size-full wp-image-1397" title="2011-11-29 18-48-34" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-18-48-341322563730.jpg&amp;type=image%2Fjpeg&amp;width=184&amp;height=49" alt="" width="184" height="49" /></a><br />
如果我们有了可以用于计算的各种概率分布，利用viterbi算法，不难获得该序列的标注情况。再根据每个token的标注状态(BMES)来进行切分。具体可以阅读这篇文章《Chinese Segmentation and New Word Detection using Conditional Random Fields》。</p>
<p style="padding-left: 90px;">CRFs模型中文分词在一些封闭测试的语料中达到了非常可观的准确率，在工业应用中，效果也是可圈可点。同时在中文分词中，CRF分词是基于汉字的构词法进行，它可以有效地识别具有结构特征的新词，而不在乎这个“新词”是否在互联网中出现过。CRF在分词里面有2个明显的不足：性能和代价。性能是在解码阶段需要耗费大量的计算量，代价是指，作为有监督学习，CRF模型的训练需要大量的标注语料，同时，互联网语料急剧增长和变化，CRF模型的更新也需要较多的人力。在后续我们会讨论如何有效地将CRF模型融入工业化切分应用中以及如何引入语言模型来更新统计切分模型。<br />
除此，CRF模型在中文分词上取得不错的成绩，能否直接移植到其它语言呢？语言特征和training语料间有怎样的关系时整个切分算法才能出色的运行？有兴趣的读者可以思考这个问题。<br />
在该节中，我们主要介绍了各种切分模型的原理，分析了他们的应用场景。在下面一节中，我们介绍一种新型的算法，它利用语言模型进行切分，它的假设是：如果两个词应该切分在一起，那么这两个词，在一定的条件下分布是接近的。</p>
<h2>基于统计语言模型的无监督切分</h2>
<p style="padding-left: 30px;">在本节中，我们会介绍一种新型的无监督切分算法(Query Segmentation Based on Eigenspace Similarity)，相对于基于HMM的方法来讲，他充分考虑了切分片段整体信息，并且在后面的章节中，我们会介绍该方法有很好的拓展性，从各个角度来讲都符合我们在最初提出的切分难点的解决。</p>
<p style="padding-left: 30px;">在介绍具体算法之前，我们说说该算法的假设。对于A B两个token，他们可以切分在一起就说明他俩在一定条件下紧挨着出现，换句话说，在一定条件下(即整个待切分句子的上下文环境)，他俩的数据分布是比较相似的，如果我们可以获得其数据分布(vector)，再计算这两个vector之间的相似度，就可以决定这两个token是否应该合并在一起。这个方法听起来似乎和互信息(Mutual Information)有点像，但是互信息并没有考虑我们前面说的一定条件下，不过也有一些工作针对MI这点引入了cosine of point-wise mutual information。即便考虑了上下文信息，还有个问题比较棘手：判断两个token是否应该合并在一起的阀值应该是多少？在有些工作中[Generating query substitutions]，这个阀值被经验地设置为一个定值，事实上，这样做可能是不大合理的。在各种<span style="text-decoration: line-through;">各种</span>下，我们构建了这样一个切分的上下文环境，并且巧妙地把统计特征投影到其主特征空间(principal eigenspace，在线性代数中，特征空间是由一个矩阵的所有特征向量张成的空间，主特征空间是有该矩阵的主要特征向量张成的空间。相比较特征空间，主特征空间可以覆盖特征空间大部分信息，并且可以辅助相关应用进行有效的降维、除噪和数据变换等)，计算相似度，配合主特征空间的维度进行切分。</p>
<p style="padding-left: 60px;">1.算法流程：</p>
<p style="padding-left: 90px;">我们以一个例子来讲述该算法是如何工作的，有兴趣的读者可以阅读该文[Query Segmentation Based on Eigenspace Similarity]。（点击查看大图）</p>
<p><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-19-21-541322565857.jpg&amp;type=image%2Fjpeg&amp;width=732&amp;height=1888"><img class="aligncenter size-full wp-image-1399" title="2011-11-29 19-21-54" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-19-21-541322565857.jpg&amp;type=image%2Fjpeg&amp;width=732&amp;height=1888" alt="" width="732" height="1888" /></a></p>
<p style="padding-left: 30px;">2.算法分析：</p>
<p style="padding-left: 60px;">该算法一个核心的要点为主特征空间维度k的确定。换个角度讲，对于给定一个待处理串，如果事先知道切分的片段数，利用一些简单的统计策略如MI已经可以较好的做切分。</p>
<p style="padding-left: 60px;">关于参数k的确定，论文中给出一种简单的判断方法。这方面也有相关的一些研究方法，有兴趣可以深入阅读谱聚类(spectral clustering)以及Principal Component Analysis, Springer (2002)一书中的第六章” Choosing a Subset of Principal Components or Variables”。</p>
<p style="padding-left: 60px;">该切分算法根据数据分布入手，由切分片段特征展开假设，通过基本token在一定相关语义下统计分布而进行切分。相对基于EM/HMM等模型的无监督切分算法，该方法一个明显的优点是充分考虑了整个切分片段的信息，而不是相邻token之间的统计量；同时，该方法通过空间变换等手段，有效的进行数据除燥等策略，从而是数据分布更趋于真实情况。</p>
<p style="padding-left: 60px;">同CRF等有监督学习相比，该方法的输入为ngram语言模型，不需人工标注数据 ，同时本方法可以识别新词，这在互联网应用中极具优势。同时针对不同语言不通领域，我们只要提供足够可靠的语言模型就可以在很大程度上解决他们的切分需求。</p>
<p style="padding-left: 60px;">当然本方法(or无监督切分方法)在切分的准确率上和基于有监督的模型相比仍有差距，我们在下节会阐述这个问题，并给出一个我们勾画的切分体系。</p>
<h2>如何打造一个好的切分框架？</h2>
<p style="padding-left: 30px;">简单的说：1. 词典是需要的，并且有有效的手段源源不断的更新词典词，在不同的应用需求下，这些词典词在切分体系中的位置和作用可能不一样。2. 强大的语言模型是需要的。原因是：如果A B两个token应该切分在一起，那么“AB”这个组合就应该在互联网中大量出现。3. 人工标注的数据也是必要的，这是因为，切分作为人们对句子一个主观的认识，这个和数据在语料中的分布不是完全一致的。</p>
<p style="padding-left: 30px;">先说说有监督和无监督两种方法的差异。如果无监督的效果可以赶得上有监督的方法，那么有监督方法就可以彻底和分词说拜拜了。那么无监督方法切分效果瓶颈来源于哪里呢？这里用一个例子来解释。很多用户在在遇到不认识的字时候，会通过如下手段去搜索学习，query“**头上加一??”，如“旦头上加一横是什么字”等等，由于无监督学习是根据数据的分布出发，在这样的case中“头上加一”或者“加一”就会被切分出来。事实上，这种切分方式和人们的认识是不一样的。这是一个极端的例子，数据分布和人们主观认识不一致有很多因素。</p>
<p style="padding-left: 30px;">我们在使用有监督的方法解决问题是，主要着眼点在考虑local consistency，也就是说，所有的工作是基于已经标注的数据进行开展，标注语料决定了最后的算法效果；而无监督方法更多的是从全局的数据分布(global consistency)来看，如果某个需求下数据具备全局的结构特征，则无监督模型也可以很好的对其进行解决。</p>
<p style="padding-left: 30px;">那么在这里我们就有个设想，引入部分的标注数据来改变数据的原始分布，最后优化切分效果。半监督学习(semi-supervised learning)的引入即可以在很大程度上提升无监督切分的效果。我们之所以引入介绍无监督切分算法，是因为该方法可以和现有的半监督学习算法相结合。</p>
<p style="padding-left: 30px;">结合上面所说的，描述一下我认为一个好的切分体系应该是什么样子。</p>
<p style="padding-left: 30px;"><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322566065.jpg&amp;type=image%2Fjpeg&amp;width=873&amp;height=518"><img class="aligncenter size-full wp-image-1401" title="1" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322566065.jpg&amp;type=image%2Fjpeg&amp;width=873&amp;height=518" alt="" width="873" height="518" /></a></p>
<p style="padding-left: 30px;">1.我们需要词典。词典的来源有很多，比如专名挖掘(NE mining), 词组挖掘(Phrase Extraction)。同时我们还要有个模块对这些资源进行不同程度的加工，最后提供一个词典给正向最大匹配(FMM)切分使用。我们会在后续的章节中，介绍新词挖掘，资源抽取等技术。</p>
<p style="padding-left: 30px;">2.我们之所以使用FMM是因为本算法可以完美地处理很多待处理语句。只有在FMM无法解决的时候，我们才会引入统计切分算法。这个时候，我们需要一个trigger来负责这件事，就如上图中的Ambiguity Detection模块。这个模块可以把一个句子分成2种形式，可以通过FMM处理的简单句，需要统计模型处理的复杂句。对于复杂句采用统计切分，最后把二者结果merge起来。</p>
<p style="padding-left: 30px;">3.统计模型的方法有很多，在这里自然要推荐文中所述的模型。该模块输入为ngram语言模型。统计切分算法的优化过程是引入标注数据来改变ngram中token之间的分布。在这里，我们推荐使用metric learning的方法，直接对frequencies matrix进行改动。切分的bad case也可以通过标注数据来修复。</p>
<p style="padding-left: 30px;">在这种体系下，我们很好地解决了切分过程中存在的一致性、歧义处理、新词、可持续提升、可扩展性、性能等因素。</p>
<p style="padding-left: 30px;">切分作为自然语言处理中一个最底层的工作，有大量的学者在这方面进行不断的研究。在中文分词方面，清华大学的自然语言处理同学收集了这方面的论文: <a href="http://nlp.csai.tsinghua.edu.cn/~zkx/cws/bib.html">http://nlp.csai.tsinghua.edu.cn/~zkx/cws/bib.html</a>，有兴趣的读者可以根据需要进行相应的扩展阅读。</p>
<p style="padding-left: 30px;">百度自然语言处理在中文分词上做了相当多的工作，在后续，我们会从切词中遇到的技术和资源进行展开讨论。</p>
<p style="text-align: right;">by Super.Jiju</p>
]]></content:encoded>
			<wfw:commentRss>http://stblog.baidu-tech.com/?feed=rss2&amp;p=1383</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>iOS内存暴增问题追查与使用陷阱</title>
		<link>http://stblog.baidu-tech.com/?p=1371</link>
		<comments>http://stblog.baidu-tech.com/?p=1371#comments</comments>
		<pubDate>Tue, 29 Nov 2011 08:44:57 +0000</pubDate>
		<dc:creator>editor</dc:creator>
				<category><![CDATA[编程技术]]></category>
		<category><![CDATA[贴吧技术]]></category>
		<category><![CDATA[autorelease]]></category>
		<category><![CDATA[内存暴增]]></category>
		<category><![CDATA[内存泄漏]]></category>

		<guid isPermaLink="false">http://stblog.baidu-tech.com/?p=1371</guid>
		<description><![CDATA[iOS平台的内存使用引用计数的机制，并且引入了半自动释放机制；这种使用上的多样性，导致开发者在内存使用上非常容易出现内存泄漏和内存莫名的增长情况； 本文会介绍iOS平台的内存使用原则与使用陷阱； 深度剖析autorelease机制；低内存报警后的处理流程；并结合自身实例介绍内存暴增的问题追查记录以及相关工具的使用情况；
TAG 
内存暴增，内存泄漏，autorelease；内存报警；

iOS平台内存常见问题
作为iOS平台的开发者，是否曾经为内存问题而苦恼过？内存莫名的持续增长，程序莫名的crash，难以发现的内存泄漏，这些都是iOS平台内存相关的常见问题；本文将会详细介绍iOS平台的内存管理机制，autorelease机制和内存的使用陷阱，这些将会解决iOS平台内存上的大部分问题，提高了程序的稳定性；
1 iOS平台内存管理介绍
iOS平台的内存管理采用引用计数的机制；当创建一个对象时使用alloc或者allWithZone方法时，引用计数就会+1；当释放对象使用release方法时，引用计数就是-1；这就意味着每一个对象都会跟踪有多少其他对象引用它，一旦引用计数为0，该对象的内存就会被释放掉；另外，iOS也提供了一种延时释放的机制AutoRelease，以这种方式申请的内存，开发者无需手动释放，系统会在某一时机释放该内存； 由于iOS平台的这种内存管理的多样性，导致开发者在内存使用上很容易出现内存泄漏或者程序莫名崩溃的情况，本文会详细介绍iOS平台内存的使用规范与技巧以及如何利用工具避免或者发现问题；
下图是内存从申请到释放的一个完整示例：


2 iOS平台内存使用原则
2.1 对象的所有权与销毁
2.1.1 谁创建，谁释放；
如果是以alloc，new或者copy，mutableCopy创建的对象，则必须调用release或者autorelease方法释放内存；
如果没有释放，则导致内存泄漏！
2.1.2 谁retain，谁释放；
如果对一个对象发送 retain消息，其引用计数会+1，则使用完必须发送release或者autorelease方法释放内存或恢复引用计数；
如果没有释放，则导致内存泄漏！
2.1.3 没创建且没retain，别释放；
不要释放那些不是自己alloc或者retain的对象，否则程序会crash；
不要释放autorelease的对象，否则程序会crash；
2.2 对象的深拷贝与浅拷贝
一般来说，复制一个对象包括创建一个新的实例，并以原始对象中的值初始化这个新的实例。复制非指针型实例变量的值很简单，比如布尔，整数和浮点数。复制指 针型实例变量有两种方法。一种方法称为浅拷贝，即将原始对象的指针值复制到副本中。因此，原始对象和副本共享引用数据。另一种方法称为深拷贝，即复制指针 所引用的数据，并将其赋给副本的实例变量。
2.2.1 深拷贝
深拷贝的流程是 先创建一个新的对象且引用计数为1，并用旧对象的值初始化这个新对象；
ClassA* objA = [[ClassA alloc] init];
ClassA* objB = [objA copy];
objB是一个新对象，引用计数为1，且objB的数据等同objA的数据；
注意： objB需要释放，否则会引起内存泄漏！
2.2.2 浅拷贝
浅拷贝的流程是，无需引入新的对象，把原有对象的引用计数+1即可
ClassA* objA = [[ClassA alloc] init];
ClassA* objB = [objA retain];
注意： objB需要释放，恢复objA的引用计数，否则会引起内存泄漏！
2.3对象的存取方法
2.3.1 属性声明和实现
变量声明的常用属性类型包括readonly，assign，retain和copy；且系统会自动为声明了属性的变量生成set和get函数；
readonly属性： 只能读，不能写；
assign属性： 是默认属性，直接赋值，没有任何保留与释放问题；
retain属性： 会增加原有对象的引用计数并且在赋值前会释放原有对象，然后在进行赋值；
copy属性：  会复制原有对象，并在赋值前释放原有对象，然后在进行赋值；
2.3.2 使用属性声明可能带来的隐患
当一个非指针变量使用retain（或者copy）这个属性时，尽量不要显性的release这个变量；直接给这个变量置空即可；否则容易产生过度释放，导致程序crash； 例如：
ClassA类的strName是NSString* 类型的变量且声明的属性为retain；
ClassA.strName = nil;  /* 释放原有对象且对此对象赋值为空 */
[ClassA.strName release]; /* strName内存可能已经被释放过了，将导致程序crash */
Assign这个属性一般是非指针变量（布尔类型，整形等）时用这个类型；属于直接赋值型，不需要考虑内存的保留与释放；
如果一个指针类型的变量使用assign类型的属性，有可能引用已经释放的变量；导致程序crash； [...]]]></description>
			<content:encoded><![CDATA[<p>iOS平台的内存使用引用计数的机制，并且引入了半自动释放机制；这种使用上的多样性，导致开发者在内存使用上非常容易出现内存泄漏和内存莫名的增长情况； 本文会介绍iOS平台的内存使用原则与使用陷阱； 深度剖析autorelease机制；低内存报警后的处理流程；并结合自身实例介绍内存暴增的问题追查记录以及相关工具的使用情况；</p>
<p><strong>TAG</strong><strong> </strong></p>
<p>内存暴增，内存泄漏，autorelease；内存报警；</p>
<p><span id="more-1371"></span></p>
<h1 style="font-size: 20px;">iOS平台内存常见问题</h1>
<p style="padding-left: 30px;">作为iOS平台的开发者，是否曾经为内存问题而苦恼过？内存莫名的持续增长，程序莫名的crash，难以发现的内存泄漏，这些都是iOS平台内存相关的常见问题；本文将会详细介绍iOS平台的内存管理机制，autorelease机制和内存的使用陷阱，这些将会解决iOS平台内存上的大部分问题，提高了程序的稳定性；</p>
<h1 style="font-size: 20px;">1 iOS平台内存管理介绍</h1>
<p style="padding-left: 30px;">iOS平台的内存管理采用引用计数的机制；当创建一个对象时使用alloc或者allWithZone方法时，引用计数就会<strong>+1</strong>；当释放对象使用release方法时，引用计数就是<strong>-1</strong>；这就意味着每一个对象都会跟踪有多少其他对象引用它，一旦引用计数为0，该对象的内存就会被释放掉；另外，iOS也提供了一种延时释放的机制AutoRelease，以这种方式申请的内存，开发者无需手动释放，系统会在某一时机释放该内存； 由于iOS平台的这种内存管理的多样性，导致开发者在内存使用上很容易出现内存泄漏或者程序莫名崩溃的情况，本文会详细介绍iOS平台内存的使用规范与技巧以及如何利用工具避免或者发现问题；</p>
<p style="padding-left: 30px;">下图是内存从申请到释放的一个完整示例：</p>
<p><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322553861.jpg&amp;type=image%2Fjpeg&amp;width=558&amp;height=310"><img class="aligncenter size-full wp-image-1372" title="1" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322553861.jpg&amp;type=image%2Fjpeg&amp;width=558&amp;height=310" alt="" width="558" height="310" /></a></p>
<p><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-16-05-021322553924.jpg&amp;type=image%2Fjpeg&amp;width=595&amp;height=200"><img class="aligncenter size-full wp-image-1373" title="2011-11-29 16-05-02" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-16-05-021322553924.jpg&amp;type=image%2Fjpeg&amp;width=595&amp;height=200" alt="" width="595" height="200" /></a></p>
<h1 style="font-size: 20px;">2 iOS平台内存使用原则</h1>
<h2 style="padding-left: 30px;">2.1 对象的所有权与销毁</h2>
<h3 style="padding-left: 60px;">2.1.1 谁创建，谁释放；</h3>
<p style="padding-left: 90px;">如果是以alloc，new或者copy，mutableCopy创建的对象，则必须调用release或者autorelease方法释放内存；</p>
<p style="padding-left: 90px;"><span style="color: #ff0000;">如果没有释放，则导致内存泄漏！</span></p>
<h3 style="padding-left: 60px;">2.1.2 谁retain，谁释放；</h3>
<p style="padding-left: 90px;">如果对一个对象发送 retain消息，其引用计数会+1，则使用完必须发送release或者autorelease方法释放内存或恢复引用计数；</p>
<p style="padding-left: 90px;"><span style="color: #ff0000;">如果没有释放，则导致内存泄漏！</span></p>
<h3 style="padding-left: 60px;">2.1.3 没创建且没retain，别释放；</h3>
<p style="padding-left: 90px;">不要释放那些不是自己alloc或者retain的对象，<span style="color: #ff0000;">否则程序会</span><strong><span style="color: #ff0000;">crash</span></strong>；</p>
<p style="padding-left: 90px;">不要释放autorelease的对象，<span style="color: #ff0000;">否则程序会</span><strong><span style="color: #ff0000;">crash</span></strong>；</p>
<h2 style="padding-left: 30px;">2.2 对象的深拷贝与浅拷贝</h2>
<p style="padding-left: 60px;">一般来说，复制一个对象包括创建一个新的实例，并以原始对象中的值初始化这个新的实例。复制非指针型实例变量的值很简单，比如布尔，整数和浮点数。复制指 针型实例变量有两种方法。一种方法称为浅拷贝，即将原始对象的指针值复制到副本中。因此，原始对象和副本共享引用数据。另一种方法称为深拷贝，即复制指针 所引用的数据，并将其赋给副本的实例变量。</p>
<h3 style="padding-left: 60px;">2.2.1 深拷贝</h3>
<p style="padding-left: 90px;">深拷贝的流程是 先创建一个<strong>新</strong>的对象且引用计数为1，并用旧对象的值初始化这个新对象；</p>
<p style="padding-left: 90px;">ClassA* objA = [[ClassA alloc] init];</p>
<p style="padding-left: 90px;">ClassA* objB = [objA <strong>copy</strong>];</p>
<p style="padding-left: 90px;">objB是一个新对象，引用计数为1，且objB的数据等同objA的数据；</p>
<p style="padding-left: 90px;">注意：<span style="color: #ff0000;"> objB需要释放，否则会引起内存泄漏！</span></p>
<h3 style="padding-left: 60px;">2.2.2 浅拷贝</h3>
<p style="padding-left: 90px;">浅拷贝的流程是，无需引入新的对象，把原有对象的引用计数<strong>+1</strong>即可</p>
<p style="padding-left: 90px;">ClassA* objA = [[ClassA alloc] init];</p>
<p style="padding-left: 90px;">ClassA* objB = [objA <strong>retain</strong>];</p>
<p style="padding-left: 90px;">注意： <span style="color: #ff0000;">objB需要释放，恢复objA的引用计数，否则会引起内存泄漏！</span></p>
<h2 style="padding-left: 30px;">2.3对象的存取方法</h2>
<h3 style="padding-left: 60px;">2.3.1 属性声明和实现</h3>
<p style="padding-left: 90px;">变量声明的常用属性类型包括readonly，assign，retain和copy；且系统会<strong>自动</strong>为声明了属性的变量生成set和get函数；</p>
<p style="padding-left: 90px;"><strong>readonly</strong>属性： 只能读，不能写；</p>
<p style="padding-left: 90px;"><strong>assign</strong>属性： 是默认属性，直接赋值，没有任何保留与释放问题；</p>
<p style="padding-left: 90px;"><strong>retain</strong>属性： 会增加原有对象的引用计数并且在赋值前会释放原有对象，然后在进行赋值；</p>
<p style="padding-left: 90px;"><strong>copy</strong>属性：  会复制原有对象，并在赋值前释放原有对象，然后在进行赋值；</p>
<h3 style="padding-left: 60px;">2.3.2 使用属性声明可能带来的隐患</h3>
<p style="padding-left: 90px;">当一个非指针变量使用retain（或者copy）这个属性时，尽量不要显性的release这个变量；直接给这个变量置空即可；否则容易产生过度释放，导致程序crash； 例如：</p>
<p style="padding-left: 90px;">ClassA类的strName是NSString* 类型的变量且声明的属性为retain；</p>
<p style="padding-left: 90px;">ClassA.strName = nil;  /* 释放原有对象且对此对象赋值为空 */</p>
<p style="padding-left: 90px;">[ClassA.strName release]; /* strName内存可能已经被释放过了，<span style="color: #ff0000;">将导致程序crash</span> */</p>
<p style="padding-left: 90px;">Assign这个属性一般是非指针变量（布尔类型，整形等）时用这个类型；属于直接赋值型，不需要考虑内存的保留与释放；</p>
<p style="padding-left: 90px;">如果一个指针类型的变量使用assign类型的属性，有可能引用已经释放的变量；导致程序crash； 例如：</p>
<p style="padding-left: 90px;">ClassB* obj =[[[ClassB alloc] init] <strong>autorelease</strong>];</p>
<p style="padding-left: 90px;">……</p>
<p style="padding-left: 90px;">ClassA.strName = obj; /* strName 指向obj的内存地址*/</p>
<p style="padding-left: 90px;">后续在使用ClassA.strName的时候， 因为obj是autorelease的，可能obj的内存已经被回收；<span style="color: #ff0000;">导致引用无效内存，程序crash；</span></p>
<h1 style="font-size: 20px;">3iOS平台AutoRelease机制</h1>
<h2 style="padding-left: 30px;">3.1 自动释放池的常见问题</h2>
<p style="padding-left: 60px;">大家在开发iOS程序的时候，是否遇到过在列表滑动的情况内存莫名的增长，频繁访问图片的时候内存莫名的增长，频繁的打开和关闭数据库的时候内存莫名的增长…… 这些都是拜iOS的autorelease机制所赐；具体分析如下：</p>
<p style="padding-left: 60px;">1: 滑动列表的时候，内存出现莫名的增长，原因可能有如下可能：</p>
<p style="padding-left: 90px;">1：没有使用UITableView的reuse机制； 导致每显示一个cell都用autorelease的方式重新alloc一次；<span style="color: #ff0000;"> 导致cell的内存不断的增加</span>；</p>
<p style="padding-left: 90px;">2：每个cell会显示一个单独的UIView， 在UIView发生内存泄漏，<span style="color: #ff0000;">导致cell的内存不断增长</span>；</p>
<p style="padding-left: 60px;">2: 频繁访问图片的时候，内存莫名的增长；</p>
<p style="padding-left: 90px;">频繁的访问网络图片，导致iOS内部API，会不断的分配autorelease方式的buffer来处理图片的解码与显示； 利用图片cache可以缓解一下此问题；</p>
<p style="padding-left: 90px;">
<p style="padding-left: 60px;">3: 频繁打开和关闭SQLite，导致内存不断的增长；</p>
<p style="padding-left: 90px;">在进行SQLite频繁打开和关闭操作，而且读写的数据buffer较大，那么SQLite在每次打开与关闭的时候，都会利用autorelease的方式分配51K的内存； 如果访问次数很多，内存马上就会顶到几十兆，甚至上百兆！ 所以针对频繁的读写数据库且数据buffer较大的情况，<span style="color: #ff0000;"> 可以设置SQLite的长连接方式；避免频繁的打开和关闭数据库</span>；</p>
<h2 style="padding-left: 30px;">3.2 自动释放池的概念</h2>
<p style="padding-left: 60px;">NSAutoreleasePool内部包含一个数组（NSMutableArray），用来保存声名为autorelease的所有对象。如果一个对象声明为autorelease，系统所做的工作就是把这个对象加入到这个数组中去。</p>
<p style="padding-left: 60px;">ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retain count = 1，把此对象加入autorelease pool中</p>
<p style="padding-left: 60px;">NSAutoreleasePool自身在销毁的时候，会遍历一遍这个数组，release数组中的每个成员。如果此时数组中成员的retain count为1，那么release之后，retain count为0，对象正式被销毁。如果此时数组中成员的retain count大于1，那么release之后，retain count大于0，此对象依然没有被销毁，内存泄露。</p>
<h2 style="padding-left: 30px;">3.3 自动释放池的作用域与嵌套</h2>
<p style="padding-left: 60px;"><strong>AutoreleasePool</strong><strong>是可以嵌套使用的！</strong><br />
池是被嵌套的，嵌套的结果是个栈，同一线程只有当前栈顶pool实例是可用的：</p>
<p style="padding-left: 60px;"><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-16-14-461322554496.jpg&amp;type=image%2Fjpeg&amp;width=197&amp;height=165"><img class="aligncenter size-full wp-image-1374" title="2011-11-29 16-14-46" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-16-14-461322554496.jpg&amp;type=image%2Fjpeg&amp;width=197&amp;height=165" alt="" width="197" height="165" /></a></p>
<p style="padding-left: 60px;">当短生命周期内，比如一个循环中，会产生大量的临时内存，可以创建一个临时的autorelease pool，这样可以达到快速回收内存的目的；</p>
<h2 style="padding-left: 30px;">3.4 自动施放池的手动创建与自动创建</h2>
<h3 style="padding-left: 60px;">3.4.1 需要手动创建自动释放池</h3>
<h3 style="padding-left: 90px;"><span style="font-weight: normal; font-size: 13px;">●如果你正在编写一个不是基于Application Kit的程序，比如命令行工具，则没有对自动释放池的内置支持；你必须自己创建它们。</span></h3>
<h3 style="padding-left: 90px;"><span style="font-weight: normal; font-size: 13px;"> </span><span style="font-weight: normal; font-size: 13px;">●如果你生成了一个从属线程，则一旦该线程开始执行，你必须立即创建你自己的自动释放池；否则，你将会泄漏对象。</span></h3>
<h3 style="padding-left: 90px;"><span style="font-weight: normal; font-size: 13px;"> </span><span style="font-weight: normal; font-size: 13px;">●如果你编写了一个循环，其中创建了许多临时对象，你可以在循环内部创建一个自动释放池，以便在下次迭代之前销毁这些对象。这可以帮助减少应用程序的最大内存占用量。</span></h3>
<h3 style="padding-left: 60px;">3.4.2 系统自动创建自动释放池</h3>
<p style="padding-left: 60px;">Application Kit会在一个事件周期（或事件循环迭代）的开端—比如鼠标按下事件—自动创建一个自动释放池，并且在事件周期的结尾释放它.</p>
<h1 style="font-size: 20px;">4 iOS平台内存使用陷阱</h1>
<h2 style="padding-left: 30px;">4.1 重复释放</h2>
<p style="padding-left: 60px;">在前文已经提到，不要释放不是自己创建的对象；</p>
<p style="padding-left: 60px;"><span style="color: #ff0000;">释放自己的autorelease对象，app会crash；</span></p>
<p style="padding-left: 60px;"><span style="color: #ff0000;">释放系统的autorelease对象，app会crash；</span></p>
<h2 style="padding-left: 30px;">4.2 循环引用</h2>
<p style="padding-left: 30px;"><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-16-37-201322555851.jpg&amp;type=image%2Fjpeg&amp;width=427&amp;height=175"><img class="aligncenter size-full wp-image-1375" title="2011-11-29 16-37-20" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-16-37-201322555851.jpg&amp;type=image%2Fjpeg&amp;width=427&amp;height=175" alt="" width="427" height="175" /></a></p>
<p style="padding-left: 60px;">循环引用，容易产生野引用，内存无法回收，最终导致内存泄漏！可以通过弱引用的方式来打破循环引用链；所谓的弱引用就是不需要retain，直接赋值的方式，这样的话，可以避免循环引用的问题，但是需要注意的是，避免重复释放的问题；</p>
<h1 style="font-size: 20px;">5 iOS平台内存报警机制</h1>
<p style="padding-left: 30px;">由于iOS平台的内存管理机制，不支持虚拟内存，所以在内存不足的情况，不会去Ram上创建虚拟内存；所以一旦出现内存不足的情况，iOS平台会通知所有已经运行的app，不论是前台app还是后台挂起的app，都会收到 memory warning的notice；一旦app收到memory warning的notice，就应该回收占用内存较大的变量；</p>
<h2 style="padding-left: 30px;">5.1 内存报警处理流程</h2>
<p style="padding-left: 60px;">1： app收到系统发过来的memory warning的notice；</p>
<p style="padding-left: 60px;">2： app释放占用较大的内存；</p>
<p style="padding-left: 60px;">3： 系统回收此app所创建的autorelease的对象；</p>
<p style="padding-left: 60px;">4： app返回到已经打开的页面时，系统重新调用viewdidload方法，view重新加载页面数据；重新显示；</p>
<h2 style="padding-left: 30px;">5.2 内存报警测试方法</h2>
<p style="padding-left: 60px;">在Simulate上可以模拟低内存报警消息；</p>
<p style="padding-left: 60px;">iOS模拟器 -&gt; 硬件 -&gt; 模拟内存警告；</p>
<p style="padding-left: 60px;">开发者可以在模拟器上来模拟手机上的低内存报警情况，可以避免由于低内存报警引出的app的莫名crash问题；</p>
<h1 style="font-size: 20px;">6 iOS平台内存检查工具</h1>
<h2 style="padding-left: 30px;">6.1 编译和分析工具Analyze</h2>
<p style="padding-left: 60px;">iOS的分析工具可以发现编译中的warning，内存泄漏隐患，甚至还可以检查出logic上的问题；所以在自测阶段一定要解决Analyze发现的问题，可以避免出现严重的bug；</p>
<p style="padding-left: 60px;"><strong>内存泄漏隐患提示</strong>：</p>
<p style="padding-left: 60px;">Potential Leak of an object allocated on line ……</p>
<p style="padding-left: 60px;"><strong>数据赋值隐患提示</strong>：</p>
<p style="padding-left: 60px;">The left operand of …… is a garbage value;</p>
<p style="padding-left: 60px;"><strong>对象引用隐患提示</strong>：</p>
<p style="padding-left: 60px;">Reference-Counted object is used after it is released;</p>
<p style="padding-left: 60px;">
<p style="padding-left: 30px;">以上提示均比较严重，可能会引起严重问题，需要开发者密切关注！</p>
<h2 style="padding-left: 30px;">6.2 内存检测工具</h2>
<h3 style="padding-left: 60px;">6.2.1 内存泄漏检测工具—Leak</h3>
<p style="padding-left: 90px;">Leak工具可以很容易的统计所有内存泄漏的点，而且还可以显示在那个文件，哪行代码有内存泄漏，这样定位问题比较容易，也比较方面；但是Leak在统计内存泄漏的时候会把autorelease方式的内存也统计进来； 所以我们在查找内存泄漏情况的时候，可以autorelease的情况忽略掉；</p>
<p style="padding-left: 90px;">Leak工具：</p>
<p style="padding-left: 90px;"><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322555979.jpg&amp;type=image%2Fjpeg&amp;width=558&amp;height=395"><img class="aligncenter size-full wp-image-1376" title="1" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322555979.jpg&amp;type=image%2Fjpeg&amp;width=558&amp;height=395" alt="" width="558" height="395" /></a></p>
<p style="padding-left: 30px;">
<p style="padding-left: 90px;">通过Leak工具可以很快发现代码中的内存泄漏，通过工具也可以很快找到发生内存泄漏的代码段：</p>
<p style="padding-left: 90px;"><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322556011.jpg&amp;type=image%2Fjpeg&amp;width=558&amp;height=395"><img class="aligncenter size-full wp-image-1377" title="1" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322556011.jpg&amp;type=image%2Fjpeg&amp;width=558&amp;height=395" alt="" width="558" height="395" /></a></p>
<h3 style="padding-left: 60px;">6.2.2 内存猛增检测工具—Allocations</h3>
<p style="padding-left: 90px;">Allocations工具可以很容易的列出所有分配内存的点，这样我们可以按照分配内存大小来进行排序， 这样可以很容易的发现哪些点分配的内存最多，而且是持续分配，这样我们来针对性的分析这些持续分配较大内存的地方；</p>
<p style="padding-left: 90px;"><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322556055.jpg&amp;type=image%2Fjpeg&amp;width=558&amp;height=396"><img class="aligncenter size-full wp-image-1378" title="1" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322556055.jpg&amp;type=image%2Fjpeg&amp;width=558&amp;height=396" alt="" width="558" height="396" /></a></p>
<p style="padding-left: 90px;">此工具会显示出所有申请内存的地方，并统计申请的次数和大小； 从这个列表中可以找出内存申请次数最多且申请内存最大的语句；从而分析出哪些地方使用的内存最多，进而可以优化和改进；</p>
<p style="padding-left: 90px;"><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322556090.jpg&amp;type=image%2Fjpeg&amp;width=558&amp;height=108"><img class="aligncenter size-full wp-image-1379" title="1" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322556090.jpg&amp;type=image%2Fjpeg&amp;width=558&amp;height=108" alt="" width="558" height="108" /></a></p>
<p style="padding-left: 90px;">上图是按照申请内存多少来排序的，可以方便的了解哪些代码申请的内存多；</p>
<h1 style="font-size: 20px;">7 参考资料</h1>
<p style="padding-left: 30px;"><a href="http://www.cocoachina.com/bbs/read.php?tid=15963">http://www.cocoachina.com/bbs/read.php?tid=15963</a></p>
<p style="padding-left: 30px;">http://developer.apple.com/library/IOs/navigation/</p>
<p><strong> </strong></p>
<p style="text-align: right;">by lixin</p>
]]></content:encoded>
			<wfw:commentRss>http://stblog.baidu-tech.com/?feed=rss2&amp;p=1371</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>大话PHP之性能</title>
		<link>http://stblog.baidu-tech.com/?p=1343</link>
		<comments>http://stblog.baidu-tech.com/?p=1343#comments</comments>
		<pubDate>Tue, 29 Nov 2011 02:25:12 +0000</pubDate>
		<dc:creator>editor</dc:creator>
				<category><![CDATA[编程技术]]></category>
		<category><![CDATA[贴吧技术]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[性能]]></category>

		<guid isPermaLink="false">http://stblog.baidu-tech.com/?p=1343</guid>
		<description><![CDATA[1缘起
关于PHP，很多人的直观感觉是PHP是一种灵活的脚本语言，库类丰富，使用简单，安全，非常适合WEB开发，但性能低下。PHP的性能是否真的就如同大家的感觉一样的差呢？本文就是围绕这么一个话题来进行探讨的。从源码、应用场景、基准性能、对比分析等几个方面深入分析PHP之性能问题，通过真实的性能数据来说话，最终找出影响PHP模块性能的关键因素。
2从原理分析PHP性能
从原理分析PHP的性能，主要从以下几个方面：内存管理、变量、函数、运行机制、网络模型来进行分析。
2.1内存管理
类似Nginx的内存管理方式，PHP在内部也是基于内存池，并且引入内存池的生命周期概念。在内存池方面，PHP对PHP脚本和扩展的所有内存相关操作都进行了托管。对大内存和小内存的管理采用了不同的实现方式和优化，具体可以参考以下文档：http://www.laruence.com/2011/11/09/2277.html。在内存分配和回收的生命周期内，PHP采用一次初始化申请+动态扩容+内存标识回收机制，并且在每次请求结束后直接对内存池进行重新mask。

2.2变量
总所周知，PHP是一种弱变量类型的语言，所以在PHP内部，所有的PHP变量都对应成一种类型Zval，其中具体定义如下：

图一、PHP变量
在变量方面，PHP做了大量的优化工作，比如说Reference counting和copy on writer机制。这样能够保证内存使用上的优化，并且减少内存拷贝次数（请参考http://blog.xiuwz.com/2011/11/09/php-using-internal-zval/）。在数组方面，PHP内部采用高效的hashtable来实现。
2.3函数
在PHP内部，所有的PHP函数都回转化成内部的一个函数指针。比如说扩展中函数
ZEND_FUNCTION ( my_function );//类似function my_function(){}
在内部展开后就会是一个函数
void zif_my_function ( INTERNAL_FUNCTION_PARAMETERS );
void zif_my_function(
int ht,
zval * return_value,
zval * this_ptr,
int return_value_used,
zend_executor_globals * executor_globals
);
从这个角度来看，PHP函数在内部也是对应一个函数指针。
2.4运行机制
在话说PHP性能的时候，很多人都会说“C/C++是编译型，JAVA是半编译型，PHP是解释型”。也就是说PHP是先动态解析再代码运行的，所以从这个角度来看，PHP性能必然很差。
的确，从PHP脚本运行来输出，的确是一个动态解析再代码运行的过程。具体来说，PHP脚本的运行机制如下图所示：

图二、PHP运行机制
PHP的运行阶段也分成三个阶段：
●Parse。语法分析阶段。
●	Compile。编译产出opcode中间码。
●	Execute。运行，动态运行进行输出。
通过上图也可以看出，其实在PHP内部本身也是存在编译的过程。事实上，在标准的生产环境中，也都基本上利用了这个特点，比如说opcode cache工具apc、eacc、xcache等等。基于opcode cache，能到做到“PHP脚本编译一次，多次运行”的效果。从这点上，PHP就和JAVA的半编译机制非常类似。
所以，从运行机制上来看，PHP的运行模式和JAVA是非常类似的，都是先产生中间码，然后运行在不同虚拟机上。
2.5动态运行
从上面的几个分析来看，PHP在内存管理、变量、函数、运行机制等几个方面都做了大量的工作，所以从原理来看，PHP不应该存在性能问题，性能至少也应该和JAVA比较接近。
但为什么还有很多人感觉PHP慢呢？尤其是一些计算量的性能对比上，总发现PHP处理的性能相对比较低效（http://shootout.alioth.debian.org/u32/php.php）。这个时候就不得不谈PHP动态语言的特性所带来的性能问题了，由于PHP是动态运行时，所以所有的变量、函数、对象调用、作用域实现等等都是在执行阶段中才确定的。这个从根本上决定了PHP性能中很难改变的一些东西：在C/C++等能够在静态编译阶段确定的变量、函数，在PHP中需要在动态运行中确定，也就决定了PHP中间码不能直接运行而需要运行在Zend Engine上。
说到PHP变量的具体实现，又不得不说一个东西了：hashtable。Hashtable可以说在PHP灵魂之一，在PHP内部广泛用到，包含变量符号栈、函数符号栈等等都是基于hashtable的。
以PHP变量为例来说明下PHP的动态运行特点，比如说代码：
&#60;?php
$var = “hello, blog.xiuwz.com”;
?&#62;
该代码的执行结果就是在变量符号栈（是一个hashtable）中新增一个项

当要使用到该变量时候，就去变量符合栈中去查找（也就是变量调用对出了一个hash查找的过程）。
同样对于函数调用也基本上类似有一个函数符号栈（hashtable）。
其实关于动态运行的变量查找特点，在PHP的运行机制中也能看出一些。PHP代码通过解释、编译后的流程下图：

图3、PHP运行实例
从上图可以看出，PHP代码在compile之后，产出的了类符号表、函数符号表、和OPCODE。在真正执行的时候，zend Engine会根据op code去对应的符号表中进行查找，处理。
从某种程度上，在这种问题的上，很难找到解决方案。因为这是由于PHP语言的动态特性所决定的。但是在国内外也有不少的人在寻找解决方案。因为通过这样，能够从根本上完全的优化PHP。典型的列子有facebook的hiphop(https://github.com/facebook/hiphop-php)。
但所有的这种编译优化方案，都基本上是牺牲了PHP动态运行的特性。当然可以在具体的编译优化中去对动态特性做一些折中，但很难做到完完全全的兼容。
2.6网络模型
目前采用PHP的方式，比较理想和通用的模式是采用fastcgi（PHP-FPM）。Php-fpm在网络模型上比较类似nginx，采用了多进程Master+多worker的模式。Php-fpm本身是基于libevent中的epoll模型。从网络模型来看，该方式也不会和其他网络模型存在性能差异。
2.7结论
从上面分析来看，在基础的内存管理、变量、函数、运行机制、网络模型方面，PHP本身并不会存在明显的性能差异，但由于PHP的动态运行特性，决定了PHP和其他的编译型语言相比，所有的变量查找、函数运行等等都会多一些hash查找的CPU开销和额外的内存开销，至于这种开销具体有多大，可以通过后续的基准性能和对比分析得出。
因此，也可以大体看出PHP不太适合的一些场景：大量计算性任务、大数据量的运算、内存要求很严格的应用场景。如果要实现这些功能，也建议通过扩展的方式实现，然后再提供钩子函数给PHP调用。这样可以减低内部计算的变量、函数等系列开销。
3基准性能
对于PHP基准性能，目前缺少标准的数据。大多数同学都存在感性的认识，有人认为800QPS就是PHP的极限了。此外，对于框架的性能和框架对性能的影响很没有响应的权威数字。
本章节的目的是给出一个基准的参考性能指标，通过数据给大家一个直观的了解。
具体的基准性能有以下几个方面：
1、  裸PHP性能。完成基本的功能。
2、  裸框架的性能。只做最简单的路由分发，只走通核心功能。
3、  标准模块的基准性能。所谓标准模块的基准性能，是指一个具有完整服务模块功能的基准性能。
3.1环境说明
测试环境：
Uname -a
Linux db-forum-test17.db01.baidu.com 2.6.9_5-7-0-0 #1 SMP Wed Aug 12 17:35:51 CST 2009 x86_64 x86_64 x86_64 GNU/Linux
Red Hat Enterprise Linux AS release 4 (Nahant Update [...]]]></description>
			<content:encoded><![CDATA[<h2>1缘起</h2>
<p>关于PHP，很多人的直观感觉是PHP是一种灵活的脚本语言，库类丰富，使用简单，安全，非常适合WEB开发，但性能低下。PHP的性能是否真的就如同大家的感觉一样的差呢？本文就是围绕这么一个话题来进行探讨的。从源码、应用场景、基准性能、对比分析等几个方面深入分析PHP之性能问题，通过真实的性能数据来说话，最终找出影响PHP模块性能的关键因素。</p>
<h2>2从原理分析PHP性能</h2>
<p>从原理分析PHP的性能，主要从以下几个方面：内存管理、变量、函数、运行机制、网络模型来进行分析。</p>
<h3>2.1内存管理</h3>
<p>类似Nginx的内存管理方式，PHP在内部也是基于内存池，并且引入内存池的生命周期概念。在内存池方面，PHP对PHP脚本和扩展的所有内存相关操作都进行了托管。对大内存和小内存的管理采用了不同的实现方式和优化，具体可以参考以下文档：http://www.laruence.com/2011/11/09/2277.html。在内存分配和回收的生命周期内，PHP采用一次初始化申请+动态扩容+内存标识回收机制，并且在每次请求结束后直接对内存池进行重新mask。</p>
<p><span id="more-1343"></span></p>
<h3>2.2变量</h3>
<p>总所周知，PHP是一种弱变量类型的语言，所以在PHP内部，所有的PHP变量都对应成一种类型Zval，其中具体定义如下：</p>
<p style="text-align: center;"><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322468114.jpg&amp;type=image%2Fjpeg&amp;width=428&amp;height=234"><img class="alignleft size-full wp-image-1344" title="1" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322468114.jpg&amp;type=image%2Fjpeg&amp;width=428&amp;height=234" alt="" width="428" height="234" /></a></p>
<p style="text-align: center;">图一、PHP变量</p>
<p>在变量方面，PHP做了大量的优化工作，比如说Reference counting和copy on writer机制。这样能够保证内存使用上的优化，并且减少内存拷贝次数（请参考http://blog.xiuwz.com/2011/11/09/php-using-internal-zval/）。在数组方面，PHP内部采用高效的hashtable来实现。</p>
<h3>2.3函数</h3>
<p>在PHP内部，所有的PHP函数都回转化成内部的一个函数指针。比如说扩展中函数</p>
<p>ZEND_FUNCTION ( my_function );//类似function my_function(){}</p>
<p>在内部展开后就会是一个函数</p>
<p>void zif_my_function ( INTERNAL_FUNCTION_PARAMETERS );</p>
<p>void zif_my_function(</p>
<p>int ht,</p>
<p>zval * return_value,</p>
<p>zval * this_ptr,</p>
<p>int return_value_used,</p>
<p>zend_executor_globals * executor_globals</p>
<p>);</p>
<p>从这个角度来看，PHP函数在内部也是对应一个函数指针。</p>
<h3>2.4运行机制</h3>
<p>在话说PHP性能的时候，很多人都会说“C/C++是编译型，JAVA是半编译型，PHP是解释型”。也就是说PHP是先动态解析再代码运行的，所以从这个角度来看，PHP性能必然很差。</p>
<p>的确，从PHP脚本运行来输出，的确是一个动态解析再代码运行的过程。具体来说，PHP脚本的运行机制如下图所示：</p>
<p><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322468213.jpg&amp;type=image%2Fjpeg&amp;width=192&amp;height=236"><img class="aligncenter size-full wp-image-1345" title="1" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322468213.jpg&amp;type=image%2Fjpeg&amp;width=192&amp;height=236" alt="" width="192" height="236" /></a></p>
<p style="text-align: center;">图二、PHP运行机制</p>
<p>PHP的运行阶段也分成三个阶段：<br />
●Parse。语法分析阶段。<br />
●	Compile。编译产出opcode中间码。<br />
●	Execute。运行，动态运行进行输出。</p>
<p>通过上图也可以看出，其实在PHP内部本身也是存在编译的过程。事实上，在标准的生产环境中，也都基本上利用了这个特点，比如说opcode cache工具apc、eacc、xcache等等。基于opcode cache，能到做到“<strong>PHP</strong><strong>脚本编译一次，多次运行</strong>”的效果。从这点上，PHP就和JAVA的半编译机制非常类似。</p>
<p>所以，从运行机制上来看，PHP的运行模式和JAVA是非常类似的，都是先产生中间码，然后运行在不同虚拟机上。</p>
<h3>2.5动态运行</h3>
<p>从上面的几个分析来看，PHP在内存管理、变量、函数、运行机制等几个方面都做了大量的工作，所以从原理来看，<strong>PHP</strong><strong>不应该存在性能问题，性能至少也应该和JAVA</strong><strong>比较接近</strong>。</p>
<p>但为什么还有很多人感觉PHP慢呢？尤其是一些计算量的性能对比上，总发现PHP处理的性能相对比较低效（http://shootout.alioth.debian.org/u32/php.php）。这个时候就不得不谈PHP动态语言的特性所带来的性能问题了，由于PHP是动态运行时，所以所有的变量、函数、对象调用、作用域实现等等都是在执行阶段中才确定的。这个从根本上决定了PHP性能中很难改变的一些东西：<strong>在</strong><strong>C/C++</strong><strong>等能够在静态编译阶段确定的变量、函数，在PHP</strong><strong>中需要在动态运行中确定，也就决定了PHP</strong><strong>中间码不能直接运行而需要运行在Zend Engine</strong><strong>上</strong>。</p>
<p>说到PHP变量的具体实现，又不得不说一个东西了：hashtable。Hashtable可以说在PHP灵魂之一，在PHP内部广泛用到，包含变量符号栈、函数符号栈等等都是基于hashtable的。</p>
<p>以PHP变量为例来说明下PHP的动态运行特点，比如说代码：</p>
<p>&lt;?php</p>
<p>$var = “hello, blog.xiuwz.com”;</p>
<p>?&gt;</p>
<p>该代码的执行结果就是在变量符号栈（是一个hashtable）中新增一个项</p>
<p><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-15-27-531322551765.jpg&amp;type=image%2Fjpeg&amp;width=451&amp;height=25"><img class="alignleft size-full wp-image-1355" title="2011-11-29 15-27-53" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-15-27-531322551765.jpg&amp;type=image%2Fjpeg&amp;width=451&amp;height=25" alt="" width="451" height="25" /></a></p>
<p>当要使用到该变量时候，就去变量符合栈中去查找（也就是变量调用对出了一个hash查找的过程）。</p>
<p>同样对于函数调用也基本上类似有一个函数符号栈（hashtable）。</p>
<p>其实关于动态运行的变量查找特点，在PHP的运行机制中也能看出一些。PHP代码通过解释、编译后的流程下图：<br />
<a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322551808.jpg&amp;type=image%2Fjpeg&amp;width=558&amp;height=422"><img class="alignleft size-full wp-image-1356" title="1" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322551808.jpg&amp;type=image%2Fjpeg&amp;width=558&amp;height=422" alt="" width="558" height="422" /></a></p>
<p style="text-align: center;">图3、PHP运行实例</p>
<p>从上图可以看出，PHP代码在compile之后，产出的了类符号表、函数符号表、和OPCODE。在真正执行的时候，zend Engine会根据op code去对应的符号表中进行查找，处理。</p>
<p>从某种程度上，在这种问题的上，很难找到解决方案。因为这是由于PHP语言的动态特性所决定的。但是在国内外也有不少的人在寻找解决方案。因为通过这样，能够从根本上完全的优化PHP。典型的列子有facebook的hiphop(<a href="https://github.com/facebook/hiphop-php">https://github.com/facebook/hiphop-php</a>)。</p>
<p>但所有的这种编译优化方案，都基本上是牺牲了PHP动态运行的特性。当然可以在具体的编译优化中去对动态特性做一些折中，但很难做到完完全全的兼容。</p>
<h3>2.6网络模型</h3>
<p>目前采用PHP的方式，比较理想和通用的模式是采用fastcgi（PHP-FPM）。Php-fpm在网络模型上比较类似nginx，采用了多进程Master+多worker的模式。Php-fpm本身是基于libevent中的epoll模型。从网络模型来看，该方式也不会和其他网络模型存在性能差异。</p>
<h3>2.7结论</h3>
<p>从上面分析来看，在基础的内存管理、变量、函数、运行机制、网络模型方面，PHP本身并不会存在明显的性能差异，但由于PHP的动态运行特性，决定了PHP和其他的编译型语言相比，所有的变量查找、函数运行等等都会多一些hash查找的CPU开销和额外的内存开销，至于这种开销具体有多大，可以通过后续的基准性能和对比分析得出。</p>
<p>因此，也可以大体看出PHP不太适合的一些场景：大量计算性任务、大数据量的运算、内存要求很严格的应用场景。如果要实现这些功能，也建议通过扩展的方式实现，然后再提供钩子函数给PHP调用。这样可以减低内部计算的变量、函数等系列开销。</p>
<h2>3基准性能</h2>
<p>对于PHP基准性能，目前缺少标准的数据。大多数同学都存在感性的认识，有人认为800QPS就是PHP的极限了。此外，对于框架的性能和框架对性能的影响很没有响应的权威数字。</p>
<p>本章节的目的是给出一个基准的参考性能指标，通过数据给大家一个直观的了解。</p>
<p>具体的基准性能有以下几个方面：</p>
<p>1、  裸PHP性能。完成基本的功能。</p>
<p>2、  裸框架的性能。只做最简单的路由分发，只走通核心功能。</p>
<p>3、  标准模块的基准性能。所谓标准模块的基准性能，是指一个具有完整服务模块功能的基准性能。</p>
<h3>3.1环境说明</h3>
<p>测试环境：</p>
<p>Uname -a</p>
<p>Linux db-forum-test17.db01.baidu.com 2.6.9_5-7-0-0 #1 SMP Wed Aug 12 17:35:51 CST 2009 x86_64 x86_64 x86_64 GNU/Linux</p>
<p>Red Hat Enterprise Linux AS release 4 (Nahant Update 3)</p>
<p>8  Intel(R) Xeon(R) CPU           E5520  @ 2.27GHz</p>
<p>软件相关：</p>
<p>Nginx：</p>
<p>nginx version: nginx/0.8.54  built by gcc 3.4.5 20051201 (Red Hat 3.4.5-2)</p>
<p>Php5：（采用php-fpm）</p>
<p>PHP 5.2.8 (cli) (built: Mar  6 2011 17:16:18)</p>
<p>Copyright (c) 1997-2008 The PHP Group</p>
<p>Zend Engine v2.2.0, Copyright (c) 1998-2008 Zend Technologies</p>
<p>with eAccelerator v0.9.5.3, Copyright (c) 2004-2006 eAccelerator, by eAccelerator</p>
<p>bingo2：</p>
<p>PHP框架。</p>
<p>其他说明：</p>
<p>目标机器的部署方式：<a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-15-33-221322552013.jpg&amp;type=image%2Fjpeg&amp;width=184&amp;height=32"><img class="alignleft size-full wp-image-1357" title="2011-11-29 15-33-22" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-15-33-221322552013.jpg&amp;type=image%2Fjpeg&amp;width=184&amp;height=32" alt="" width="184" height="32" /></a>脚本。</p>
<p>测试压力机器和目标机器独立部署。</p>
<h3>3.2裸PHP性能</h3>
<p>最简单的PHP脚本。</p>
<p>&lt;?php</p>
<p>require_once &#8216;./actions/indexAction.php&#8217;;</p>
<p>$objAction = new indexAction();</p>
<p>$objAction-&gt;init();</p>
<p>$objAction-&gt;execute();</p>
<p>?&gt;</p>
<p>Acitons/indexAction.php里面的代码如下</p>
<p>&lt;?php</p>
<p>class indexAction</p>
<p>{</p>
<p>public function execute()</p>
<p>{</p>
<p>echo &#8216;hello, world!&#8217;;</p>
<p>}</p>
<p>}</p>
<p>?&gt;</p>
<p>通过压力工具测试结果如下：</p>
<p><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-30-12-45-091322628332.jpg&amp;type=image%2Fjpeg&amp;width=472&amp;height=162"><img class="aligncenter size-full wp-image-1426" title="2011-11-30 12-45-09" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-30-12-45-091322628332.jpg&amp;type=image%2Fjpeg&amp;width=472&amp;height=162" alt="" width="472" height="162" /></a></p>
<h3>3.3裸PHP框架性能</h3>
<p>为了和3.2的对比，基于bingo2框架实现了类似的功能。代码如下</p>
<p>&lt;?php</p>
<p>require_once &#8216;Bingo/Controller/Front.php&#8217;;</p>
<p>$objFrontController = Bingo_Controller_Front::getInstance(array(</p>
<p>&#8216;actionDir&#8217; =&gt; &#8216;./actions&#8217;,</p>
<p>));</p>
<p>$objFrontController-&gt;dispatch();</p>
<p>?&gt;</p>
<p>压力测试结果如下：</p>
<p><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-15-48-431322552942.jpg&amp;type=image%2Fjpeg&amp;width=375&amp;height=107"><img class="aligncenter size-full wp-image-1363" title="2011-11-29 15-48-43" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-15-48-431322552942.jpg&amp;type=image%2Fjpeg&amp;width=375&amp;height=107" alt="" width="375" height="107" /></a></p>
<p>从该测试结果可以看出：<strong>框架虽然有一定的消耗，但对整体的性能来说影响是非常小的</strong>。</p>
<h3>3.4标准PHP模块的基准性能</h3>
<p>所谓标准PHP模块，是指一个PHP模块所必须要具体的基本功能：</p>
<p style="padding-left: 30px;">●路由分发。</p>
<p style="padding-left: 30px;">●自动加载。</p>
<p style="padding-left: 30px;">●LOG初始化&amp;Notice日志打印。所以的UI请求都一条标准的日志。</p>
<p style="padding-left: 30px;">●错误处理。</p>
<p style="padding-left: 30px;">●时间校正。</p>
<p style="padding-left: 30px;">●自动计算每个阶段耗时开销。</p>
<p style="padding-left: 30px;">●编码识别&amp;编码转化。</p>
<p style="padding-left: 30px;">●标准配置文件的解析和调用</p>
<p style="padding-left: 30px;">采用bingo2的代码自动生成工具产生标准的测试PHP模块：test。</p>
<p>测试结果如下：</p>
<p><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-15-51-401322553111.jpg&amp;type=image%2Fjpeg&amp;width=372&amp;height=182"><img class="aligncenter size-full wp-image-1366" title="2011-11-29 15-51-40" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-29-15-51-401322553111.jpg&amp;type=image%2Fjpeg&amp;width=372&amp;height=182" alt="" width="372" height="182" /></a></p>
<h3>3.5结论</h3>
<p>从测试数据的结论来看，PHP本身的性能还是可以的。基准性能完全能够达到几千甚至上W的QPS。至于为什么在大多数的PHP模块中表现不佳，其实这个时候更应该去找出系统的瓶颈点，而不是简单的说OK，PHP不行，那我们换C来搞吧。（下一个章节，会通过一些例子来对比，采用C来处理不见得有特别的优势）</p>
<p>通过基准数据，可以得出以下几个具体的结论：</p>
<p>1、    PHP本身性能也很不错。简单功能下能够达到5000QPS（50CPU IDLE），极限也能过W。</p>
<p>2、    PHP框架本身对性能影响非常有限。尤其是在有一定业务逻辑和数据交互的情况下，几乎可以忽略。</p>
<p>3、    一个标准的PHP模块，基准性能能够达到2000QPS（80 cpu idle）。</p>
<h2>4PHP与C性能对比分析</h2>
<p>很多时候，大家发现PHP模块性能不行的时候，就来一句“ok，我们采用C重写吧”。在公司内，采用C/C++来写业务逻辑模块的现象到处都有，在前几年甚至几乎全部都是采用C来写。那时候大家写的真是一个痛苦：调试难、敏捷不要谈。</p>
<p>那么，本章节要谈论的一个话题就是：C写的业务逻辑和PHP写的业务逻辑模块进行性能对比，采用真实的数据来说话。</p>
<h3>4.1前提</h3>
<p>为什么要特别说出这个前提呢？因为在理想情况下，一个功能采用PHP实现，该性能铁定不可能比理想的C写出来好。这个前提需要特别注意。</p>
<p>但为什么还要对比呢？因为在现实情况下，能写出非常优秀的C程序，并且在频繁修改的情况下还能做到完全高性能的又有几个呢？并且在现实的应用中C实现的性能是否真的全都都比PHP要好好几倍呢？这些目前都没有确切的数据来论证。</p>
<p>所以，本章节的对比是基于现实中的情况来进行的，并采用真实数据来说话。</p>
<h3>4.2 真实业务模块PHP模块 VS C模块</h3>
<h3>4.2.1业务模块介绍</h3>
<p>一个真实的案列，该业务模块的流量高达数十亿。该模块的架构图如下：</p>
<p><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322627516.jpg&amp;type=image%2Fjpeg&amp;width=558&amp;height=148"><img class="aligncenter size-full wp-image-1419" title="1" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322627516.jpg&amp;type=image%2Fjpeg&amp;width=558&amp;height=148" alt="" width="558" height="148" /></a></p>
<p style="text-align: center;">图4、业务模块架构图</p>
<p>该业务模块功能非常简单，上层是web server，下游是各个数据模块。都是基于socket进行数据交互。该业务模块的主要工作模型是：<strong>响应</strong><strong>web server</strong><strong>的请求，根据请求从各个后端数据模块读取相应数据，并根据数据产出最终的HTML</strong><strong>页面返回给web</strong><strong>服务器</strong>。</p>
<p>为了方便后续介绍，定义CUI表示用C实现的模块，PHPUI表示用PHP实现的模块。</p>
<h3>4.2.2C/C++模块的性能数据结果</h3>
<p>09年，该模块重构选择了一个新的C/C++框架。当时重构的时候，该模块连接的后端数据模块规模<strong>在</strong><strong>5-7</strong><strong>个</strong>。</p>
<p>基于C/C++的模块，最终测试数据数据分成两个部分：</p>
<p>一、性能对比测试。</p>
<p>基于当时线上压力，进行真实数据的性能测试。所以当时只测试一个压力数据如下：</p>
<p><span style="color: #ff0000;">压力：210QPS</span></p>
<p><span style="color: #ff0000;">CPU（IDLE）：84.18</span></p>
<p>二、极限性能测试1。</p>
<p>该测试模型是：CUI只连接一个核心数据模块，其他数据模块完全关闭。</p>
<p><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-30-12-33-151322627607.jpg&amp;type=image%2Fjpeg&amp;width=245&amp;height=508"><img class="aligncenter size-full wp-image-1420" title="2011-11-30 12-33-15" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-30-12-33-151322627607.jpg&amp;type=image%2Fjpeg&amp;width=245&amp;height=508" alt="" width="245" height="508" /></a></p>
<p>三、极限性能测试2。</p>
<p>该测试模型是：CUI连接后端一个核心数据模块，3个数据模块，其他数据模块不连接。</p>
<p>测试后性能数据如下：</p>
<p><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-30-12-34-041322627660.jpg&amp;type=image%2Fjpeg&amp;width=243&amp;height=162"><img class="aligncenter size-full wp-image-1421" title="2011-11-30 12-34-04" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-30-12-34-041322627660.jpg&amp;type=image%2Fjpeg&amp;width=243&amp;height=162" alt="" width="243" height="162" /></a></p>
<h3>4.2.3 PHP实现模块的性能测试数据</h3>
<p>到11年，基于09年的CUI基本上达到了代码不看维护的地步。而且这个时候，CUI的极限性能已经不到<strong>600QPS</strong>（主要原因是随着项目的发展，后端数据模块的数目增加到<span style="color: #ff0000;">14个</span>）。据此，决定采用PHP方案来重写整个模块，并产出最终的pbui模块。</p>
<p>性能测试结果分成两种：</p>
<p>1、PHPUI连接一个核心模块。测试数据如下：</p>
<p><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322627698.jpg&amp;type=image%2Fjpeg&amp;width=498&amp;height=172"><img class="aligncenter size-full wp-image-1422" title="1" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322627698.jpg&amp;type=image%2Fjpeg&amp;width=498&amp;height=172" alt="" width="498" height="172" /></a></p>
<p style="text-align: center;">图5、PHPUI性能测试结果1</p>
<p>2、PHPUI连接后端所有模块（14个）。测试性能数据如下：</p>
<p><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322627734.jpg&amp;type=image%2Fjpeg&amp;width=469&amp;height=192"><img class="aligncenter size-full wp-image-1423" title="1" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322627734.jpg&amp;type=image%2Fjpeg&amp;width=469&amp;height=192" alt="" width="469" height="192" /></a></p>
<p style="text-align: center;">图6、PHPUI性能测试结果2</p>
<h3>4.2.4数据对比结论</h3>
<p>由于PHPUI和CUI的业务逻辑和测试方法都不完全相同，所以抽取了部分大体能对比的点进行整理。具体对比数据如下：</p>
<p><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-30-12-36-081322627777.jpg&amp;type=image%2Fjpeg&amp;width=684&amp;height=156"><img class="aligncenter size-full wp-image-1424" title="2011-11-30 12-36-08" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-30-12-36-081322627777.jpg&amp;type=image%2Fjpeg&amp;width=684&amp;height=156" alt="" width="684" height="156" /></a></p>
<p>从上面的对比数据来看，<strong>在真实的业务项目中，</strong><strong>PHPUI</strong><strong>的性能并不会比CUI</strong><strong>差</strong>。这个不是简简单单一个模块来验证的，在部门里面，我们有不少模块都是从C/C++迁移到PHP，从迁移的结果来看，并没有存在质的性能下降，大部分模块迁移后性能指标都是非常接近的。</p>
<p>这个时候就需要思考为什么会这样了？细分来说有两个问题：</p>
<p>1、   为什么在真实业务项目中，PHPUI的性能并不会比CUI差太多？</p>
<p>2、  为什么基准的PHP性能这么高，80CPU的情况下2000QPS，但到了真实的PHP模块中只能是200QPS？</p>
<p>其实这两个问题，也可以归结成一种原因：<strong>在真实业务项目中，影响性能更多的不是说采用了什么语言，而是其业务相关的部分，比如说</strong><strong>socket</strong><strong>交互次数，比如说字符串处理，也比如说网络交互包大小</strong>。</p>
<p>OK。那么接下来的关键是找出影响性能的关键因素。</p>
<h3>4.2.5影响PHP模块性能的关键因素</h3>
<p>从前面分析，我们得出，影响前端PHP模块性能的关键因素不是语言本身（是否是PHP/JAVA/C都不重要）。那么到底影响PHP业务模块性能的关键因素在哪里呢？CPU耗时是统计一个项目性能的关键点之一，考虑到系统中都打印出了系列日志。通过分析日志中请求的耗时分布可以大体上看出关键点。</p>
<p>在我们系统中，CPU耗时重点打印出以下几个方面：</p>
<p>1、  请求总时间。</p>
<p>2、  请求关键函数的性能，其中所有的socket交互都有耗时计算。</p>
<p>3、  模版渲染也是好事的一个关键点。</p>
<p>在前面分析中，我们基本上判定socket和字符串处理是一个关键点之一，通过数据我们来验证下。抽取一个模块指定数目的日志，进行综合分析得出以下数据：</p>
<p><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-30-12-37-041322627835.jpg&amp;type=image%2Fjpeg&amp;width=683&amp;height=105"><img class="aligncenter size-full wp-image-1425" title="2011-11-30 12-37-04" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=2011-11-30-12-37-041322627835.jpg&amp;type=image%2Fjpeg&amp;width=683&amp;height=105" alt="" width="683" height="105" /></a></p>
<p>通过这个可以看出，在一个业务模块中，影响最大的是socket数据交互，其次是大量的字符串处理。具体细分来说是以下几个因素：socket交互次数、socket交互包大小、socket交互响应时间、字符串处理。</p>
<h3>4.2.6结论</h3>
<p>通过上述分析，可以得出以下结论：在前端业务模块中，PHP语言本身不会成为性能瓶颈。因为影响性能的几个关键因数是：</p>
<p>● 网络交互数目。</p>
<p>●  网络交互数据大小，包含数据打包解包开销。</p>
<p>● 网络交互响应时间。</p>
<p>●  大量的字符串处理。</p>
<h2>5最终结论</h2>
<p>通过上述三个章节的具体分析，可以得出以下结论：</p>
<p>1、从PHP实现原理来看，PHP属于半编译型语言，并且在各个方面都进行了大量的优化工作，本身不会存在明显的性能问题。但由于动态语言的特性，决定了PHP需要运行在Zend Engine虚拟机上，并且在变量查找、函数调用、作用域切换等各个方面需要一些额外开销。</p>
<p>2、从PHP的基准性能来看，PHP本身不会存在明显的资源消耗，单机QPS能够轻松过W， PHP框架本身也不会对业务系统的性能带来关键性的影响。</p>
<p>3、从真实的应用场景来看，基于C语言实现的模块不见得比基于PHP实现的模块性能高效很多。因为在真实的应用场景中，更多的性能开销在于网络数据交互和字符串处理。语言方面微小的性能差异不会成为瓶颈。</p>
<p>据此，可以推出：基于C语言实现的大部分业务系统都可以考虑迁移到PHP上来，一方面能够快速开发，另外一方面性能也不会存在问题。</p>
<p>最后，关于影响PHP性能的关键因素的具体分析和关于语言函数级别PHP与C的基准性能对比分析，请关注下文《深入探讨PHP性能问题》。</p>
<h2>6参考文档</h2>
<p><a href="http://yanbin.org/">http://yanbin.org/</a></p>
<p><a href="https://wiki.php.net/internals/zend_mm">https://wiki.php.net/internals/zend_mm</a></p>
<p><a href="http://blog.xiuwz.com/2011/11/09/php-using-internal-zval/">http://blog.xiuwz.com/2011/11/09/php-using-internal-zval/</a></p>
<p><a href="http://developers.facebook.com/blog/post/358/">http://developers.facebook.com/blog/post/358/</a></p>
<p><a href="https://github.com/facebook/hiphop-php">https://github.com/facebook/hiphop-php</a></p>
<p style="text-align: right;">by xuliqiang</p>
]]></content:encoded>
			<wfw:commentRss>http://stblog.baidu-tech.com/?feed=rss2&amp;p=1343</wfw:commentRss>
		<slash:comments>21</slash:comments>
		</item>
		<item>
		<title>支持快速迭代的LAMP解决方案 ——贴吧LAMP解决方案</title>
		<link>http://stblog.baidu-tech.com/?p=1324</link>
		<comments>http://stblog.baidu-tech.com/?p=1324#comments</comments>
		<pubDate>Mon, 28 Nov 2011 06:46:51 +0000</pubDate>
		<dc:creator>editor</dc:creator>
				<category><![CDATA[编程技术]]></category>
		<category><![CDATA[贴吧技术]]></category>
		<category><![CDATA[lamp]]></category>
		<category><![CDATA[快速迭代]]></category>

		<guid isPermaLink="false">http://stblog.baidu-tech.com/?p=1324</guid>
		<description><![CDATA[摘要：天下武功，唯快不破，互联网竞争的利器就是快！且听贴吧LAMP解决方案如何全面支持快速迭代。
关键词：LAMP，快速迭代
领域：架构
总概
贴吧是功能性产品，唯快不破是永恒的准则，这一特点决定了快速迭代是需要解决的关键性问题。快速迭代，分解开来有如下部分：开发阶段，快速开发；测试阶段，包含了环境快速搭建、自动化测试工具；运维阶段，包含了集群管理技术、自动化运维工具；同时，这三方面的工作需要一个整体性的解决方案衔接起来。
早期的贴吧，作为一个高性能社区，功能相对单一，全部采用C语言开发，系统可重用程度低，开发、测试效率低，运维方面的积累也很少。为了提高效率，开始尝试LAMP架构，经过几年的发展，贴吧已全部迁移到了LAMP。随着产品规模急剧膨胀，30+子系统，150+模块，500+机器，10亿+流量，在LAMP架构方面积累了很多经验，逐渐形成了快速迭代的一体化方案。如下图所示：

该解决方案由开发阶段、测试阶段、运维阶段组成。开发阶段又分成接入层、业务逻辑层、存储层。该解决方案支撑大规模的线上应用，同时保持了快速迭代的特性。基于该解决方案，开发人员能专注于业务逻辑开发，测试人员能专注于持续集成，运维成本能大大降低。
开发
开发方面分为接入层、业务逻辑层、存储层。
接入层处于浏览器和后端服务之间，用来解析http协议并组织成相应的协议格式，完成客户端和服务器之间的通信，还包括攻击防范、页面缓存、负载均衡等多种功能。Web server是其核心组成部分。接入层的目标是通过统一的方案提供简单可依赖的接入层架构，经过全面调研nginx具有通用性强、效率高、功能全面、配置灵活等特点，是webserver未来发展的主力军，确定采用nginx统一接入层。
业务逻辑层包含了PHP框架、业务逻辑、LIB库、交互层。业务逻辑层常常包含一些开发规范，这些规范就像法律一样，我们不仅要有法可依，还要有法必依。在我们的解决方案中，PHP框架=规范+库，规范比如目录部署规范、URL规范、配置规范等，这些规范通过相应的库实现，以达到有法必依的目的。LIB库封装常用的功能。基于这个解决方案，开发者开发应用，只需完成业务逻辑部分。
中间层，如下图所示，包含在业务逻辑层中，对于业务逻辑层的快速迭代非常重要。中间层对下做交互抽象，支持各种协议屏蔽协议细节；通过资源定位屏蔽部署细节；通过负载均衡提高系统稳定性。中间层对上做接口抽象，支持服务整合、接口适配、公共逻辑。中间层首先建立系统&#8211;子系统&#8211;模块的体系，进行服务整合，图中的API-LIB就是根据子系统划分，将各模块的接口(MIDL: Module IDL)转化为子系统接口(SIDL: Service IDL)；接口适配，SERVICE的接口通过SIDL描述，让接口描述、接口文档、线上代码等自动同步，可维护性大大提高，同时通过元数据规范保证全系统的接口一致，易用性大大提高；收敛公共逻辑，对于公共逻辑，比如权限逻辑，收敛起来可维护性大大提高。

存储层，提供各种通用服务、组件。其中的通用数据存储框架提供通用的数据存储和访问解决方案，以一种统一的设计模式来支持大多数数据存储模块的设计和实现；统一数据访问接口，对外部屏蔽数据拆分和存储的细节；做到数据存储的良好扩展性，通过通用的数据拆分模式来应对数据增长；将具有共性的需求抽象成通用服务或通用库，以简化设计和开发。
基于该解决方案，开发一个应用只需要：在接入层配置相应的分流，在业务逻辑层开发业务逻辑，使用存储层合适的服务或基于框架完成数据模块开发。能大大的提高开发效率，支持快速迭代。
测试
测试方面，为了支持快速迭代，必须提高自动化程度。而影响自动化的首要因素就是环境自动构建，常见的问题有：环境复杂，比如关联关系复杂；环境搭建代价过大；环境功能不完整等。采用基准环境能解决这一问题，项目上线后自动从scmpf更新到基准环境；测试环境/开发环境从基准环境同步。基于基准环境，系统级别的持续集成也成为可能，同时可以集中大量测试工具。

运维
运维方面面临着很多问题：服务迁移成本高，环境不一致带来各种回滚，机器利用率不均衡，运维自动化程度低。为了解决这些问题，提出PHP系统运维方案。环境同步方面，主要是代码同步的问题，采用运维规范+监控的方案；性能监控方面，基于交互层完成请求状态、交互性能监控，基于调度中心获取机器状态；机器调度方面，通过调度中心完成动态/半自动机器调度。如下图所示：

展望
通过该LAMP解决方案，在开发、测试、运维方面都能极大的提高效率。未来在LAMP架构方面，需要更多的在规范化、平台化上下功夫。规范之后才能开展这种自动化的工作提高效率；平台化可以把各种规范固化下来，提供自动化的支持。
by zhouren
]]></description>
			<content:encoded><![CDATA[<p><strong>摘要：</strong>天下武功，唯快不破，互联网竞争的利器就是快！且听贴吧LAMP解决方案如何全面支持快速迭代。</p>
<p><strong>关键词：</strong>LAMP，快速迭代</p>
<p><strong>领域：</strong>架构</p>
<h3>总概</h3>
<p>贴吧是功能性产品，唯快不破是永恒的准则，这一特点决定了快速迭代是需要解决的关键性问题。快速迭代，分解开来有如下部分：开发阶段，快速开发；测试阶段，包含了环境快速搭建、自动化测试工具；运维阶段，包含了集群管理技术、自动化运维工具；同时，这三方面的工作需要一个整体性的解决方案衔接起来。</p>
<p>早期的贴吧，作为一个高性能社区，功能相对单一，全部采用C语言开发，系统可重用程度低，开发、测试效率低，运维方面的积累也很少。为了提高效率，开始尝试LAMP架构，经过几年的发展，贴吧已全部迁移到了LAMP。随着产品规模急剧膨胀，30+子系统，150+模块，500+机器，10亿+流量，在LAMP架构方面积累了很多经验，逐渐形成了快速迭代的一体化方案。如下图所示：</p>
<p><span id="more-1324"></span><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322462597.jpg&amp;type=image%2Fjpeg&amp;width=558&amp;height=405"><img class="alignleft size-full wp-image-1325" title="1" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322462597.jpg&amp;type=image%2Fjpeg&amp;width=558&amp;height=405" alt="" width="558" height="405" /></a></p>
<p>该解决方案由开发阶段、测试阶段、运维阶段组成。开发阶段又分成接入层、业务逻辑层、存储层。该解决方案支撑大规模的线上应用，同时保持了快速迭代的特性。基于该解决方案，开发人员能专注于业务逻辑开发，测试人员能专注于持续集成，运维成本能大大降低。</p>
<h3>开发</h3>
<p>开发方面分为接入层、业务逻辑层、存储层。</p>
<p>接入层处于浏览器和后端服务之间，用来解析http协议并组织成相应的协议格式，完成客户端和服务器之间的通信，还包括攻击防范、页面缓存、负载均衡等多种功能。Web server是其核心组成部分。接入层的目标是通过统一的方案提供简单可依赖的接入层架构，经过全面调研nginx具有通用性强、效率高、功能全面、配置灵活等特点，是webserver未来发展的主力军，确定采用nginx统一接入层。</p>
<p>业务逻辑层包含了PHP框架、业务逻辑、LIB库、交互层。业务逻辑层常常包含一些开发规范，这些规范就像法律一样，我们不仅要有法可依，还要有法必依。在我们的解决方案中，PHP框架=规范+库，规范比如目录部署规范、URL规范、配置规范等，这些规范通过相应的库实现，以达到有法必依的目的。LIB库封装常用的功能。基于这个解决方案，开发者开发应用，只需完成业务逻辑部分。</p>
<p>中间层，如下图所示，包含在业务逻辑层中，对于业务逻辑层的快速迭代非常重要。中间层对下做交互抽象，支持各种协议屏蔽协议细节；通过资源定位屏蔽部署细节；通过负载均衡提高系统稳定性。中间层对上做接口抽象，支持服务整合、接口适配、公共逻辑。中间层首先建立系统&#8211;子系统&#8211;模块的体系，进行服务整合，图中的API-LIB就是根据子系统划分，将各模块的接口(MIDL: Module IDL)转化为子系统接口(SIDL: Service IDL)；接口适配，SERVICE的接口通过SIDL描述，让接口描述、接口文档、线上代码等自动同步，可维护性大大提高，同时通过元数据规范保证全系统的接口一致，易用性大大提高；收敛公共逻辑，对于公共逻辑，比如权限逻辑，收敛起来可维护性大大提高。</p>
<p><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322462644.jpg&amp;type=image%2Fjpeg&amp;width=558&amp;height=472"><img class="alignleft size-full wp-image-1326" title="1" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322462644.jpg&amp;type=image%2Fjpeg&amp;width=558&amp;height=472" alt="" width="558" height="472" /></a></p>
<p>存储层，提供各种通用服务、组件。其中的通用数据存储框架提供通用的数据存储和访问解决方案，以一种统一的设计模式来支持大多数数据存储模块的设计和实现；统一数据访问接口，对外部屏蔽数据拆分和存储的细节；做到数据存储的良好扩展性，通过通用的数据拆分模式来应对数据增长；将具有共性的需求抽象成通用服务或通用库，以简化设计和开发。</p>
<p>基于该解决方案，开发一个应用只需要：在接入层配置相应的分流，在业务逻辑层开发业务逻辑，使用存储层合适的服务或基于框架完成数据模块开发。能大大的提高开发效率，支持快速迭代。</p>
<h3>测试</h3>
<p>测试方面，为了支持快速迭代，必须提高自动化程度。而影响自动化的首要因素就是环境自动构建，常见的问题有：环境复杂，比如关联关系复杂；环境搭建代价过大；环境功能不完整等。采用基准环境能解决这一问题，项目上线后自动从scmpf更新到基准环境；测试环境/开发环境从基准环境同步。基于基准环境，系统级别的持续集成也成为可能，同时可以集中大量测试工具。</p>
<p><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322462709.jpg&amp;type=image%2Fjpeg&amp;width=302&amp;height=232"><img class="alignleft size-full wp-image-1327" title="1" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322462709.jpg&amp;type=image%2Fjpeg&amp;width=302&amp;height=232" alt="" width="302" height="232" /></a></p>
<h3>运维</h3>
<p>运维方面面临着很多问题：服务迁移成本高，环境不一致带来各种回滚，机器利用率不均衡，运维自动化程度低。为了解决这些问题，提出PHP系统运维方案。环境同步方面，主要是代码同步的问题，采用运维规范+监控的方案；性能监控方面，基于交互层完成请求状态、交互性能监控，基于调度中心获取机器状态；机器调度方面，通过调度中心完成动态/半自动机器调度。如下图所示：</p>
<p><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322462738.jpg&amp;type=image%2Fjpeg&amp;width=370&amp;height=233"><img class="alignleft size-full wp-image-1328" title="1" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322462738.jpg&amp;type=image%2Fjpeg&amp;width=370&amp;height=233" alt="" width="370" height="233" /></a></p>
<h2>展望</h2>
<p>通过该LAMP解决方案，在开发、测试、运维方面都能极大的提高效率。未来在LAMP架构方面，需要更多的在规范化、平台化上下功夫。规范之后才能开展这种自动化的工作提高效率；平台化可以把各种规范固化下来，提供自动化的支持。</p>
<p style="text-align: right;">by zhouren</p>
]]></content:encoded>
			<wfw:commentRss>http://stblog.baidu-tech.com/?feed=rss2&amp;p=1324</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>贴吧涂鸦&#8211;毕加索的画板</title>
		<link>http://stblog.baidu-tech.com/?p=1312</link>
		<comments>http://stblog.baidu-tech.com/?p=1312#comments</comments>
		<pubDate>Mon, 28 Nov 2011 06:07:19 +0000</pubDate>
		<dc:creator>editor</dc:creator>
				<category><![CDATA[贴吧技术]]></category>
		<category><![CDATA[Actionscript3]]></category>
		<category><![CDATA[画图板]]></category>

		<guid isPermaLink="false">http://stblog.baidu-tech.com/?p=1312</guid>
		<description><![CDATA[摘要
贴吧社区上线了用户等级权限系统，“涂鸦”属于“等级权限”项目中的单项权限功能，有助于丰富完善等级权限体系，为高等级用户提供更强大的功能，帮助产出差异化内容。
“涂鸦”使用Actionscript3开发。本文主要介绍功能实现方式和开发过程中值得注意的地方。
TAG
Actionscript3 画图板
术语或简称
AS： Actionscript3.0；
DOOUM： AS的鼠标五个事件，MouseDown、MouseOver、MouseOut、MouseUp、MouseMove事件；

内容
1.分层设计 &#8212; 简化的MVC；
2.单例模式；
3.画笔、橡皮擦和背景的实现；
4.撤销、重做的实现；
分层设计
网络层
CheckJsReady：负责初始化时和JS互相确认初始化完毕；
SayToJs：负责ActionScript和JavaScript的交互工作；
ImageUpload： 负责ActionScript和Server的交互；
应用层
1)    View &#38; Model 因为View层的元素不会出现多例的情况，View和Model层已结合在一起。
ViewList： 保存所有主要View对象引用，单例；
Paper：涂鸦画板；
ToolPanel： 工具栏，放置涂鸦相关工具；
BackgroundPicture： 画板背景对象；
WaterMarker：涂鸦水印；
2)     Control
ControlCore： 控制中心，单例；
分层模型图示(图1)

MVC模型图示（图2）

基本配置文件
BaseConf： 基本配置，包括Flash所有的默认配置；
FunctionalButtonImages: 加载所有的按钮上的Icon图片，默认编译在swf中；
MouseCursorImages: 加载所有的鼠标指针，默认编译在swf中；
MsgList: 消息文本列表，程序所有的提醒消息文本。
单例模式
单例模式可以不用在多个类中产生实例，而只是产生一个实例。单例模式可以实现在多个类中可以共享一个实例的数据。同时也可以避免在多个类中反复创建某个实例的工作，因为实际上我们并不需要也不想要每次都new一遍。
如前面所述，ControlCore和 ViewList均使用的是单例模式。
ControlCore作为控制中心，应该是以一个全局的对象存在。所以它不应该在每个类中都出现一个实例。
ViewList保存的是主要View层对象的引用，其实质是对象引用的List，也应该是一个全局的对象。
在单例模式中，使用单例模式的类应该是无法被实例化的。这样才能保证这个类的正确使用。一般将构造函数定义成私有的（private）即可达到这个目的。但是在AS中，构造函数是无法定义成私有的。所以在AS选择了另一种做法。
例： 以下是 A.as代码
Package {
	   public class A{
       static private var _a:A;
//构造函数需要传入Class N的实例
    public function A(n:N){}

    public static function g():A{
	if(A._a == null) {
		A._a [...]]]></description>
			<content:encoded><![CDATA[<h2>摘要</h2>
<p>贴吧社区上线了用户等级权限系统，“涂鸦”属于“等级权限”项目中的单项权限功能，有助于丰富完善等级权限体系，为高等级用户提供更强大的功能，帮助产出差异化内容。</p>
<p>“涂鸦”使用Actionscript3开发。本文主要介绍功能实现方式和开发过程中值得注意的地方。</p>
<h2>TAG</h2>
<p>Actionscript3 画图板</p>
<h2>术语或简称</h2>
<p>AS： Actionscript3.0；</p>
<p><a href="file:///C:/Documents%20and%20Settings/xupeirui/%E6%A1%8C%E9%9D%A2/%E8%B4%B4%E5%90%A7%E6%B6%82%E9%B8%A6--%E6%AF%95%E5%8A%A0%E7%B4%A2%E7%9A%84%E7%94%BB%E6%9D%BF.docx#_画笔和橡皮擦">DOOUM</a>： AS的鼠标五个事件，MouseDown、MouseOver、MouseOut、MouseUp、MouseMove事件；</p>
<p><span id="more-1312"></span></p>
<h2>内容</h2>
<h2 style="padding-left: 30px;"><span style="font-weight: normal; font-size: 13px;">1.分层设计 &#8212; 简化的MVC；</span></h2>
<h2 style="padding-left: 30px;"><span style="font-weight: normal; font-size: 13px;">2.单例模式；</span></h2>
<h2 style="padding-left: 30px;"><span style="font-weight: normal; font-size: 13px;">3.画笔、橡皮擦和背景的实现；</span></h2>
<h2 style="padding-left: 30px;"><span style="font-weight: normal; font-size: 13px;">4.撤销、重做的实现；</span></h2>
<h3>分层设计</h3>
<h4>网络层</h4>
<p style="padding-left: 30px;">CheckJsReady：负责初始化时和JS互相确认初始化完毕；</p>
<p style="padding-left: 30px;">SayToJs：负责ActionScript和JavaScript的交互工作；</p>
<p style="padding-left: 30px;">ImageUpload： 负责ActionScript和Server的交互；</p>
<h4>应用层</h4>
<p style="padding-left: 30px;">1)    <em>View &amp; Model </em><em><span style="color: #888888;">因为View层的元素不会出现多例的情况，View和Model层已结合在一起。</span></em></p>
<p style="padding-left: 30px;">ViewList： 保存所有主要View对象引用，单例；</p>
<p style="padding-left: 30px;">Paper：涂鸦画板；</p>
<p style="padding-left: 30px;">ToolPanel： 工具栏，放置涂鸦相关工具；</p>
<p style="padding-left: 30px;">BackgroundPicture： 画板背景对象；</p>
<p style="padding-left: 30px;">WaterMarker：涂鸦水印；</p>
<p style="padding-left: 30px;">2)     <em>Control</em></p>
<p style="padding-left: 30px;">ControlCore： 控制中心，单例；</p>
<h4>分层模型图示(图1)</h4>
<p><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322458431.jpg&amp;type=image%2Fjpeg&amp;width=521&amp;height=548"><img class="alignleft size-full wp-image-1314" title="1" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322458431.jpg&amp;type=image%2Fjpeg&amp;width=521&amp;height=548" alt="" width="521" height="548" /></a></p>
<h4>MVC模型图示（图2）</h4>
<p><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322458943.jpg&amp;type=image%2Fjpeg&amp;width=557&amp;height=238"><img class="alignleft size-full wp-image-1315" title="1" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322458943.jpg&amp;type=image%2Fjpeg&amp;width=557&amp;height=238" alt="" width="557" height="238" /></a></p>
<h4>基本配置文件</h4>
<p>BaseConf： 基本配置，包括Flash所有的默认配置；<br />
FunctionalButtonImages: 加载所有的按钮上的Icon图片，默认编译在swf中；<br />
MouseCursorImages: 加载所有的鼠标指针，默认编译在swf中；<br />
MsgList: 消息文本列表，程序所有的提醒消息文本。</p>
<h3>单例模式</h3>
<p>单例模式可以不用在多个类中产生实例，而只是产生一个实例。单例模式可以实现在多个类中可以共享一个实例的数据。同时也可以避免在多个类中反复创建某个实例的工作，因为实际上我们并不需要也不想要每次都new一遍。</p>
<p>如前面所述，ControlCore和 ViewList均使用的是单例模式。</p>
<p>ControlCore作为控制中心，应该是以一个全局的对象存在。所以它不应该在每个类中都出现一个实例。</p>
<p>ViewList保存的是主要View层对象的引用，其实质是对象引用的List，也应该是一个全局的对象。</p>
<p>在单例模式中，使用单例模式的类应该是无法被实例化的。这样才能保证这个类的正确使用。一般将构造函数定义成私有的（private）即可达到这个目的。但是在AS中，构造函数是无法定义成私有的。所以在AS选择了另一种做法。</p>
<p><strong>例： 以下是 A.as代码</strong></p>
<pre class="brush: shell">Package {
	   public class A{
       static private var _a:A;
//构造函数需要传入Class N的实例
    public function A(n:N){}

    public static function g():A{
	if(A._a == null) {
		A._a = new A(new N());
}
return A._a;
}

public function doSomething():void{}
 }
}

//在package外再定义一个类N
Class N{}</pre>
<p>说明：<br />
类A的构造函数需要传入类N的实例，但是类N是在A.as中定义的，只有在这个文件才能被访问。这样就保证了，类A的构造函数在A.as以外是无法正常被调用的，类似于构造函数私有化了。<br />
在类A中声明一个私有的属性-类A静态对象，该属性将会在g()方法第一次被调用的时候被定义。g()方法的返回是一个类A的实例化对象，他有类A的所有的属性和方法。所以类A的其他公共方法都可以通过g()的返回值来调用。<br />
这种方式还有一种好处是在g()没有被调用之前，私有的_a是不会被定义的。这样可以不用一开始就占用资源。同样，不把类中A所有方法定义成静态方法也是这个原因。<br />
如 doSomething方法 可以这样调用  A.g().dosomething()。<br />
通过这种形式，在AS中也能很方便的使用单例模式。</p>
<h3>画笔、橡皮擦和背景</h3>
<p>涂鸦最主要的逻辑实现都集中在Paper上。</p>
<p>一是因为Paper是画纸，是用户主要操作区域；</p>
<p>二是因为当初设计时，没有再进行细分，一些附属的功能也加在Paper里。</p>
<h4>画笔</h4>
<p>其实实现画笔很简单，监听好鼠标的<a href="file:///C:/Documents%20and%20Settings/xupeirui/%E6%A1%8C%E9%9D%A2/%E8%B4%B4%E5%90%A7%E6%B6%82%E9%B8%A6--%E6%AF%95%E5%8A%A0%E7%B4%A2%E7%9A%84%E7%94%BB%E6%9D%BF.docx#_术语或简称">DOOUM</a>五个事件即可。</p>
<p>在实现上，Paper只是一个容器，装载着已画好的图像和正在画图像。并提供给ControlCore一个提取图像数据的接口当做提交之用。</p>
<p>背景BackgroundPicture处于Paper之下。当用户选择加载本地的图片之后，显示该图片。</p>
<p>大致的层次关系如（图3）所示：</p>
<p><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322459576.jpg&amp;type=image%2Fjpeg&amp;width=397&amp;height=321"><img class="alignleft size-full wp-image-1316" title="1" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322459576.jpg&amp;type=image%2Fjpeg&amp;width=397&amp;height=321" alt="" width="397" height="321" /></a></p>
<p>如图所示，在用户开始绘画的时候（触发Paper的MouseDown事件），Paper则会创建一个和自己一般大小的A。Paper通过监听用户鼠标事件获得的鼠标轨迹数据，A通过接口获得数据，并draw出。</p>
<p>由于一些原因，在鼠标快速移入移出Paper的时候会导致笔迹与画纸边界出现断裂的现象。通过监听Paper的MouseOver &amp; MouseOut，在触发这两个事件的时候获取鼠标触发以上两个事件的坐标，在A中进行一些偏差容错的计算，使得笔迹连贯自然。（不过应该有更好的方式来实现）</p>
<p>用户画完后，触发Paper的MouseUp事件。在此时将A的数据与B的数据进行合并，同时将A移除。用户看到的就是最新画好的图像了。</p>
<h4>橡皮擦</h4>
<p>橡皮擦，可以看做是一种比较独特的画笔。记得以前有一种笔，笔迹是透明的，而且可以把笔迹经过的地方的其他的颜色抹去，很类似这里的橡皮擦。</p>
<p>其实橡皮擦只是在draw的模式上有所区别。</p>
<p>亦如上图。</p>
<p>默认情况下，A的blendMode为BlendMode.Normal，在两层数据合并时的模式也是一样。</p>
<p>用户选择橡皮擦之后， A的BlendMode则被定义为BlendMode.LAYER，在两层数据合并时的模式改为BlendMode. ERASE。</p>
<p>这样用户用橡皮擦“画笔”画过的地方就变成了透明的。</p>
<h3>撤销、重做</h3>
<p>在最开始设计的时候，思维形成了定式。从表面上看每次撤销和重做，都是回滚或者回退用户操作的某一笔。程序需要操作的是某一笔的信息—笔迹的坐标记录。</p>
<p>实际实现中发现，这样不靠谱。因为一笔可以无限长，这样就会导致撤销和重做会都抖需要遍历一个长度不可控的数组。即便是使用Vector，时间和空间的复杂度都是单位计量*N。</p>
<p>通过参考其他的绘画应用，使用了以下方式来实现撤销和重做，使得时间和空间都变得可控：</p>
<p>为撤销和重做自定义两个固定长度的“队列”。同时插入B的图像初始数据对象（BitmapData）入撤销队列。</p>
<p>在每次A与B的数据合并之后（参考图3），将B的数据对象的副本插入撤销队列。</p>
<p>当用户撤销时将队尾的数据对象弹出并插入重做队列中，再将此时的顶部数据对象显示在B中。</p>
<p>当用户重做时将重做队尾数据对象弹出并插入撤销队列中，并将对象显示在B中。</p>
<p>具体如图 4.1 – 4.3所示，</p>
<p style="text-align: center;"><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322459923.jpg&amp;type=image%2Fjpeg&amp;width=397&amp;height=354"><img class="alignleft size-full wp-image-1317" title="1" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322459923.jpg&amp;type=image%2Fjpeg&amp;width=397&amp;height=354" alt="" width="397" height="354" /></a></p>
<p style="text-align: center;">图4.1 画好的图像压入撤销栈</p>
<p style="text-align: center;"><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322460019.jpg&amp;type=image%2Fjpeg&amp;width=385&amp;height=346"><img class="alignleft size-full wp-image-1318" title="1" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322460019.jpg&amp;type=image%2Fjpeg&amp;width=385&amp;height=346" alt="" width="385" height="346" /></a></p>
<p style="text-align: center;">图4.2 撤销<br />
<a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322460134.jpg&amp;type=image%2Fjpeg&amp;width=394&amp;height=353"><img class="alignleft size-full wp-image-1320" title="1" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322460134.jpg&amp;type=image%2Fjpeg&amp;width=394&amp;height=353" alt="" width="394" height="353" /></a><br />
4.3	重做</p>
<p>从图中可以看出，撤销队列中始终是需要有数据对象的，而且当前队尾的数据对象和当前显示的图像数据对象一致。当确定了撤销的步数为N的之后，那么撤销队列的最大值则为N+1，而重做队列的最大值则为N。<br />
撤销队列满了时，将队头的数据对象弹出并销毁。</p>
<p style="text-align: right;">by lanbin</p>
]]></content:encoded>
			<wfw:commentRss>http://stblog.baidu-tech.com/?feed=rss2&amp;p=1312</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>CC-lib无线跨平台web页面自动化生成技术的设计实现</title>
		<link>http://stblog.baidu-tech.com/?p=1304</link>
		<comments>http://stblog.baidu-tech.com/?p=1304#comments</comments>
		<pubDate>Thu, 24 Nov 2011 12:13:23 +0000</pubDate>
		<dc:creator>editor</dc:creator>
				<category><![CDATA[前端技术]]></category>
		<category><![CDATA[贴吧技术]]></category>
		<category><![CDATA[CC-lib]]></category>
		<category><![CDATA[web前端]]></category>
		<category><![CDATA[无线]]></category>
		<category><![CDATA[浏览器兼容]]></category>
		<category><![CDATA[自动化生成]]></category>
		<category><![CDATA[跨平台]]></category>

		<guid isPermaLink="false">http://stblog.baidu-tech.com/?p=1304</guid>
		<description><![CDATA[摘要：前端开发通常需要开发多套web页面代码，从而为不同的移动终端浏览器开发不同的web页面，例如低端手机需使用wml，高端手机则支持html和javascript等。本文介绍了一种跨平台web页面自动化生成技术，该技术利用php设计了一个中间层（CC-lib），可以屏蔽底层的web展示语言的差异，程序运行时动态生成各个UI组件的wml/xhtml/html代码，从而可以有效降低前端开发人员的页面开发维护成本。
关键词：浏览器兼容，跨平台，无线，web前端，自动化生成，CC-lib
技术领域：无线，web前端

一、背景
在无线领域，通常要为不同的机型，使用不同的编程语言（wml/xhtml/html）编写网页，往往存在下面几个问题：
（1）维护3份代码，开发效率低、维护成本高。
（2）应用开发人员需要关注不同平台的语言差异，调试、自测繁琐。
（3）业务展现逻辑代码和wml/xhtml/html的标签容易混杂在php模板页面里，使代码编写不便，可读性差。
二、技术实现原理
本文介绍一种名为CC-lib的web页面自动化生成工具（实际是一个php实现的组件库），它通过设计实现一个php中间层，来屏蔽底层平台的编程语言差异。
CC-lib形成一套php下的组件库：如panel,form等可视组件或控件；模板开发人员基于这个库来编写PHP程序；
实现编写一套php代码，可分别生成3套(wml/xhtml/html)模板（运行时生成不同的语法标签）。
解决了html标签与程序代码逻辑混杂在一起的问题。
三、CC-lib的设计
1，CC-lib支持的平台
（1）低端手机：wml，古老的手机、低端山寨机
（2）普通手机：xhtml，各种大中小屏的手机
（3）高端手机：html，如iphone/android
2，CC-lib的特点
（1）在组件库的底层内部，在程序运行时自动生成对应的wml或xhtml或html标签来输出，最终形成网页。
（2）组件库底层来决定使用特定平台的哪些特定标签来绘制组件，上层应用开发人员可以不关心底层实现细节。
（3）一些xhtml支持，但是wml不支持的特性，例如左右布局、颜色、锚点等，可以通过底层来进行模糊容错处理。
（4）可支持全局的样式、风格的统一，换肤方便。
（5）是独立的php库，可以单独使用。
（6）新的需求，可以通过增加新的UI组件来扩展。例如iphone手机上的一些动画特效。
（7）采用类似jquery的链式的代码风格：$cc-&#62;class_name(&#8216;head_title&#8217;)-&#62;html(&#8216;hello&#8217;)-&#62;render();
3，CC-lib的整体设计
CC-lib设计一套公共的接口，不同平台(wml/xhtml/html)下分别使用不同的子类去实现这些接口。例如：CCForm接口，分别由3个平台下的CCFormWML类、CCFormXHTML类、CCFormHTML类实现。
CCIPanel面板接口是CC-lib的核心API接口。CCIPanel是最基础的元素，它代表一个网页区域，xhtml版中采用div或span实现，其它的元素都继承自这个类。
可以往面板中添加各种网页元素，如图片、链接、文本、子面板、表单等。

四，CC-lib需处理的一些兼容性问题
不同平台间的细小差异是很多的，下面列出常见的几点：
1，块状元素与行内元素在不同平台下的不同展现。例如：xhtml下可以使用div/span来分别模拟块状元素和行内元素，然而wml平台下没有div和span元素，只能使用br来模拟。
2，左右布局的支持。xhtml下可以支持使用table来做左右两列布局，而wml下则无法支持，只能进行退化处理。
3，form表单的差异。 wml的表单是用anchor+go标签来做的，一个提交按钮一个anchor+go;而xhtml里，表单是用form实现的，一个form里可以直接有多个提交按钮，且多个按钮间是可以共用一批hidden等input表单参数的。
五、CC-lib使用实例
下面是基于CC-lib编写的一个简单页面代码，运行之后将生成使用wml标签来编写的web页面。

六、小结
CC-lib可以用于实现wml/xhtml/html等平台的兼容性处理，当不同平台版本之间的产品功能差异不大时，可以实现一套代码同时为多个平台浏览器进行web页面展示。
by yangzuncheng
]]></description>
			<content:encoded><![CDATA[<p><strong>摘要：</strong>前端开发通常需要开发多套web页面代码，从而为不同的移动终端浏览器开发不同的web页面，例如低端手机需使用wml，高端手机则支持html和javascript等。本文介绍了一种跨平台web页面自动化生成技术，该技术利用php设计了一个中间层（CC-lib），可以屏蔽底层的web展示语言的差异，程序运行时动态生成各个UI组件的wml/xhtml/html代码，从而可以有效降低前端开发人员的页面开发维护成本。</p>
<p><strong>关键词：</strong>浏览器兼容，跨平台，无线，web前端，自动化生成，CC-lib</p>
<p><strong>技术领域：</strong>无线，web前端</p>
<p><span id="more-1304"></span></p>
<h2><strong>一、背景</strong></h2>
<p>在无线领域，通常要为不同的机型，使用不同的编程语言（wml/xhtml/html）编写网页，往往存在下面几个问题：</p>
<p>（1）维护3份代码，开发效率低、维护成本高。</p>
<p>（2）应用开发人员需要关注不同平台的语言差异，调试、自测繁琐。</p>
<p>（3）业务展现逻辑代码和wml/xhtml/html的标签容易混杂在php模板页面里，使代码编写不便，可读性差。</p>
<h2><strong>二、技术实现原理</strong></h2>
<p>本文介绍一种名为CC-lib的web页面自动化生成工具（实际是一个php实现的组件库），它通过设计实现一个php中间层，来屏蔽底层平台的编程语言差异。</p>
<p>CC-lib形成一套php下的组件库：如panel,form等可视组件或控件；模板开发人员基于这个库来编写PHP程序；</p>
<p>实现编写一套php代码，可分别生成3套(wml/xhtml/html)模板（运行时生成不同的语法标签）。</p>
<p>解决了html标签与程序代码逻辑混杂在一起的问题。</p>
<h2><strong>三、CC-lib的设计</strong></h2>
<p><strong>1</strong><strong>，CC-lib支持的平台</strong></p>
<p>（1）低端手机：wml，古老的手机、低端山寨机</p>
<p>（2）普通手机：xhtml，各种大中小屏的手机</p>
<p>（3）高端手机：html，如iphone/android</p>
<p><strong>2</strong><strong>，CC-lib的特点</strong></p>
<p>（1）在组件库的底层内部，在程序运行时自动生成对应的wml或xhtml或html标签来输出，最终形成网页。</p>
<p>（2）组件库底层来决定使用特定平台的哪些特定标签来绘制组件，上层应用开发人员可以不关心底层实现细节。</p>
<p>（3）一些xhtml支持，但是wml不支持的特性，例如左右布局、颜色、锚点等，可以通过底层来进行模糊容错处理。</p>
<p>（4）可支持全局的样式、风格的统一，换肤方便。</p>
<p>（5）是独立的php库，可以单独使用。</p>
<p>（6）新的需求，可以通过增加新的UI组件来扩展。例如iphone手机上的一些动画特效。</p>
<p>（7）采用类似jquery的链式的代码风格：$cc-&gt;class_name(&#8216;head_title&#8217;)-&gt;html(&#8216;hello&#8217;)-&gt;render();</p>
<p><strong>3</strong><strong>，CC-lib的整体设计</strong></p>
<p>CC-lib设计一套公共的接口，不同平台(wml/xhtml/html)下分别使用不同的子类去实现这些接口。例如：CCForm接口，分别由3个平台下的CCFormWML类、CCFormXHTML类、CCFormHTML类实现。</p>
<p>CCIPanel面板接口是CC-lib的核心API接口。CCIPanel是最基础的元素，它代表一个网页区域，xhtml版中采用div或span实现，其它的元素都继承自这个类。</p>
<p>可以往面板中添加各种网页元素，如图片、链接、文本、子面板、表单等。</p>
<p><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=21322136557.jpg&amp;type=image%2Fjpeg&amp;width=678&amp;height=500"><img class="alignleft size-full wp-image-1305" title="2" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=21322136557.jpg&amp;type=image%2Fjpeg&amp;width=678&amp;height=500" alt="" width="678" height="500" /></a></p>
<h2><strong>四，CC-lib需处理的一些兼容性问题</strong></h2>
<p>不同平台间的细小差异是很多的，下面列出常见的几点：</p>
<p><strong>1</strong><strong>，块状元素与行内元素在不同平台下的不同展现。</strong>例如：xhtml下可以使用div/span来分别模拟块状元素和行内元素，然而wml平台下没有div和span元素，只能使用br来模拟。</p>
<p><strong>2</strong><strong>，左右布局的支持。</strong>xhtml下可以支持使用table来做左右两列布局，而wml下则无法支持，只能进行退化处理。</p>
<p><strong>3</strong><strong>，form表单的差异。</strong> wml的表单是用anchor+go标签来做的，一个提交按钮一个anchor+go;而xhtml里，表单是用form实现的，一个form里可以直接有多个提交按钮，且多个按钮间是可以共用一批hidden等input表单参数的。</p>
<h2><strong>五、CC-lib使用实例</strong></h2>
<p>下面是基于CC-lib编写的一个简单页面代码，运行之后将生成使用wml标签来编写的web页面。</p>
<p><a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=21322136609.jpg&amp;type=image%2Fjpeg&amp;width=663&amp;height=450"><img class="alignleft size-full wp-image-1306" title="2" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=21322136609.jpg&amp;type=image%2Fjpeg&amp;width=663&amp;height=450" alt="" width="663" height="450" /></a></p>
<p><strong>六、小结</strong><br />
CC-lib可以用于实现wml/xhtml/html等平台的兼容性处理，当不同平台版本之间的产品功能差异不大时，可以实现一套代码同时为多个平台浏览器进行web页面展示。</p>
<p style="text-align: right;">by yangzuncheng</p>
]]></content:encoded>
			<wfw:commentRss>http://stblog.baidu-tech.com/?feed=rss2&amp;p=1304</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>IOS自动化打包介绍</title>
		<link>http://stblog.baidu-tech.com/?p=1295</link>
		<comments>http://stblog.baidu-tech.com/?p=1295#comments</comments>
		<pubDate>Thu, 24 Nov 2011 10:31:28 +0000</pubDate>
		<dc:creator>editor</dc:creator>
				<category><![CDATA[无线客户端技术]]></category>
		<category><![CDATA[贴吧技术]]></category>
		<category><![CDATA[app打包]]></category>
		<category><![CDATA[Ios打包]]></category>
		<category><![CDATA[iphone打包]]></category>
		<category><![CDATA[iphone自动化打渠道包]]></category>

		<guid isPermaLink="false">http://stblog.baidu-tech.com/?p=1295</guid>
		<description><![CDATA[摘要
　　随着苹果手持设备用户的不断增加，ios应用也增长迅速，同时随着iphone被越狱越来越多的app 的渠道也不断增多，为各个渠道打包成了一件费时费力的工作，本文提供一种比较智能的打包方式来减少其带来的各种不便。
TAG
Ios打包，app打包，iphone打包，iphone自动化打包，ios打渠道包，iphone自动化打渠道包

自动化打包背景介绍
1、背景
随着ios程序发布的渠道逐渐的增多，为每个渠道打包也成为特别耗费时间和体力的一项技术活了，而这一般大多数都是由rd来完成的。这样就占用了rd很多的开发时间，何不把这些东西写成一个自动化的脚本，然后交给qa 或是 pm来完成这个打包过程了。经过一番调研发现网上这种脚本还是很少的，不过xcode 提供了shell编译工具 xcodebuild 和 ipa打包工具xcrun ，这就有理由让我们利用这两个工具写一个自动化的打包脚本来提高我们的工作效率和自动化程度。
2、ios程序包格式、渠道包格式
1)	产生多渠道的原因及多渠道带来的打包问题：随着iphone、ipad、itouch等手持设备火热销售，而它们上面的应用也随之火爆了起来，而随之而来的就是以上设备被越狱后就可以在越狱的设备上直接运行ipa程序包 而不用通过appstore去下载，这样一来国内就产生了众多的专门为越狱手机而开设的渠道提供ipa程序包的下载。随着越来越多的渠道，推广时为各个渠道打包就成了一项比较耗费时间和精力的技术活了，因此我们必须寻找一种自动化的方式 让打包变得高效简单。
2)	Ios程序包分为appstore二进制文件跟渠道包两种格式
 appstore二进制文件：通过xcode工具可以生成一个.app格式的二进制文件。
渠道包：格式为.ipa格式，在没有自动化打包工具之前都是利用xcode来生成相应的渠道包，而且每次只能生成一个渠道包，每次打包之前都得手动该渠道ID，带来的问题就是耗费时间、效率低下、容易出错、增加风险。
3、传统的ios打包方式
利用xcode 打包
1)	appstore 二进制程序包
打开你的项目，进入“Edit Project Settings”，进入Configuration页面，选中Release点击下面的Duplicate，复制一个新的配置项出来，命名为Distribution。然后进入Build页面，顶上的Configuration下拉框选中Distribution，下面的Code Signing Identity里面的Any iPhone OS Device后面对应的值选中你的那个Distribution的证书。然后点击“Build”&#8211;&#62; “Build” 就可以编译程序了
编译成功后，你就可以在相应的build目录下看到一个.app的二进制文件。
2)	渠道ipa包
根据以上步骤同样配置好Distribution 证书 ，然后点击 “Build”&#8211;&#62; “Build and Archive” 就可以编译程序了。接着打开“Window””Organizer” 左边栏中选择”ARCHIVED APPLICATIONS” 然后再右侧列表中选中刚才编译的程序包 再点击右侧右边顶部的”Share”按钮 保存到磁盘即可。就会生成一个.ipa的文件  即为渠道包。
注意：以上运行设备必须选择“Deveice“
4、传统的打包带来的问题
耗费时间、耗费体力、效率低下、只能依赖RD来完成、容易出错、发布风险比较高、QA回归确认比较困难、 不智能化。
自动化打包具体实现
1、xcodebuild 介绍：
xcodebuild[-project
][-activetarget][-alltargets][-target]...[-parallelizeTargets][-activeconfiguration][-configuration][-sdk &#124;][=]...[]...
xcodebuild[-version[-sdk &#124;]]
xcodebuild[-showsdks]
xcodebuild[-find ][-sdk &#124;]
xcodebuild[-list]
也可以在终端输入：xcodebuild –help 或 –h查看具体的选项
显示xcodebuildversion：xcodebuild –version
显示当前系统安装的sdk：xcodebuild –showsdks
显示当前目录下project Information：xcodebuild –list
 需要注意的是：执行以上命令时必须把位置定位在ios项目文件的根目录下 否则会提示找不相关命令的。
2、xcrun 介绍：
此工具主要用于将app文件打包成ipa格式的程序包。（主要用于已越狱手机）。
具体用法如下：
/usr/bin/xcrun -sdk iphoneos [...]]]></description>
			<content:encoded><![CDATA[<h1 style="font-size: 20px;">摘要</h1>
<p>　　随着苹果手持设备用户的不断增加，ios应用也增长迅速，同时随着iphone被越狱越来越多的app 的渠道也不断增多，为各个渠道打包成了一件费时费力的工作，本文提供一种比较智能的打包方式来减少其带来的各种不便。</p>
<h1 style="font-size: 20px;">TAG</h1>
<p>Ios打包，app打包，iphone打包，iphone自动化打包，ios打渠道包，iphone自动化打渠道包</p>
<p><span id="more-1295"></span></p>
<h1 style="font-size: 20px;">自动化打包背景介绍</h1>
<h2>1、背景</h2>
<p>随着ios程序发布的渠道逐渐的增多，为每个渠道打包也成为特别耗费时间和体力的一项技术活了，而这一般大多数都是由rd来完成的。这样就占用了rd很多的开发时间，何不把这些东西写成一个自动化的脚本，然后交给qa 或是 pm来完成这个打包过程了。经过一番调研发现网上这种脚本还是很少的，不过xcode 提供了shell编译工具 xcodebuild 和 ipa打包工具xcrun ，这就有理由让我们利用这两个工具写一个自动化的打包脚本来提高我们的工作效率和自动化程度。</p>
<h2>2、ios程序包格式、渠道包格式</h2>
<p>1)	产生多渠道的原因及多渠道带来的打包问题：随着iphone、ipad、itouch等手持设备火热销售，而它们上面的应用也随之火爆了起来，而随之而来的就是以上设备被越狱后就可以在越狱的设备上直接运行ipa程序包 而不用通过appstore去下载，这样一来国内就产生了众多的专门为越狱手机而开设的渠道提供ipa程序包的下载。随着越来越多的渠道，推广时为各个渠道打包就成了一项比较耗费时间和精力的技术活了，因此我们必须寻找一种自动化的方式 让打包变得高效简单。<br />
2)	Ios程序包分为appstore二进制文件跟渠道包两种格式<br />
<span style="color: #3366ff;"> appstore二进制文件</span>：通过xcode工具可以生成一个.app格式的二进制文件。<br />
渠道包：格式为.ipa格式，在没有自动化打包工具之前都是利用xcode来生成相应的渠道包，而且每次只能生成一个<span style="color: #3366ff;">渠道</span><span style="color: #3366ff;">包</span>，每次打包之前都得手动该渠道ID，带来的问题就是耗费时间、效率低下、容易出错、增加风险。</p>
<h2>3、传统的ios打包方式</h2>
<p>利用xcode 打包</p>
<p>1)	appstore 二进制程序包<br />
打开你的项目，进入“Edit Project Settings”，进入Configuration页面，选中Release点击下面的Duplicate，复制一个新的配置项出来，命名为Distribution。然后进入Build页面，顶上的Configuration下拉框选中Distribution，下面的Code Signing Identity里面的Any iPhone OS Device后面对应的值选中你的那个Distribution的证书。然后点击“Build”&#8211;&gt; “Build” 就可以编译程序了<br />
编译成功后，你就可以在相应的build目录下看到一个.app的二进制文件。<br />
2)	渠道ipa包<br />
根据以上步骤同样配置好Distribution 证书 ，然后点击 “Build”&#8211;&gt; “Build and Archive” 就可以编译程序了。接着打开“Window””Organizer” 左边栏中选择”ARCHIVED APPLICATIONS” 然后再右侧列表中选中刚才编译的程序包 再点击右侧右边顶部的”Share”按钮 保存到磁盘即可。就会生成一个.ipa的文件  即为渠道包。<br />
注意：以上运行设备必须选择“Deveice“</p>
<h2>4、传统的打包带来的问题</h2>
<p>耗费时间、耗费体力、效率低下、只能依赖RD来完成、容易出错、发布风险比较高、QA回归确认比较困难、 不智能化。</p>
<h1 style="font-size: 20px;">自动化打包具体实现</h1>
<h2>1、xcodebuild 介绍：</h2>
<pre class="brush: shell">xcodebuild[-project
][-activetarget][-alltargets][-target]...[-parallelizeTargets][-activeconfiguration][-configuration][-sdk |][=]...[]...
xcodebuild[-version[-sdk |]]
xcodebuild[-showsdks]
xcodebuild[-find ][-sdk |]
xcodebuild[-list]</pre>
<p>也可以在终端输入：xcodebuild –help 或 –h查看具体的选项<br />
显示xcodebuildversion：xcodebuild –version<br />
显示当前系统安装的sdk：xcodebuild –showsdks<br />
显示当前目录下project Information：xcodebuild –list<br />
<span style="color: #ff0000;"> 需要注意的是：执行以上命令时必须把位置定位在ios项目文件的根目录下 否则会提示找不相关命令的。</span></p>
<h2>2、xcrun 介绍：</h2>
<p>此工具主要用于将app文件打包成ipa格式的程序包。（主要用于已越狱手机）。<br />
具体用法如下：</p>
<pre class="brush: shell">/usr/bin/xcrun -sdk iphoneos PackageApplication –v [{TARGET}.app] -o [{TARGET}.ipa] --sign [{Iphone Distribution:xxx}] –embed [{xxx.mobileprovision}]</pre>
<p>其中：-v 对应的是app文件的绝对相对路径 –o 对应ipa文件的路径跟文件名 –sign<br />
对应的是 发布证书中对应的公司名或是个人名  &#8211;embed 对应的是发布证书文件<br />
注意如果对应的Distribution 配置中已经配置好了相关证书信息的话 –sign 和 –embed可以忽略</p>
<h2>3、具体方案</h2>
<p>a)	从源程序一次性打出所有渠道的ipa包 跟appstore的二进制包<br />
为了让自动化脚本执行一次把所有的渠道包都打好，所以必须有一个配置文件用来存储所有的渠道名跟渠道号，而项目文件中也应该有个对应存储当前渠道号的文件，每次程序都从这个存放渠道号的文件中读取渠道号即可，<span style="color: #ff0000;">大概的思路就是利用脚本循环执行打包过程，而每次打包前都通过脚本修改项目中存放渠道号的文件为当前循环的最新渠道号，让后逐个打包。<br />
注：具体事例见附录</span><br />
b)	提供一个ipa格式的母包 从母包生成其它所有的渠道包跟 appstore 包<br />
qa的一些疑问，如何确保所有的渠道包就是他们验证过的那份代码呢？<br />
的确，以上代码每次都是重新对程序进行打包，可qa往往测试验证的只有一个包，如果个个去验证无意中之中又增加了qa的工作量哈！！而且风险也不可控。因此基于上面的问题我们想出了一下办法：<span style="color: #ff0000;">qa只验证一个程序包（即母包）如果这个包通过验证 我们就通过母包去生成其它渠道的包，这样一来qa也不用确认那么多的渠道包了，风险也得到了有效的控制。</span><br />
可能你会问：用一个包生成其他的包可行么？？<br />
原因是这样的：因为每个渠道只是渠道号发生变化，而其他的内容又不会发生变化，而我们的渠道号又是存储在sourceid.dat这个文件中的，所以只要改变母包中的sourceid.dat文件的内容即可，而ipa包又是同zip格式进行压缩，<span style="color: #ff0000;">所以基本思路就是通过zip先对母包进行解压，然后改变sourceid.dat的内容 最后再用zip进行压缩成相应的渠道包即可。</span><br />
Ipa包的目录：<a href="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322127480.jpg&amp;type=image%2Fjpeg&amp;width=159&amp;height=46"><img class="alignleft size-full wp-image-1298" title="1" src="http://stblog.baidu-tech.com/wp-content/uploads/wp-display-data.php?filename=11322127480.jpg&amp;type=image%2Fjpeg&amp;width=159&amp;height=46" alt="" width="159" height="46" /></a><br />
<span style="color: #ff0000;"> 注：具体事例见附录</span></p>
<h1 style="font-size: 20px;">一些问题</h1>
<p>当有些shell命令在mac的终端中运行不通过时，请确保你的shell脚本是在mac环境下编写的而不是同xp等其他环境中copy过来的。因为mac跟xp的编码是不一样所以会有问题。</p>
<h1 style="font-size: 20px;">总结：</h1>
<p>通过从母包打出其他渠道的包这种方法：得到以下好处</p>
<p>i.              降低了rd的工作量，一起qa的工作量。</p>
<p>ii.              让测试发布程序时的风险得到了控制。</p>
<p>iii.              提高了打包发布工作效率。（几十个包 只需短短的几分钟）。</p>
<p>iv.              提高了自动化。</p>
<p>v.              不依赖mac 以及xcode环境 直接在linux 下即可完成 从母包生成其它包</p>
<h1 style="font-size: 20px;">附录</h1>
<h2>利用xcode环境一次生成所有包的shell 脚本代码：</h2>
<pre class="brush: shell">#!/bin/sh

xcodebuild clean -configuration Distribution      //clean项目

distDir="/Users/xxxx/dist"
releaseDir="build/Distribution-iphoneos"
version="1_0_0"
rm -rdf "$distDir"
mkdir "$distDir"
for line in $(cat data.dat)        //读取所有渠道号data.dat文件
do
ipafilename=`echo $line|cut -f1 -d':'` //渠道名
sourceid=`echo $line|cut -f2 -d':'`    //渠道号
echo "ipafilename=$ipaname"
echo "sourceid=$sourceid"
targetName="youtargename"   //项目名称(xcode左边列表中显示的项目名称)
echo "sourceid=$sourceid"
echo "ipafilename=$ipafilename"
echo "$sourceid" &gt; sourceid.dat
echo "sourceid.dat: "
cat sourceid.dat
rm -rdf "$releaseDir"

ipapath="${distDir}/${targetName}_${version}_from_${sourceid}.ipa"

echo "***开始build app文件***"
xcodebuild -target "$targetName" -configuration Distribution  -sdk iphoneos build
appfile="${releaseDir}/${targetName}.app"
if [ $sourceid == "appstore" ]
then
cd $releaseDir
zip -r "${targetName}_${ipafilename}_${version}.zip" "${targetName}.app"
mv "${targetName}_${ipafilename}.zip" $distDir 2&gt; /dev/null
cd ../..
else
echo "***开始打ipa渠道包****"
/usr/bin/xcrun -sdk iphoneos PackageApplication -v "$appfile" -o "$ipapath" --sign "iPhone Distribution:xxxxxx"
fi
done</pre>
<p><span style="color: #ff0000;">注：以上的data.dat文件为存放渠道号列表的文件 其格式为：3g:1001b 即 （渠道名：渠道号） sourceid.dat 为项目文件中存放渠道号的文件（内容只有一个渠道号）。当然了上面脚本只是说明了下如何利用xcodebuild 和 xcrun 进行打包 以及自动打包的一个逻辑，shell脚本好的同学可以自由发挥哈。。。</span></p>
<h2>从ipa格式的母包生成其它渠道包的shell脚本实例：</h2>
<pre class="brush: shell">#!/bin/sh
sourceipaname="母包名.ipa"
appname=”app文件名.app”  //加压后Pauload目录项.app文件名需要根据自己的项目修改
distDir="/Users/lxxx/Qa"   //打包后文件存储目录
version="1.0.0"
rm -rdf "$distDir "
mkdir "$distDir" unzip $sourceipaname     //解压母包文件
for line in $(cat data.dat)   //读取渠道号文件并进行循环
 do
ipafilename=`echo $line|cut -f1 -d':'`
 	sourceid=`echo $line|cut -f2 -d':'`
	echo "ipafilename=$ipaname"
	echo "sourceid=$sourceid"
 	targetName="ipa包名"
 	echo "sourceid=$sourceid"
 	echo "ipafilename=$ipafilename"
 	cd Payload
 	cd $appname
   	echo "replace sourceid.dat before: "
 	cat sourceid.dat
 	echo "$sourceid" &gt; sourceid.dat
 	echo "replace sourceid.dat after: "
 cat sourceid.dat
 if [ $sourceid == "appstroe" ]
then
 cd ..
zip -r "${targetName}_${version}_from_${sourceid}.zip" $appname //appstore二进制文件

	mv "${targetName}_${version}_from_${sourceid}.zip" $distDir
 	cd ..
 	else
 	cd ../..
 	zip -r "${targetName}_${version}_from_${sourceid}.ipa" Payload   //打成其他渠道的包
mv "${targetName}_${version}_from_${sourceid}.ipa" $distDir
 	fi
 done rm -rdf Payload</pre>
<p><span style="color: #ff0000;">注：以上data.dat也是用来存储所有渠道号的，sourceipaname就是通过qa验证的母包，appname为ipa包加压后Payload 目录下的app文件名并且以上所有文件必须与脚本文件保持在同一目录下以及在mac环境中执行。</span></p>
<p style="text-align: right;">by liuzhibin</p>
]]></content:encoded>
			<wfw:commentRss>http://stblog.baidu-tech.com/?feed=rss2&amp;p=1295</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>

