﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-feixuwu-随笔分类-游戏开发</title><link>http://www.cppblog.com/feixuwu/category/14217.html</link><description>穷则独善其身，达则兼济天下。</description><language>zh-cn</language><lastBuildDate>Sat, 18 Feb 2012 16:18:29 GMT</lastBuildDate><pubDate>Sat, 18 Feb 2012 16:18:29 GMT</pubDate><ttl>60</ttl><item><title>项目开发中的一些思考</title><link>http://www.cppblog.com/feixuwu/archive/2012/02/16/165779.html</link><dc:creator>feixuwu</dc:creator><author>feixuwu</author><pubDate>Thu, 16 Feb 2012 13:00:00 GMT</pubDate><guid>http://www.cppblog.com/feixuwu/archive/2012/02/16/165779.html</guid><wfw:comment>http://www.cppblog.com/feixuwu/comments/165779.html</wfw:comment><comments>http://www.cppblog.com/feixuwu/archive/2012/02/16/165779.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/feixuwu/comments/commentRss/165779.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/feixuwu/services/trackbacks/165779.html</trackback:ping><description><![CDATA[<p><span style="font-size: 12pt">&nbsp; &nbsp; &nbsp; </span><span style="font-size: 10pt">&nbsp; 2011已经谢幕了，现在都流行总结，要是让我总结2011，可以用2个词来概括，辛苦、刺激。</span><br /><span style="font-size: 10pt">辛苦是因为2011基本上是加了一年班，从过完年开始，到2012年过年前最后一周，这一年来，是我感觉最辛苦的一年，好在最终</span><br /><span style="font-size: 10pt">项目算是打了个翻身仗，心里总算有了些慰藉。<br /><br /></span>&nbsp; &nbsp; &nbsp; &nbsp;<span style="font-size: 10pt">2011年游戏经历从技术封测、内测、公测到整改、重新内测公测，一路走来，遇到无数稀奇古怪的Bug，</span><br /><span style="font-size: 10pt">有时候压力大的时候，晚上都睡不着，脑子里回想着现场的一丝丝蛛丝马迹，希望能找到bug的原因，经历过无数次绝望到重生的喜悦，也有被猜忌不信任的痛苦，活脱脱就是一部部侦探剧情。</span><br /><span style="font-size: 10pt">&nbsp; 没有从事过游戏开发或者游戏没上线的同学很难理解:bug有这么难找吗？的确，如果是简单的空指针宕机，当然是好找的，用我们的话，这类问题是个傻子都能解决(其实不然，很多时候直接原因是空指针，</span><br /><span style="font-size: 10pt">真正的原因隐藏很深)，但是更多的是隐藏很深的问题，需要反复的分析现场，假设剧情才能得到灵感，然后推演，才可能得到结果，当然，这个和游戏逻辑的复杂度是分不开的。</span><br /><span style="font-size: 10pt">&nbsp; 具体的bug细节不便在此分析，但是大部分的问题，其实都是因为不正常的设计引起的，所以其实我一直在思考，在软件开发领域，其实也存在着"道",说通俗点叫客观规律，不按照道行事，迟早是要受到惩罚的。</span><br /><span style="font-size: 10pt">但是在游戏后台开发中，很多时候存在不同技术方案的矛盾，难以让人取舍，这些矛盾都是真实在很多项目存在的。</span><br /><br /></p>
<h2></h2>
<h2><span style="font-size: 24pt">动态内存还是静态内存</span></h2>
<p><font size="3"><span style="font-weight: normal; font-size: 12pt">&nbsp; &nbsp; &nbsp;</span><span style="font-weight: normal; font-size: 10pt"> 很多开发者由于担心内存泄露，在项目中禁止使用动态内存(当然这实际上几乎是做不到的)，使用对象池来避免动态内存，就是预先创建预计最大数量的对象，后续申请和归还的时候，都是操作对象池，<br /></span><span style="font-weight: normal; font-size: 10pt">避免动态new和delete。这样的项目还不少，我见过的就好几个。对象池的好处是显而易见的，基本上可以避免内存泄露。但是实际上，这种方式是把双刃剑，个人觉得在游戏项目中，这种方式弊大于利。<br /></span><span style="font-weight: normal; font-size: 10pt">主要弊端有下面几点：<br /></span><span style="font-weight: normal; font-size: 10pt">1、开发不方便，导致需要添加很多的对象池管理类，即使有模板帮忙，也是非常繁琐的。实际开发中，几乎不可能对这些小对象类都搞一个对象池管理类。<br /><br /></span><span style="font-weight: normal; font-size: 10pt">2、由于采用预先生成对象，一般会预估一个对象可能存在的最大数量，然后按照最大数量来创建，浪费内存。<br /></span><span style="font-weight: normal; font-size: 10pt">&nbsp; 的确，你没有内存泄露，但是你启动的时候就需要好几个G的内存，这个是内存浪费，好在现在server开发基本都是64位，没有地址空间的困扰了，但是，在大部分情况下浪费好几个G的内存，<br /></span><span style="font-weight: normal; font-size: 10pt">光想想都有点心疼。<br /><br /></span><span style="font-weight: normal; font-size: 10pt">3、引入了新的风险，由于采用对象池，申请新对象的时候，只是简单的pop一个空闲对象就可以了，很容易漏掉对象初始化的工作，在回收对象的时候，大部分开发者也很容易漏掉清理工作，或者初始化和<br /></span><span style="font-weight: normal; font-size: 10pt">清理工作过于简单，这样容易导致新对象被历史操作影响。曾经遇到过一个新FB所有传送点都打不开的问题，就是因为历史对象回收时数据没清理导致的。<br /></span><span style="font-weight: normal; font-size: 12pt"><br /></span><span style="font-weight: normal; font-size: 10pt">&nbsp; &nbsp; 回头来看对象池的优点，很多开发者坚持是为了解决内存碎片和内存泄露。先说内存碎片，暂且不说内存碎片真的是否有这么严重，退一步，其实内存碎片已经有很多的成熟解决方案了，自己重载smallObject还是<br /></span><span style="font-weight: normal; font-size: 10pt">采用标准的tcmalloc解决，都是非常轻松的。至于内存泄露，个人觉得这个问题其实是很好查的，也是c++程序员的基本要求。</span><span style="font-weight: normal; font-size: 14pt"><br /><br /></span></font></p>
<h2><span style="font-size: 24pt">分模块针对接口编程还是一锅粥</span></h2>
<p><span style="font-weight: normal; font-size: 10pt">&nbsp; &nbsp; &nbsp;这个问题单独提出来，几乎所有人都会说，当然是分模块针对接口开发了。和天下所有的事情一样，知易行难。由于游戏逻辑项目影响的地方非常多，比如死亡的时候，既需要判断死亡掉落，又需要处理任务状态，</span><br /><span style="font-weight: normal; font-size: 10pt">如果在战场和竞技场中，还要判断基数和得分等等，这就导致很多开发者不假思索的把所有的东西都揉在一起，你中有我，我中有你，我改你的代码你改我的。</span><br /><span style="font-weight: normal; font-size: 10pt">一个最简单的例子，我在项目中开发掉落功能，当把物品添加到玩家背包后，发现客户端没有更新背包，一查，居然还需要掉落的开发者自己构造数据包同步客户端，其实作为其他模块，根本不关心背包数据同步的细节。</span><br /><span style="font-weight: normal; font-size: 10pt">这个其实在现实生活中很常见，我委托背包模块添加一个物品，具体的细节是被由被委托人来负责的。将过多的细节交给其他模块处理，会导致复杂度增加，容易出现问题，对其他人来说，也是一个精力浪费，如果是一个复杂</span><br /><span style="font-weight: normal; font-size: 10pt">模块，你会发现需要了解太多的细节，修改太多自己不熟悉的代码，进而导致风险。还有一种观点，认为一锅粥的开发方式有助于了解游戏的各个业务模块，对这种观点，我是不以为然的，每天陷入到繁琐的细节，真的对熟悉业务有好处吗？或许闲下来玩玩游戏更有帮助，而且，这么乱的代码，看起来也是非常累的。分模块开发，具体的办法，游戏编程精粹5上有篇文章写得很好，这里不扩展了。</span><br /><br /><span style="font-size: 24pt">真的需要禁用STL吗</span><br /><br /><span style="font-weight: normal; font-size: 10pt">&nbsp; 不止一次在和其他项目交流的资料里看到对方很威严的宣称在项目里禁止使用STL。说实话，我还真没觉得STL有什么不好。见过太多这类项目自己重复实现一个个蹩脚的排序算法、容器等等。<br /></span><span style="font-weight: normal; font-size: 10pt">大部分人一般都会根据经验选择使用自己熟悉的技术，这个无可厚非，但是像这样明着禁止使用STL，真不知道如何能理直气壮。其实大部分不用STL的理由，基本上都是不熟悉，完全没有足够的理由禁止使用。</span><span style="font-weight: normal; font-size: 12pt"><br /><br /></span><span style="font-size: 24pt">游戏开发无技术含量？<br /></span>&nbsp;<br /><span style="font-weight: normal; font-size: 12pt">&nbsp; </span><span style="font-weight: normal; font-size: 10pt">&nbsp; 曾经多次听到行业内的兄弟有此感慨，确实，游戏逻辑复杂度非常高，架构上大部分都是类似的。但是这并不说明游戏后台开发复杂度不高，如何将游戏开发逻辑复杂剥离开来，做到稳定高效开发，其实还是有很多<br /></span><span style="font-weight: normal; font-size: 10pt">东西可以探讨的，看看那些项目，大部分都是一锅粥，需要什么功能就蛮干，加上去，这样确实毫无技术含量，都是蛮干。所以，一件事情是否有技术含量，不光是看事情本身，还要看怎么干，蛮干和苦干，那是最没有技术<br /></span><span style="font-weight: normal; font-size: 10pt">含量的方式了，程序员还是要有强烈的&#8220;偷懒&#8221;意识。</span><br /><br /></p>
<p>&nbsp;</p><img src ="http://www.cppblog.com/feixuwu/aggbug/165779.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/feixuwu/" target="_blank">feixuwu</a> 2012-02-16 21:00 <a href="http://www.cppblog.com/feixuwu/archive/2012/02/16/165779.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>linux下PageHeap</title><link>http://www.cppblog.com/feixuwu/archive/2011/05/14/146395.html</link><dc:creator>feixuwu</dc:creator><author>feixuwu</author><pubDate>Sat, 14 May 2011 13:16:00 GMT</pubDate><guid>http://www.cppblog.com/feixuwu/archive/2011/05/14/146395.html</guid><wfw:comment>http://www.cppblog.com/feixuwu/comments/146395.html</wfw:comment><comments>http://www.cppblog.com/feixuwu/archive/2011/05/14/146395.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/feixuwu/comments/commentRss/146395.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/feixuwu/services/trackbacks/146395.html</trackback:ping><description><![CDATA[<h1>&nbsp;问题</h1>&nbsp;&nbsp; 最近游戏开始技术封测了，不过刚刚上线3个小时，Server就挂了，挂在框架代码里，一个不可能挂的地方。<br />从CallStack看，是在获取数据时发送请求包的时候挂的，由于框架部分是其他部门的同事开发的，所以查问题的时候就拉上他们了，<br />大家折腾了2天，没有实质性的进展，服务器还是基本上每3个小时宕机一次。由于上层逻辑大部分都在我那，所以压力比较大，宕机的直接原因是hashtable的一个桶的指针异常，<br />这个hashtable是框架代码的一个内部成员，按道理我们是无从破坏的，只有可能是多线程环境下迭代器损坏导致的。<br />但是框架代码在这个地方确实无懈可击，所以真正的原因应该还是上层代码破坏了堆内存，很可能是一个memcpy越界导致的。这毕竟是个猜想，如何找到证据呢，这是个问题。<br />把所有代码里的memcpy浏览了一遍，没有发现明显问题。<br />
<h1>猜测</h1>&nbsp; 一般游戏中比较容易出现但是不好查的问题很多时候都是脚本（lua）导致的，我们的脚本部分是一个同事几年前写的，在几个产品中都使用过，按道理没这么脆弱，不过老大还是和最初开发这个模块的部门沟通了下，<br />还真发现问题了，赶紧拿了新的版本更新上去。经过一天的观察，服务器没有宕机了，OK，问题碰巧解决了,背了这么久的黑锅，终于放下来了。<br /><br />
<h1>PageHeap</h1>&nbsp;&nbsp; 假如没有碰巧解决了这个问题，正常的思路该如何解决这个问题呢，这个时候我怀念windows了，在windows下有PageHeap来解决这类写越界的问题。基本思路就是每次分配内存的时候，都将内存的结尾放在页的边缘，紧接着这块内存分配一块不能写的内存，这样，一旦写越界，就会写异常，导致宕机。linux下没有现成的工具，但是linux提供了mmap功能，我们可以自己实现这样一个功能，当然，这一切都不用自己动手了，tcmalloc已经包含了<br />这个功能了，不过在文档里基本没有介绍，我也是在阅读tcmalloc代码时看到的，这个功能默认是关闭的，打开这个开关需要改写代码：<br /><br />这个代码在debugallocation.cc里：<br /><br />DEFINE_bool(malloc_page_fence,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; EnvToBool("TCMALLOC_PAGE_FENCE", false),<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "Enables putting of memory allocations at page boundaries "<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "with a guard page following the allocation (to catch buffer "<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "overruns right when they happen).");<br />把false改成true就可以了。<br />想要在项目里加入PageHeap功能，只需要链接的时候加上 -ltcmalloc_debug即可。把它加入项目中，试着运行下，直接挂了，<br />仔细一看，原来是项目中很多成员变量没有初始化导致的，tcmalloc_debug会自动将new 和malloc出来的内存初始化为指定值，这样，一旦变量没有初始化，很容易就暴露了。<br />修改完这个问题后，编译，再运行，还是挂，这个是mprotect的时候挂的，错误是内存不够，这怎么可能呢，其实是达到了资源限制了。<br /><span class="Apple-style-span" style="font-size: 12pt; line-height: normal; font-family: Verdana,Arial,Helvetica">echo 128000 &gt; /proc/sys/vm/max_map_count <br />把map数量限制加大,再运行，OK了！<br /></span>&nbsp; <br />&nbsp; 但是游戏Server启动后，发现一个问题，CPU长期处于100%，导致登陆一个玩家都很困难，gdb中断后，info thread，发现大部分的操作都在mmap和mprotect,最开始<br />怀疑我的linux版本有问题，导致这2个AP慢，写了测试程序试了下，发现其实API不慢，估计是频繁调用导致的。<br />所以得换种思路优化下才可以，其实大部分情况下，我们free的时候，无需将页面munmap掉，可以先cache进来，下次分配的时候，如果有，直接拿来用就可以了。<br />最简单的cache算法就是定义一个void* s_pageCache[50000]数组，页面数相同的内存组成一个链表，挂在一个数组项下，这个很像STL的小内存处理，我们可以将mmap出来的内存的<br />前面几个字节(一个指针大小)用于索引下一个freePage。当然这个过程需要加锁，不能用pthread的锁（因为他们会调用malloc等内存分配函数），必须用spinlock，从linux源码里直接抄一个过来即可。<br />static void*&nbsp;&nbsp;&nbsp;s_pagePool[MAX_PAGE_ALLOC]={0};<br /><br />malloc的时候，先从pagePool里面获取:<br />// 先从pagePool找<br />&nbsp;void* pFreePage = NULL;<br />&nbsp;spin_lock(&amp;s_pageHeapLock);<br />&nbsp;assert(nPageNum &lt; MAX_PAGE_ALLOC);<br />&nbsp;if(s_pagePool[nPageNum])<br />&nbsp;{<br />&nbsp;&nbsp;&nbsp;pFreePage = s_pagePool[nPageNum];<br />&nbsp;&nbsp;&nbsp;void* pNextFreePage = *((void**)pFreePage);<br />&nbsp;&nbsp;&nbsp;s_pagePool[nPageNum] = pNextFreePage;<br />&nbsp;}<br />&nbsp;spin_unlock(&amp;s_pageHeapLock);<br /><br />free内存的时候，直接放到pagePoll里:<br />spin_lock(&amp;s_pageHeapLock);<br />&nbsp;assert(nPageNum &lt; MAX_PAGE_ALLOC);<br />&nbsp;void* pNextFree = s_pagePool[nPageNum];<br />&nbsp;*(void**)pAddress = pNextFree;<br />&nbsp;s_pagePool[nPageNum] = pAddress;<br />&nbsp;<br />&nbsp;spin_unlock(&amp;s_pageHeapLock);<br /><br />编译、运行,OK了，CPU迅速降下来了，空载的时候不到1%,而且也能达到检测写溢出的问题。<br /><br /><img src ="http://www.cppblog.com/feixuwu/aggbug/146395.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/feixuwu/" target="_blank">feixuwu</a> 2011-05-14 21:16 <a href="http://www.cppblog.com/feixuwu/archive/2011/05/14/146395.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>core和CallStack</title><link>http://www.cppblog.com/feixuwu/archive/2011/04/10/143871.html</link><dc:creator>feixuwu</dc:creator><author>feixuwu</author><pubDate>Sun, 10 Apr 2011 06:47:00 GMT</pubDate><guid>http://www.cppblog.com/feixuwu/archive/2011/04/10/143871.html</guid><wfw:comment>http://www.cppblog.com/feixuwu/comments/143871.html</wfw:comment><comments>http://www.cppblog.com/feixuwu/archive/2011/04/10/143871.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/feixuwu/comments/commentRss/143871.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/feixuwu/services/trackbacks/143871.html</trackback:ping><description><![CDATA[<p>&nbsp; 最近项目开始集中测试了，服务器程序经常crash，由于服务器一般情况下都是关闭了core的，所以好几次都只能通过杂乱的日志来定位问题。<br>当然，我们可以通过ulimit来打开core开关，不过这可能带来新的问题：我们的服务器程序每个core文件大概有1G多，测试期间如果频繁crash,没有注意及时清理，一不小心就会把磁盘写满，<br>而且core文件毕竟是和进程程序相关的，有时候找相应版本也是个麻烦事。<br><br>能否在程序crash的时候，将callStack以及参数和局部变量都记录到日志里？<br>这个技术其实在游戏客户端已经用了很多年了，一般游戏客户端crash后，都会弹出一个是否发送错误的选择框，其实就是发送的CallStack的日志和MiniDUmp文件。<br>要想记录CallStack就必然涉及到Stack的遍历，linux下的Stack遍历使用很简单，简单的backtrace就可以搞定，man backtrace就有现成的例子，<br>这比windows下复杂的头疼的StackWalk好用的多。<br><br>解决了Stack遍历问题后，还剩下一个问题：如何在程序crash的时候得到通知执行我们自己的dump代码？<br>在Windwos下有SEH异常来实现这个功能，而linux下可以通过使用信号在进程crash的时候执行自己的处理代码。<br><br>好了，开始写个简单代码测试下:<br>首先设置几个主要crash信号的处理函数<br>signal(SIGSEGV, &amp;DumpHelper::OnCrash);<br>signal(SIGABRT, &amp;DumpHelper::OnCrash);<br>signal(SIGFPE, &amp;DumpHelper::OnCrash);<br><br>在OnCrash里我们用前面提到的backtrace系列函数，来记录堆栈:<br>void* szStackFrame[100];<br>int nFrameCount = backtrace(szStackFrame, 100);<br>char** strFrameInfo = backtrace_symbols(szStackFrame, nFrameCount);&nbsp;<br>char szDumpFileName[1024] = {0};<br>snprintf(szDumpFileName, sizeof(szDumpFileName), "dump_%u.log", (unsigned int)time(NULL) );<br>FILE* pFile = fopen(szDumpFileName, "wb");<br>if(!pFile) return;<br>for(int i = 0; i &lt; nFrameCount; i++)<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;fprintf(pFile, "%s\n", strFrameInfo[i]);<br>}<br>fclose(pFile);<br>free(strFrameInfo);<br><br>接着，设置几个嵌套调用的函数：<br>void fun()<br>{<br>&nbsp;//assert(0);<br>&nbsp;int* p = NULL;<br>&nbsp;*p =3;<br>}</p>
<p>void fun1()<br>{<br>&nbsp;fun();<br>}</p>
<p>void fun2()<br>{<br>&nbsp;fun1();<br>}</p>
<p>void fun3()<br>{<br>&nbsp;fun2();<br>}<br><br>最后，我们在main函数里执行fun3,注意编译的时候带上-rdynamic 选项。<span class=Apple-style-span style="WORD-SPACING: 0px; FONT: medium Simsun; TEXT-TRANSFORM: none; COLOR: rgb(0,0,0); TEXT-INDENT: 0px; WHITE-SPACE: normal; LETTER-SPACING: normal; BORDER-COLLAPSE: separate; orphans: 2; widows: 2; webkit-border-horizontal-spacing: 0px; webkit-border-vertical-spacing: 0px; webkit-text-decorations-in-effect: none; webkit-text-size-adjust: auto; webkit-text-stroke-width: 0px"></p>
运行下，果然可以打印基本的堆栈，不过马上，发现了新的问题：这个堆栈信息也太简陋了，只有调用函数的名字，其余的参数、局部变量完全没有，<br>这个和gdb能看到的callStack差距也太大了。<br>解决这个问题最简单的办法就是用gdb来打印堆栈,在这里，gdb和其他程序有区别，如果你试图通过 echo "bt"|gdb -p XXX&gt;a.txt来获得堆栈，那将会非常失望，<br>根本不起作用，google了下，基本没什么解决办法。<br>不过gdb 可以从文件读入指令，例如 gdb XXX&lt;cmddata，这给了我们机会，<br>system("echo&nbsp; \"bt full|gcore\"&gt;testcmd");<br>&nbsp;&nbsp;char dbx[160]={0};<br>&nbsp;&nbsp;&nbsp; &nbsp;sprintf(dbx, "gdb -p %d ./main&lt;testcmd &gt;gdbdump_%d.log", getpid(), getpid() );<br>&nbsp;&nbsp;system(dbx);<br><br>测试运行，发现可以打印详细的堆栈，不过，要求机器上有gdb.<br>上面的命令还dump了一个core文件，不过这个core文件的堆栈信息是错误的，我不知道为什么。。。。<br><br>多线程环境下使用上述办法，只能输出一个线程的堆栈，需要先获取线程数目，然后逐个线程打印堆栈。<br><br>最后，为了避免影响正常的coredump,要在OnCrash的处理函数里将信号的处理函数设置为默认。<br>如果我一定要有core呢，setrlimit吧，去掉core限制即可。</span> 
<img src ="http://www.cppblog.com/feixuwu/aggbug/143871.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/feixuwu/" target="_blank">feixuwu</a> 2011-04-10 14:47 <a href="http://www.cppblog.com/feixuwu/archive/2011/04/10/143871.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>GCC项目编译速度优化</title><link>http://www.cppblog.com/feixuwu/archive/2011/03/19/142210.html</link><dc:creator>feixuwu</dc:creator><author>feixuwu</author><pubDate>Sat, 19 Mar 2011 08:39:00 GMT</pubDate><guid>http://www.cppblog.com/feixuwu/archive/2011/03/19/142210.html</guid><wfw:comment>http://www.cppblog.com/feixuwu/comments/142210.html</wfw:comment><comments>http://www.cppblog.com/feixuwu/archive/2011/03/19/142210.html#Feedback</comments><slash:comments>5</slash:comments><wfw:commentRss>http://www.cppblog.com/feixuwu/comments/commentRss/142210.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/feixuwu/services/trackbacks/142210.html</trackback:ping><description><![CDATA[&nbsp;&nbsp; 我们的新项目是在linux平台下运行的，本人是Linux和windows下都开发过，我呆的2个linux后台项目都是所有代码放在一块，编译成一个可<br>执行文件，基本不考虑编译成动态库，所有代码的头文件依赖也是一团糟，随着项目的增大，编译速度越来越慢，到后来编译一个项目4进程同时编译都需要10来分钟。<br>&nbsp; <br>&nbsp; 其实分析下可以发现，主要的编译速度损耗在头文件上，尤其是模板相关的头文件。VC有一个预编译头文件技术，将常用的公共头文件放在一起，预先编译成pch文件，这样<br>可以加快编译速度。gcc到底有没有类似技术呢，打开gcc的手册搜索了precompiled，发现还真有相关介绍，使用方法也很简单。<br>&nbsp;<br>主要是以下步骤：<br>&nbsp; 1、在项目下建立一个 stdafx.h的文件，包含了大部分公共头文件。在每个cpp最开始都#include "stdafx.h"。cpp文件包含了这个预编译头文件后，就可以将原来和<br>stdafx .h 里头文件重复的内容删除了，尤其是模板相关的头文件，另外，非PCH的头文件里尽量少包含其他头文件。&nbsp;&nbsp;&nbsp; &nbsp; <br>&nbsp; 2、修改makefile文件, 加入OBJ对 gch的依赖,用一个简单的项目做示例，一看就明白<br>&nbsp;&nbsp;&nbsp;
<div style="FONT-SIZE: 12px; COLOR: #a0a0a0">TARGET=TimerTest<br>PCH=stdafx.h.gch<br>PCH_H=stdafx.h<br>OBJ=stdafx.o TimerManager.o TimerTest.o<br><br>%.o:%.cpp<br>&nbsp;&nbsp;&nbsp; g++ -Wall -c -g $^ -o $@<br><br>$(TARGET):$(OBJ)<br>&nbsp;&nbsp;&nbsp; g++ -g&nbsp; $^ -o $@<br><br><br>pch.d:stdafx.cpp<br>&nbsp;&nbsp;&nbsp; g++ -g -MM stdafx.cpp |sed 's/stdafx.o/stdafx.h.gch/'&gt;$@<br><br>-include pch.d<br><br>$(OBJ):$(PCH)<br>$(PCH):<br>&nbsp;&nbsp;&nbsp; g++ $(PCH_H)<br><br>clean:<br>&nbsp;&nbsp;&nbsp; rm -f $(OBJ) $(PCH)<br></div>
<br>&nbsp;&nbsp;&nbsp; 完成以上内容后，make clean,再重新编译，初步估计只需要2分钟！！&nbsp; 整整优化了4-5倍。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br><br>
<img src ="http://www.cppblog.com/feixuwu/aggbug/142210.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/feixuwu/" target="_blank">feixuwu</a> 2011-03-19 16:39 <a href="http://www.cppblog.com/feixuwu/archive/2011/03/19/142210.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>定时器的实现</title><link>http://www.cppblog.com/feixuwu/archive/2011/03/13/141744.html</link><dc:creator>feixuwu</dc:creator><author>feixuwu</author><pubDate>Sun, 13 Mar 2011 14:06:00 GMT</pubDate><guid>http://www.cppblog.com/feixuwu/archive/2011/03/13/141744.html</guid><wfw:comment>http://www.cppblog.com/feixuwu/comments/141744.html</wfw:comment><comments>http://www.cppblog.com/feixuwu/archive/2011/03/13/141744.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/feixuwu/comments/commentRss/141744.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/feixuwu/services/trackbacks/141744.html</trackback:ping><description><![CDATA[&nbsp; 最新换了个项目组，阅读代码后，发现Server端代码居然没有事件和定时器。由于没有事件，所以各个模块代码互相调用的地方特别多，导致代码结构混乱，所有代码都放在一块，乱成一锅粥了。<br>没有定时器，所有需要定时的任务，都只能添加类似OnUpdate的函数，在主循环的时候执行。定时需求少的时候，看不出明显的问题，但是一旦这种需求多了，尤其是很多内部对象有定时需求的时候，<br>这个问题就比较明显了，写好了OnUpdate后，还要建立一条从主循环MainLoop到自身OnUpdate的调用链。<br>&nbsp;<br>&nbsp; 事件其实就是一个广播和订阅的关系，Delegate就是实现这样一套机制的利器，目前Delegate的实现主要有2种，一种是CodeProject上的一个FastDelegate实现，另外一个比较典型的实现就是boost的<br>实现了，无论采取哪种实现方案，实现难度都不算太大。<br>&nbsp; Server当前框架对定时器无任何支持，只有一个DoMainLoop的函数可以派生来运行自己的定时逻辑。<br>&nbsp; 我原来都是用的ACE封装的组件，用了一段时间也没发现明显问题，不过ACE的定时器不太适合在这个新项目用，主要原因有如下几点：<br>&nbsp; 1、ACE库太大了，不想仅仅为了定时器引入一个这么庞大的库。<br>&nbsp; 2、ACE的定时器需要额外启动一个定时器线程，定时任务是在定时器线程跑的，而我们的项目逻辑其实是在单个线程运行的，如果直接采用ACE定时器，会给逻辑带来额外的复杂度。由于整个逻辑线程的框架是公共模块，手头也没有代码，所以将定时器线程的任务发送到主逻辑线程运行也是不可行的。<br>&nbsp; 3、ACE的定时器有很多种，TIMER_QUEUE、TIMER_WHELL、TIMER_HEAP等，个人感觉这些定时器的插入、取消操作都比较耗时，加以改装放到主线程run的带价将会很大。<br><br>其实linux内核就有一个比较高性能的定时器，代码在kernel/Timer.c里， 2.6内核的定时器代码更是简洁。<br>linux的定时任务都是以jiffie
为单位的，linux将所有定时任务分为5个阶梯，<br>struct tvec {<br>&nbsp;&nbsp; &nbsp;struct list_head vec[TVN_SIZE];<br>};<br><br>struct tvec_root {<br>&nbsp;&nbsp; &nbsp;struct list_head vec[TVR_SIZE];<br>};<br><br>struct tvec_base {<br>&nbsp;&nbsp; &nbsp;spinlock_t lock;<br>&nbsp;&nbsp; &nbsp;struct timer_list *running_timer;<br>&nbsp;&nbsp; &nbsp;unsigned long timer_jiffies;<br>&nbsp;&nbsp; &nbsp;struct tvec_root tv1;<br>&nbsp;&nbsp; &nbsp;struct tvec tv2;<br>&nbsp;&nbsp; &nbsp;struct tvec tv3;<br>&nbsp;&nbsp; &nbsp;struct tvec tv4;<br>&nbsp;&nbsp; &nbsp;struct tvec tv5;<br>} ____cacheline_aligned;
<br><br>对一个新的定时任务，处理方法如下:<br>static void internal_add_timer(struct tvec_base *base, struct timer_list *timer)<br>{<br>&nbsp;&nbsp; &nbsp;unsigned long expires = timer-&gt;expires;<br>&nbsp;&nbsp; &nbsp;unsigned long idx = expires - base-&gt;timer_jiffies;<br>&nbsp;&nbsp; &nbsp;struct list_head *vec;<br><br>&nbsp;&nbsp; &nbsp;if (idx &lt; TVR_SIZE) {<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;int i = expires &amp; TVR_MASK;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;vec = base-&gt;tv1.vec + i;<br>&nbsp;&nbsp; &nbsp;} else if (idx &lt; 1 &lt;&lt; (TVR_BITS + TVN_BITS)) {<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;int i = (expires &gt;&gt; TVR_BITS) &amp; TVN_MASK;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;vec = base-&gt;tv2.vec + i;<br>&nbsp;&nbsp; &nbsp;} else if (idx &lt; 1 &lt;&lt; (TVR_BITS + 2 * TVN_BITS)) {<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;int i = (expires &gt;&gt; (TVR_BITS + TVN_BITS)) &amp; TVN_MASK;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;vec = base-&gt;tv3.vec + i;<br>&nbsp;&nbsp; &nbsp;} else if (idx &lt; 1 &lt;&lt; (TVR_BITS + 3 * TVN_BITS)) {<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;int i = (expires &gt;&gt; (TVR_BITS + 2 * TVN_BITS)) &amp; TVN_MASK;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;vec = base-&gt;tv4.vec + i;<br>&nbsp;&nbsp; &nbsp;} else if ((signed long) idx &lt; 0) {<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;/*<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp; * Can happen if you add a timer with expires == jiffies,<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp; * or you set a timer to go off in the past<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp; */<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;vec = base-&gt;tv1.vec + (base-&gt;timer_jiffies &amp; TVR_MASK);<br>&nbsp;&nbsp; &nbsp;} else {<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;int i;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;/* If the timeout is larger than 0xffffffff on 64-bit<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp; * architectures then we use the maximum timeout:<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp; */<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (idx &gt; 0xffffffffUL) {<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;idx = 0xffffffffUL;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;expires = idx + base-&gt;timer_jiffies;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;}<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;i = (expires &gt;&gt; (TVR_BITS + 3 * TVN_BITS)) &amp; TVN_MASK;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;vec = base-&gt;tv5.vec + i;<br>&nbsp;&nbsp; &nbsp;}<br>&nbsp;&nbsp; &nbsp;/*<br>&nbsp;&nbsp; &nbsp; * Timers are FIFO:<br>&nbsp;&nbsp; &nbsp; */<br>&nbsp;&nbsp; &nbsp;list_add_tail(&amp;timer-&gt;entry, vec);<br>}<br>从上可以看到Linux对定时器的处理：对即将在TVR_SIZE
个jiffies内到达的定时任务，将它挂到第一组tv1
下，具体就是挂到expires &amp; TVR_MASK
对应的列表上去。<br>同一个jiffies到达的定时器是挂在同一个链表的。<br>同理，挂到第二个组的是 到期时间小于 1 &lt;&lt; (TVR_BITS + TVN_BITS) jiffies的。<br>挂到第三个组的是 到期时间小于1 &lt;&lt; (TVR_BITS + 2 * TVN_BITS)
jiffies的。<br>挂到第四个组的是 到期时间小于 1 &lt;&lt; (TVR_BITS + 3 * TVN_BITS)
jiffies的。<br>超过1 &lt;&lt; (TVR_BITS + 3 * TVN_BITS) 的挂到第五组。<br>这样，所有到期的任务都会在第一组。任何时刻都可以直接通过当前jiffies&amp;TVR_SIZE
来找到需要运行的定时器任务列表，定时器的插入效率就是O(1)。<br><br>下面是定时器的运行代码：<br>static int cascade(struct tvec_base *base, struct tvec *tv, int index)<br>{<br>&nbsp;&nbsp; &nbsp;/* cascade all the timers from tv up one level */<br>&nbsp;&nbsp; &nbsp;struct timer_list *timer, *tmp;<br>&nbsp;&nbsp; &nbsp;struct list_head tv_list;<br><br>&nbsp;&nbsp; &nbsp;list_replace_init(tv-&gt;vec + index, &amp;tv_list);<br><br>&nbsp;&nbsp; &nbsp;/*<br>&nbsp;&nbsp; &nbsp; * We are removing _all_ timers from the list, so we<br>&nbsp;&nbsp; &nbsp; * don't have to detach them individually.<br>&nbsp;&nbsp; &nbsp; */<br>&nbsp;&nbsp; &nbsp;list_for_each_entry_safe(timer, tmp, &amp;tv_list, entry) {<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;BUG_ON(tbase_get_base(timer-&gt;base) != base);<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;internal_add_timer(base, timer);<br>&nbsp;&nbsp; &nbsp;}<br><br>&nbsp;&nbsp; &nbsp;return index;<br>}<br><br>#define INDEX(N) ((base-&gt;timer_jiffies &gt;&gt; (TVR_BITS + (N) * TVN_BITS)) &amp; TVN_MASK)<br><br>/**<br>&nbsp;* __run_timers - run all expired timers (if any) on this CPU.<br>&nbsp;* @base: the timer vector to be processed.<br>&nbsp;*<br>&nbsp;* This function cascades all vectors and executes all expired timer<br>&nbsp;* vectors.<br>&nbsp;*/<br>static inline void __run_timers(struct tvec_base *base)<br>{<br>&nbsp;&nbsp; &nbsp;struct timer_list *timer;<br><br>&nbsp;&nbsp; &nbsp;spin_lock_irq(&amp;base-&gt;lock);<br>&nbsp;&nbsp; &nbsp;while (time_after_eq(jiffies, base-&gt;timer_jiffies)) {<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;struct list_head work_list;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;struct list_head *head = &amp;work_list;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;int index = base-&gt;timer_jiffies &amp; TVR_MASK;<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;/*<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp; * Cascade timers:<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp; */<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (!index &amp;&amp;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;(!cascade(base, &amp;base-&gt;tv2, INDEX(0))) &amp;&amp;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;(!cascade(base, &amp;base-&gt;tv3, INDEX(1))) &amp;&amp;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;!cascade(base, &amp;base-&gt;tv4, INDEX(2)))<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;cascade(base, &amp;base-&gt;tv5, INDEX(3));<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;++base-&gt;timer_jiffies;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;list_replace_init(base-&gt;tv1.vec + index, &amp;work_list);<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;while (!list_empty(head)) {<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;void (*fn)(unsigned long);<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;unsigned long data;<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;timer = list_first_entry(head, struct timer_list,entry);<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;fn = timer-&gt;function;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;data = timer-&gt;data;<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;timer_stats_account_timer(timer);<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;set_running_timer(base, timer);<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;detach_timer(timer, 1);<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;spin_unlock_irq(&amp;base-&gt;lock);<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;{<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;int preempt_count = preempt_count();<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;fn(data);<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (preempt_count != preempt_count()) {<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;printk(KERN_ERR "huh, entered %p "<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "with preempt_count %08x, exited"<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; " with %08x?\n",<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fn, preempt_count,<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; preempt_count());<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;BUG();<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;}<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;}<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;spin_lock_irq(&amp;base-&gt;lock);<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;}<br>&nbsp;&nbsp; &nbsp;}<br>&nbsp;&nbsp; &nbsp;set_running_timer(base, NULL);<br>&nbsp;&nbsp; &nbsp;spin_unlock_irq(&amp;base-&gt;lock);<br>}<br>当第一组运行完一轮后，需要将tv2的一组新的定时任务加到第一组。这就好比时钟的指针，秒针运行一圈后，分针步进一格，后续的调整都是类似。
<br>cascade
就是负责将下一组的定时任务添加到前面的任务阶梯。只有当第一轮的定时任务全部运行完毕后，才会需要从第二轮调入新的任务，只有第二级别的任务都调入完毕后，才需要从第三轮的定时任务调入新的任务：<br>&nbsp;if (!index &amp;&amp;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;(!cascade(base, &amp;base-&gt;tv2, INDEX(0))) &amp;&amp;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;(!cascade(base, &amp;base-&gt;tv3, INDEX(1))) &amp;&amp;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;!cascade(base, &amp;base-&gt;tv4, INDEX(2)))<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;cascade(base, &amp;base-&gt;tv5, INDEX(3));
<br><br>这就是负责调整的代码，相当的简洁。<br>参照上述代码实现一个定时器后，加入4000个定时任务：<br>&nbsp;&nbsp;&nbsp; for(int i = 1; i &lt; 4000; i++)<br>&nbsp;&nbsp; &nbsp;{<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;g_TimerHandle[i] = g_timerManager.setTimer(&amp;tmpSink1, i, i*10, "ss");<br>&nbsp;&nbsp; &nbsp;}
<br>从10毫秒到4000*10毫秒,运行后，测试下性能，<br>函数名&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 执行次数&nbsp;&nbsp;&nbsp; 最小时间&nbsp;&nbsp;&nbsp;&nbsp; 平均时间&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 最大时间<br>TimerManager::runTimer&nbsp;&nbsp;&nbsp; 2170566&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 10 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 10&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3046&nbsp;&nbsp;&nbsp; <br>可以看到，除了个别时间是因为线程切换导致数据比较大外,平均每次运行runTimer的时间是10微秒。<br>这个时间还包括每个定时器的执行消耗，效率还是不错的。<br><img src ="http://www.cppblog.com/feixuwu/aggbug/141744.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/feixuwu/" target="_blank">feixuwu</a> 2011-03-13 22:06 <a href="http://www.cppblog.com/feixuwu/archive/2011/03/13/141744.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Ogre初体验</title><link>http://www.cppblog.com/feixuwu/archive/2010/09/25/127669.html</link><dc:creator>feixuwu</dc:creator><author>feixuwu</author><pubDate>Sat, 25 Sep 2010 13:44:00 GMT</pubDate><guid>http://www.cppblog.com/feixuwu/archive/2010/09/25/127669.html</guid><wfw:comment>http://www.cppblog.com/feixuwu/comments/127669.html</wfw:comment><comments>http://www.cppblog.com/feixuwu/archive/2010/09/25/127669.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/feixuwu/comments/commentRss/127669.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/feixuwu/services/trackbacks/127669.html</trackback:ping><description><![CDATA[&nbsp; 最近游戏又要封测了，工作比较紧张，晚上下班了比较累，回家懒得写代码了，不过顺便倒是继续完成了对 新剑侠情缘（和月影传说的资源格式相同）的资源逆向。完成了资源逆向后，突然兴致来了，写了个简单的地图查看器，到目前为止，一切运行正常。后来做了个简单的Demo，实现了基本的寻路和技能动画播放，其实新剑侠情缘原本的技能效果以今天的眼光看起来也还可以，即便如此，我还是集成了hge的粒子系统进去，试了下效果，还是挺奇怪的。<br>做完了这些之后，本想为我的PSP山寨一个新剑侠情缘。不料后来连续加了好几天班，加了几天班之后，人也懒了，山寨游戏的事情也就无疾而终了。<br>前面写过几篇逆向工程的文章，前几天翻出来看了下，感觉像是另一个人写的天书，我自己看自己的文章尚且如此，别人就更不用说了，其实对大部分人而言，关心的只是逆向的成果。对新剑侠情缘的资源和相关渲染感兴趣的朋友可以单独Email我。 <br>&nbsp; 开始阅读Ogre代码正是在这百无聊赖的状态下开始的，Ogre推出来很多年了，貌似05年就听说朋友说起过这个项目，不过我一向是专注服务端开发，对客户端开发经验不是很多，在3D领域就完全是的新手了，所以一直也没仔细研究。这几天拿起原来下载的一个版本，简单读了下代码。<br>Ogre的结构还是很清晰的，和手册上说的一样，主要就是那几个对象，Demo大部分也很简单，代码量不多，看起来很振奋人心。<br>但是对我这样的新手来说，首先想了解的当然是渲染流程。 Ogre的渲染流程确实会让3D新手不适应，它是从RenderTarget开始的，一个RenderTarget可以有几个ViewPort，每个ViewPort都有一个独立的摄像机，这可以实现同屏幕多个渲染。<br>通过ViewPort对象的update调用<br>&nbsp;mCamera-&gt;_renderScene(this, mShowOverlays);
<br>来执行场景渲染，而场景渲染里，最重要的要算_findVisibleObjects了，<br>这个函数将可见的物体添加到渲染队列里，这个函数非常的绕，里面还用到了Vistor,精神不好容易被绕晕，好在我挺住了，熬过来了。<br>熟悉了大致的渲染流程后，我觉得该写点东西来实战了。<br>3D教程的开始一般会教大家画三角形，所以我也想用Ogre画个三角形玩玩，<br>一开始，我也想从像那些Demo一样从ExampleApplication继承，不过我发现这样启动太慢了，而且我不需要加载那么多的材质，<br>所以自己手动Configure了，代码如下:<br>Ogre::LogManager* pLogManager = new Ogre::LogManager;<br>&nbsp;&nbsp; &nbsp;Ogre::Log* pLog = pLogManager-&gt;createLog("ogreLearn1.log");<br>&nbsp;&nbsp; &nbsp;pLog-&gt;setDebugOutputEnabled(true);<br><br>&nbsp;&nbsp; &nbsp;Ogre::Root* pRootObject = new Ogre::Root;<br>&nbsp;&nbsp; &nbsp;pRootObject-&gt;loadPlugin("RenderSystem_Direct3D9_d.dll");<br>&nbsp;&nbsp; &nbsp;pRootObject-&gt;loadPlugin("Plugin_OctreeSceneManager_d.dll");<br>&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp; Ogre::RenderSystem* pRenderSystem = pRootObject-&gt;getRenderSystemByName("Direct3D9 Rendering Subsystem");<br>&nbsp;&nbsp;&nbsp; pRenderSystem-&gt;setConfigOption("Full Screen", "False");<br>&nbsp;&nbsp;&nbsp; pRootObject-&gt;setRenderSystem(pRenderSystem);&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; Ogre::RenderWindow* pRenderWindow = pRootObject-&gt;initialise(true); <br><br>编译测试了下，可以正常运行，不过发现屏幕是花的，我还没有创建场景呢，继续添加摄像机和ViewPort以及场景<br>// 创建场景和摄像机以及ViewPort<br>&nbsp;&nbsp; &nbsp;Ogre::SceneManager* pSceneManager = pRootObject-&gt;createSceneManager(Ogre::ST_GENERIC, "OgreLearn1");<br>&nbsp;&nbsp; &nbsp;Ogre::Camera* pCamera = pSceneManager-&gt;createCamera("MainCamara");<br>&nbsp;&nbsp; &nbsp;pCamera-&gt;setPosition(0.0, 0.0, -20.0);<br>&nbsp;&nbsp; &nbsp;pCamera-&gt;lookAt(0, 0, 0);<br>&nbsp;&nbsp; &nbsp;pCamera-&gt;setNearClipDistance(2);<br><br>&nbsp;&nbsp; &nbsp;Ogre::Viewport* pViewPort = pRenderWindow-&gt;addViewport(pCamera);<br>&nbsp;&nbsp; &nbsp;pViewPort-&gt;setBackgroundColour(Ogre::ColourValue(0, 0, 0, 1.0f) );<br>&nbsp;&nbsp; &nbsp;pCamera-&gt;setAspectRatio(pViewPort-&gt;getActualWidth()/pViewPort-&gt;getActualHeight() );
<br><br>最后加上pRootObject-&gt;startRendering();
<br> 编译运行，一切正常，屏幕颜色也变成了想要的黑色，恩，下一步该添加三角形了，我不太喜欢用OgreManualObject，一堆的繁琐操作。这里用自定义的Mesh来绘制3角形。<br>pSceneManager-&gt;setAmbientLight(Ogre::ColourValue(0.2, 0.2, 0.2) );<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;Ogre::MeshPtr pMeshData = Ogre::MeshManager::getSingleton().createManual("Learn", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;Ogre::SubMesh* pSubMesh = pMeshData-&gt;createSubMesh();<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;pSubMesh-&gt;useSharedVertices = false;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;pSubMesh-&gt;vertexData = new Ogre::VertexData;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;pSubMesh-&gt;vertexData-&gt;vertexStart = 0;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;pSubMesh-&gt;vertexData-&gt;vertexCount = 3;<br><br>
先设置了环境光(其实没啥用，我后面会禁止)，然后创建了一个自定义的Mesh,<br>紧接着的是创建一个SubMesh，要知道Ogre中最小的网格就是SubMesh,创建好SubMesh后，要填充网格结构了，<br>创建了一个VertexData,设置顶点数目为3（也就是一个三角形），下面该定义顶点格式了，<br>Ogre::VertexDeclaration* pDecle = pSubMesh-&gt;vertexData-&gt;vertexDeclaration;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;size_t sOffset = 0;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;pDecle-&gt;addElement(0, sOffset, Ogre::VET_FLOAT3, Ogre::VES_POSITION);<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;sOffset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3);<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;pDecle-&gt;addElement(0, sOffset, Ogre::VET_COLOUR, Ogre::VES_DIFFUSE);<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;sOffset += Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR);
<br><br>上述代码定义了顶点格式，只有基本的坐标和颜色。<br>下一步将是申请显存，填充顶点结构。<br>Ogre::HardwareVertexBufferSharedPtr vBuf = Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(sOffset, 3, Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;float* pReal = static_cast&lt;float*&gt;(vBuf-&gt;lock(Ogre::HardwareBuffer::HBL_DISCARD));<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;Ogre::RGBA* pColor = NULL;<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;*pReal++ = -2.0f;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;*pReal++ = 0.0f;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;*pReal++ = 0.0f;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;pColor = (Ogre::RGBA*)pReal;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;pRenderSystem-&gt;convertColourValue(Ogre::ColourValue(1.0f, 0.0, 0, 0.0f), pColor);<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;pReal = (float*)(pColor+1);<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; *pReal++ = 0.0f;<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; *pReal++ = 2.0f;<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; *pReal++ = 0.0f;<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; pColor = (Ogre::RGBA*)pReal;<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; pRenderSystem-&gt;convertColourValue(Ogre::ColourValue(0.0f, 0, 1.0, 1.0f), pColor);<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; pReal = (float*)(pColor+1);<br><br><br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; *pReal++ = 2.0f;<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; *pReal++ = 0.0f;<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; *pReal++ = 0.0f;<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; pColor = (Ogre::RGBA*)pReal;<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; pRenderSystem-&gt;convertColourValue(Ogre::ColourValue(1.0f, 0, 0, 1.0f), pColor);<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; pReal = (float*)(pColor+1);<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; vBuf-&gt;unlock();<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; pSubMesh-&gt;vertexData-&gt;vertexBufferBinding-&gt;setBinding(0, vBuf);<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; pMeshData-&gt;load();<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; pMeshData-&gt;_setBounds(Ogre::AxisAlignedBox(-2, 0, -1, 2, 2, 1) ); <br>填充顶点后，设置网格包围盒，这样一个自定义的网格就创建好了，接下来要创建一个使用该网格的实体了<br>&nbsp;&nbsp;&nbsp; Ogre::Entity* pEntity = pSceneManager-&gt;createEntity("TestEntity", "Learn");<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;pEntity-&gt;setMaterialName("BaseWhiteNoLighting");<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;pSceneManager-&gt;getRootSceneNode()-&gt;createChildSceneNode()-&gt;attachObject(pEntity);<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;pEntity-&gt;getParentNode()-&gt;setPosition(3, 0, 0);<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;pEntity-&gt;getParentNode()-&gt;rotate(Ogre::Quaternion(1.0f, 1.0f, 0, 1.0f) );
<br><br>好了，这样实体也创建好了，接下来执行渲染吧：<br>pRootObject-&gt;startRendering();
<br><br>
<h2>遇到的问题</h2>
&nbsp; 上述代码是运行正常的，但是一开始，我执行的结果是看不到任何东西，跟踪了下，发现实体每次都被摄像机裁剪了，才发觉自定义Mesh要自己设置包围盒子，<br>设置可包围盒子。<br>&nbsp;设置了包围盒后，数据已经进入了D3D的渲染管道，但是还是没看到三角形，仔细观察，原来摄像机对着的是三角形的背面。。。<br>调整摄像机后，终于能看到一个三角形了，不过是白色的。。。<br>从这个症状看，应该是没有关闭光照导致的，但是我明明主动调用RenderSystem关闭光照了啊，仔细跟踪了下原来是材质在捣乱，<br>默认的材质是开启了光照的，所以在渲染前的SceneManager::_setPass
的时候，开启了光照。<br>这好办，主动设置了关闭光照的材质"BaseWhiteNoLighting" 后，终于看到了彩色三角形了。<br><br><br>  <img src ="http://www.cppblog.com/feixuwu/aggbug/127669.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/feixuwu/" target="_blank">feixuwu</a> 2010-09-25 21:44 <a href="http://www.cppblog.com/feixuwu/archive/2010/09/25/127669.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>select 和 epoll</title><link>http://www.cppblog.com/feixuwu/archive/2010/07/10/119995.html</link><dc:creator>feixuwu</dc:creator><author>feixuwu</author><pubDate>Sat, 10 Jul 2010 10:40:00 GMT</pubDate><guid>http://www.cppblog.com/feixuwu/archive/2010/07/10/119995.html</guid><wfw:comment>http://www.cppblog.com/feixuwu/comments/119995.html</wfw:comment><comments>http://www.cppblog.com/feixuwu/archive/2010/07/10/119995.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/feixuwu/comments/commentRss/119995.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/feixuwu/services/trackbacks/119995.html</trackback:ping><description><![CDATA[最近有朋友在面试的时候被问了select 和epoll效率差的原因，和一般人一样，大部分都会回答select是轮询、epoll是触发式的，所以效率高。这个答案听上去很完美，大致也说出了二者的主要区别。<br>今天闲来无事，翻看了下内核代码，结合内核代码和大家分享下我的观点。<br><br>
<h1>一、连接数</h1>
我本人也曾经在项目中用过select和epoll,对于select，感触最深的是linux下select最大数目限制(windows 下似乎没有限制)，每个进程的select最多能处理FD_SETSIZE个FD(文件句柄)，<br>如果要处理超过1024个句柄，只能采用多进程了。<br>常见的使用slect的多进程模型是这样的： 一个进程专门accept，成功后将fd通过unix socket传递给子进程处理，父进程可以根据子进程负载分派。曾经用过1个父进程+4个子进程 承载了超过4000个的负载。<br>这种模型在我们当时的业务运行的非常好。epoll在连接数方面没有限制，当然可能需要用户调用API重现设置进程的资源限制。<br><br>
<h1>二、IO差别</h1>
<h2>1、select的实现</h2>
这段可以结合linux内核代码描述了，我使用的是2.6.28，其他2.6的代码应该差不多吧。<br>先看看select:<br>select系统调用的代码在fs/Select.c下，<br>asmlinkage long sys_select(int n, fd_set __user *inp, fd_set __user *outp,<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;fd_set __user *exp, struct timeval __user *tvp)<br>{<br>&nbsp;&nbsp; &nbsp;struct timespec end_time, *to = NULL;<br>&nbsp;&nbsp; &nbsp;struct timeval tv;<br>&nbsp;&nbsp; &nbsp;int ret;<br><br>&nbsp;&nbsp; &nbsp;if (tvp) {<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (copy_from_user(&amp;tv, tvp, sizeof(tv)))<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return -EFAULT;<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;to = &amp;end_time;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (poll_select_set_timeout(to,<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;tv.tv_sec + (tv.tv_usec / USEC_PER_SEC),<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;(tv.tv_usec % USEC_PER_SEC) * NSEC_PER_USEC))<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return -EINVAL;<br>&nbsp;&nbsp; &nbsp;}<br><br>&nbsp;&nbsp; &nbsp;ret = core_sys_select(n, inp, outp, exp, to);<br>&nbsp;&nbsp; &nbsp;ret = poll_select_copy_remaining(&amp;end_time, tvp, 1, ret);<br><br>&nbsp;&nbsp; &nbsp;return ret;<br>}
<br>前面是从用户控件拷贝各个fd_set到内核空间，接下来的具体工作在core_sys_select中，<br>core_sys_select-&gt;do_select,真正的核心内容在do_select里：<br>int do_select(int n, fd_set_bits *fds, struct timespec *end_time)<br>{<br>&nbsp;&nbsp; &nbsp;ktime_t expire, *to = NULL;<br>&nbsp;&nbsp; &nbsp;struct poll_wqueues table;<br>&nbsp;&nbsp; &nbsp;poll_table *wait;<br>&nbsp;&nbsp; &nbsp;int retval, i, timed_out = 0;<br>&nbsp;&nbsp; &nbsp;unsigned long slack = 0;<br><br>&nbsp;&nbsp; &nbsp;rcu_read_lock();<br>&nbsp;&nbsp; &nbsp;retval = max_select_fd(n, fds);<br>&nbsp;&nbsp; &nbsp;rcu_read_unlock();<br><br>&nbsp;&nbsp; &nbsp;if (retval &lt; 0)<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;return retval;<br>&nbsp;&nbsp; &nbsp;n = retval;<br><br>&nbsp;&nbsp; &nbsp;poll_initwait(&amp;table);<br>&nbsp;&nbsp; &nbsp;wait = &amp;table.pt;<br>&nbsp;&nbsp; &nbsp;if (end_time &amp;&amp; !end_time-&gt;tv_sec &amp;&amp; !end_time-&gt;tv_nsec) {<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;wait = NULL;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;timed_out = 1;<br>&nbsp;&nbsp; &nbsp;}<br><br>&nbsp;&nbsp; &nbsp;if (end_time &amp;&amp; !timed_out)<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;slack = estimate_accuracy(end_time);<br><br>&nbsp;&nbsp; &nbsp;retval = 0;<br>&nbsp;&nbsp; &nbsp;for (;;) {<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;set_current_state(TASK_INTERRUPTIBLE);<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;inp = fds-&gt;in; outp = fds-&gt;out; exp = fds-&gt;ex;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;rinp = fds-&gt;res_in; routp = fds-&gt;res_out; rexp = fds-&gt;res_ex;<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;for (i = 0; i &lt; n; ++rinp, ++routp, ++rexp) {<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;unsigned long in, out, ex, all_bits, bit = 1, mask, j;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;unsigned long res_in = 0, res_out = 0, res_ex = 0;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;const struct file_operations *f_op = NULL;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;struct file *file = NULL;<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;in = *inp++; out = *outp++; ex = *exp++;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;all_bits = in | out | ex;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (all_bits == 0) {<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;i += __NFDBITS;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;continue;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;}<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;for (j = 0; j &lt; __NFDBITS; ++j, ++i, bit &lt;&lt;= 1) {<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;int fput_needed;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (i &gt;= n)<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;break;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (!(bit &amp; all_bits))<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;continue;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;file = fget_light(i, &amp;fput_needed);<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (file) {<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;f_op = file-&gt;f_op;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;mask = DEFAULT_POLLMASK;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (f_op &amp;&amp; f_op-&gt;poll)<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;mask = (*f_op-&gt;poll)(file, retval ? NULL : wait);<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;fput_light(file, fput_needed);<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if ((mask &amp; POLLIN_SET) &amp;&amp; (in &amp; bit)) {<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;res_in |= bit;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;retval++;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;}<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if ((mask &amp; POLLOUT_SET) &amp;&amp; (out &amp; bit)) {<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;res_out |= bit;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;retval++;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;}<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if ((mask &amp; POLLEX_SET) &amp;&amp; (ex &amp; bit)) {<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;res_ex |= bit;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;retval++;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;}<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;}<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;}<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (res_in)<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;*rinp = res_in;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (res_out)<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;*routp = res_out;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (res_ex)<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;*rexp = res_ex;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;cond_resched();<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;}<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;wait = NULL;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (retval || timed_out || signal_pending(current))<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;break;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (table.error) {<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;retval = table.error;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;break;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;}<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;/*<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp; * If this is the first loop and we have a timeout<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp; * given, then we convert to ktime_t and set the to<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp; * pointer to the expiry value.<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp; */<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (end_time &amp;&amp; !to) {<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;expire = timespec_to_ktime(*end_time);<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;to = &amp;expire;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;}<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;if (!schedule_hrtimeout_range(to, slack, HRTIMER_MODE_ABS))<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;timed_out = 1;<br>&nbsp;&nbsp; &nbsp;}<br>&nbsp;&nbsp; &nbsp;__set_current_state(TASK_RUNNING);<br><br>&nbsp;&nbsp; &nbsp;poll_freewait(&amp;table);<br><br>&nbsp;&nbsp; &nbsp;return retval;<br>}
<br>上面的代码很多，其实真正关键的代码是这一句:<br>mask = (*f_op-&gt;poll)(file, retval ? NULL : wait);
<br>这个是调用文件系统的 poll函数，不同的文件系统poll函数自然不同，由于我们这里关注的是tcp连接，而socketfs的注册在 net/Socket.c里。<br>register_filesystem(&amp;sock_fs_type);
<br>socket文件系统的函数也是在net/Socket.c里：<br>static const struct file_operations socket_file_ops = {<br>&nbsp;&nbsp; &nbsp;.owner =&nbsp;&nbsp; &nbsp;THIS_MODULE,<br>&nbsp;&nbsp; &nbsp;.llseek =&nbsp;&nbsp; &nbsp;no_llseek,<br>&nbsp;&nbsp; &nbsp;.aio_read =&nbsp;&nbsp; &nbsp;sock_aio_read,<br>&nbsp;&nbsp; &nbsp;.aio_write =&nbsp;&nbsp; &nbsp;sock_aio_write,<br>&nbsp;&nbsp; &nbsp;.poll =&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;sock_poll,<br>&nbsp;&nbsp; &nbsp;.unlocked_ioctl = sock_ioctl,<br>#ifdef CONFIG_COMPAT<br>&nbsp;&nbsp; &nbsp;.compat_ioctl = compat_sock_ioctl,<br>#endif<br>&nbsp;&nbsp; &nbsp;.mmap =&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;sock_mmap,<br>&nbsp;&nbsp; &nbsp;.open =&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;sock_no_open,&nbsp;&nbsp; &nbsp;/* special open code to disallow open via /proc */<br>&nbsp;&nbsp; &nbsp;.release =&nbsp;&nbsp; &nbsp;sock_close,<br>&nbsp;&nbsp; &nbsp;.fasync =&nbsp;&nbsp; &nbsp;sock_fasync,<br>&nbsp;&nbsp; &nbsp;.sendpage =&nbsp;&nbsp; &nbsp;sock_sendpage,<br>&nbsp;&nbsp; &nbsp;.splice_write = generic_splice_sendpage,<br>&nbsp;&nbsp; &nbsp;.splice_read =&nbsp;&nbsp; &nbsp;sock_splice_read,<br>};<br>从sock_poll跟随下去，<br>最后可以到 net/ipv4/tcp.c的<br>unsigned int tcp_poll(struct file *file, struct socket *sock, poll_table *wait)
<br>这个是最终的查询函数，<br>也就是说select 的核心功能是调用tcp文件系统的poll函数，不停的查询，如果没有想要的数据，主动执行一次调度（防止一直占用cpu），直到有一个连接有想要的消息为止。<br>从这里可以看出select的执行方式基本就是不同的调用poll,直到有需要的消息为止，如果select 处理的socket很多，这其实对整个机器的性能也是一个消耗。<br><br>
<h2>2、epoll的实现</h2>
epoll的实现代码在 fs/EventPoll.c下，<br>由于epoll涉及到几个系统调用，这里不逐个分析了，仅仅分析几个关键点，<br>第一个关键点在<br>static int ep_insert(struct eventpoll *ep, struct epoll_event *event,<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct file *tfile, int fd)
<br>这是在我们调用sys_epoll_ctl 添加一个被管理socket的时候调用的函数，关键的几行如下：<br>epq.epi = epi;<br>&nbsp;&nbsp; &nbsp;init_poll_funcptr(&amp;epq.pt, ep_ptable_queue_proc);<br><br>&nbsp;&nbsp; &nbsp;/*<br>&nbsp;&nbsp; &nbsp; * Attach the item to the poll hooks and get current event bits.<br>&nbsp;&nbsp; &nbsp; * We can safely use the file* here because its usage count has<br>&nbsp;&nbsp; &nbsp; * been increased by the caller of this function. Note that after<br>&nbsp;&nbsp; &nbsp; * this operation completes, the poll callback can start hitting<br>&nbsp;&nbsp; &nbsp; * the new item.<br>&nbsp;&nbsp; &nbsp; */<br>&nbsp;&nbsp; &nbsp;revents = tfile-&gt;f_op-&gt;poll(tfile, &amp;epq.pt);
<br>这里也是调用文件系统的poll函数，不过这次初始化了一个结构，这个结构会带有一个poll函数的callback函数：ep_ptable_queue_proc，<br>在调用poll函数的时候，会执行这个callback，这个callback的功能就是将当前进程添加到 socket的等待进程上。<br>static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead,<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp; poll_table *pt)<br>{<br>&nbsp;&nbsp; &nbsp;struct epitem *epi = ep_item_from_epqueue(pt);<br>&nbsp;&nbsp; &nbsp;struct eppoll_entry *pwq;<br><br>&nbsp;&nbsp; &nbsp;if (epi-&gt;nwait &gt;= 0 &amp;&amp; (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) {<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;init_waitqueue_func_entry(&amp;pwq-&gt;wait, ep_poll_callback);<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;pwq-&gt;whead = whead;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;pwq-&gt;base = epi;<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;add_wait_queue(whead, &amp;pwq-&gt;wait);<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;list_add_tail(&amp;pwq-&gt;llink, &amp;epi-&gt;pwqlist);<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;epi-&gt;nwait++;<br>&nbsp;&nbsp; &nbsp;} else {<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;/* We have to signal that an error occurred */<br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;epi-&gt;nwait = -1;<br>&nbsp;&nbsp; &nbsp;}<br>}&nbsp;
<br>注意到参数 whead
实际上是 sk-&gt;sleep，其实就是将当前进程添加到sk的等待队列里，当该socket收到数据或者其他事件触发时，会调用<br>sock_def_readable
或者sock_def_write_space
通知函数来唤醒等待进程，这2个函数都是在socket创建的时候填充在sk结构里的。<br>从前面的分析来看，epoll确实是比select聪明的多、轻松的多，不用再苦哈哈的去轮询了。<br><br>      <img src ="http://www.cppblog.com/feixuwu/aggbug/119995.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/feixuwu/" target="_blank">feixuwu</a> 2010-07-10 18:40 <a href="http://www.cppblog.com/feixuwu/archive/2010/07/10/119995.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>推荐一个跨平台内存分配器</title><link>http://www.cppblog.com/feixuwu/archive/2010/07/10/119980.html</link><dc:creator>feixuwu</dc:creator><author>feixuwu</author><pubDate>Sat, 10 Jul 2010 09:32:00 GMT</pubDate><guid>http://www.cppblog.com/feixuwu/archive/2010/07/10/119980.html</guid><wfw:comment>http://www.cppblog.com/feixuwu/comments/119980.html</wfw:comment><comments>http://www.cppblog.com/feixuwu/archive/2010/07/10/119980.html#Feedback</comments><slash:comments>11</slash:comments><wfw:commentRss>http://www.cppblog.com/feixuwu/comments/commentRss/119980.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/feixuwu/services/trackbacks/119980.html</trackback:ping><description><![CDATA[&nbsp; 昨天一个同事一大早在群里推荐了一个google project上的开源内存分配器（<a href="http://code.google.com/p/google-perftools/" swaped="true" target="_blank">http://code.google.com/p/google-perftools/</a>），据说google的很多产品都用到了这个内存分配库，而且经他测试，我们的游戏客户端集成了这个最新内存分配器后，FPS足足提高了将近10帧左右，这可是个了不起的提升，要知道3D组的兄弟忙了几周也没见这么大的性能提升。<br><br>如果我们自己本身用的crt提供的内存分配器，这个提升也算不得什么。问题是我们内部系统是有一个小内存管理器的，一般来说小内存分配的算法都大同小异，现成的实现也很多，比如linux内核的slab、SGI STL的分配器、ogre自带的内存分配器，我们自己的内存分配器也和前面列举的实现差不多。让我们来看看这个项目有什么特别的吧。<br><br>
<h1>一、使用方法</h1>
打开主页，由于公司网络禁止SVN从外部更新，所以只能下载了打包的源代码。解压后，看到有个doc目录，进去，打开使用文档，发现使用方法极为简单：<br>To use TCMalloc, just link TCMalloc into your application via the
"-ltcmalloc" linker flag.再看算法，也没什么特别的，还是和slab以及SGI STL分配器类似的算法。<br>unix环境居然只要链接这个tcmalloc库就可以了！，太方便了，不过我手头没有linux环境，文档上也没提到windows环境怎么使用，<br>打开源代码包，有个vs2003解决方案，打开，随便挑选一个测试项目，查看项目属性，发现仅仅有2点不同：<br>1、链接器命令行里多了<br>&nbsp; "..\..\release\libtcmalloc_minimal.lib"，就是链接的时候依赖了这个内存优化库。<br>2、链接器-&gt;输入-&gt;强制符号引用 多了 __tcmalloc。<br>这样就可以正确的使用tcmalloc库了，测试了下，测试项目运行OK!<br><br>
<h1>二、如何替换CRT的malloc</h1>
从前面的描述可知，项目强制引用了__tcmalloc， 搜索了测试代码，没发现用到_tcmalloc相关的函数和变量,这个选项应该是为了防止dll被优化掉(因为代码里没有什么地方用到这个dll的符号)。<br>初看起来，链接这个库后，不会影响任何现有代码:我们没有引用这个Lib库的头文件，也没有使用过这个dll的导出函数。那么这个dll是怎么优化应用程序性能的呢？<br>实际调试，果然发现问题了，看看如下代码<br>&nbsp;&nbsp;&nbsp; void* pData = malloc(100);<br>00401085 6A 64&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 64h&nbsp; <br>00401087 FF 15 A4 20 40 00 call&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dword ptr [__imp__malloc (4020A4h)] <br>跟踪 call malloc这句，step进去，发现是<br>78134D09 E9 D2 37 ED 97&nbsp;&nbsp; jmp&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; `anonymous namespace'::LibcInfoWithPatchFunctions&lt;8&gt;::Perftools_malloc (100084E0h) <br>果然，从这里开始，就跳转到libtcmalloc提供的Perftools_malloc了。<br>原来是通过API挂钩来实现无缝替换系统自带的malloc等crt函数的，而且还是通过大家公认的不推荐的改写函数入口指令来实现的，一般只有在游戏外挂和金山词霸之类的软件才会用到这样的挂钩技术，<br>而且金山词霸经常需要更新补丁解决不同系统兼容问题。<br><br>
<h1>三、性能差别原因</h1>
如前面所述，tcmalloc确实用了很hacker的办法来实现无缝的替换系统自带的内存分配函数（本人在使用这类技术通常是用来干坏事的。。。），但是这也不足以解释为什么它的效率比我们自己的好那么多。<br>回到tcmalloc 的手册，tcmalloc除了使用常规的小内存管理外，对多线程环境做了特殊处理，这和我原来见到的内存分配器大有不同，一般的内存分配器作者都会偷懒，把多线程问题扔给使用者，大多是加<br>个bool型的模板参数来表示是否是多线程环境，还美其名曰:可定制，末了还得吹嘘下模板的优越性。<br>tcmalloc是怎么做的呢？ 答案是每线程一个ThreadCache，大部分操作系统都会支持thread local storage 就是传说中的TLS,这样就可以实现每线程一个分配器了，<br>这样，不同线程分配都是在各自的threadCache里分配的。我们的项目的分配器由于是多线程环境的，所以不管三七二十一，全都加锁了，性能自然就低了。<br><br>仅仅是如此，还是不足以将tcmalloc和ptmalloc2分个高下，后者也是每个线程都有threadCache的。<br>关于这个问题，doc里有一段说明，原文贴出来：<br>ptmalloc2 also reduces lock contention by using per-thread arenas but
there is a big problem with ptmalloc2's use of per-thread arenas.  In
ptmalloc2 memory can never move from one arena to another.  This can
lead to huge amounts of wasted space.<br>大意是这样的：ptmalloc2 也是通过tls来降低线程锁，但是ptmalloc2各个线程的内存是独立的，也就是说，第一个线程申请的内存，释放的时候还是必须放到第一个线程池中（不可移动），这样可能导致大量内存浪费。<br>&nbsp;<br>
<h1>四、代码细节</h1>
<h2>1、无缝替换malloc等crt和系统分配函数。</h2>
&nbsp;&nbsp; 前面提到tcmalloc会无缝的替换掉原有dll中的malloc，这就意味着使用tcmalloc的项目必须是 MD（多线程dll）或者MDd（多线程dll调试）。tcmalloc的dll定义了一个<br>static TCMallocGuard module_enter_exit_hook;<br>的静态变量，这个变量会在dll加载的时候先于DllMain运行，在这个类的构造函数，会运行PatchWindowsFunctions来挂钩所有dll的 malloc、free、new等分配函数，这样就达到了替换功能，除此之外，<br>为了保证系统兼容性，挂钩API的时候还实现了智能分析指令，否则写入第一条Jmp指令的时候可能会破环后续指令的完整性。<br><br>
<h2>2、LibcInfoWithPatchFunctions 和ThreadCache。</h2>
LibcInfoWithPatchFunctions模板类包含tcmalloc实现的优化后的malloc等一系列函数。LibcInfoWithPatchFunctions的模板参数在我看来没什么用处，tcmalloc默认可以挂钩<br>最多10个带有malloc导出函数的库(我想肯定是够用了)。ThreadCache在每个线程都会有一个TLS对象：<br>__thread ThreadCache* ThreadCache::threadlocal_heap_。<br>
<h2>3、可能的问题</h2>
<br>设想下这样一个情景：假如有一个dll 在tcmalloc之前加载，并且在分配了内存（使用crt提供的malloc），那么在加载tcmalloc后，tcmalloc会替换所有的free函数，然后，在某个时刻，<br>在前面的那个dll代码中释放该内存，这岂不是很危险。实际测试发现没有任何问题，关键在这里：<br>&nbsp;span = Static::pageheap()-&gt;GetDescriptor(p);<br>&nbsp;&nbsp;&nbsp; if (!span) {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // span can be NULL because the pointer passed in is invalid<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // (not something returned by malloc or friends), or because the<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // pointer was allocated with some other allocator besides<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // tcmalloc.&nbsp; The latter can happen if tcmalloc is linked in via<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // a dynamic library, but is not listed last on the link line.<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // In that case, libraries after it on the link line will<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // allocate with libc malloc, but free with tcmalloc's free.<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (*invalid_free_fn)(ptr);&nbsp; // Decide how to handle the bad free request<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return;<br>&nbsp;&nbsp;&nbsp; }
<br>tcmalloc会通过span识别这个内存是否自己分配的，如果不是，tcmalloc会调用该dll原始对应函数(这个很重要)释放。这样就解决了这个棘手的问题。<br>
<h1>五、其他</h1>
其实tcmalloc使用的每个技术点我从前都用过，但是我从来没想过用API挂钩来实现这样一个有趣的内存优化库（即使想过，也是一闪而过就否定了）。<br>从tcmalloc得到灵感，结合常用的外挂技术，可以很轻松的开发一个独立工具：这个工具可以挂载到指定进程进行内存优化，在我看来，这可能可以作为一个外挂辅助工具来优化那些<br>内存优化做的很差导致帧速很低的国产游戏。<br>   <img src ="http://www.cppblog.com/feixuwu/aggbug/119980.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/feixuwu/" target="_blank">feixuwu</a> 2010-07-10 17:32 <a href="http://www.cppblog.com/feixuwu/archive/2010/07/10/119980.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>