您的位置:首页 > 其它

实习心得(二)--关于段错误,内存泄露,性能瓶颈

2013-08-07 18:46 267 查看
这三件事情遇到的时候都困扰了我至少不止一天我才解决,也是这三个错误,遇到的时候老大跟我说“没改过...错误的程序员不是真正的程序员”,而老大这句话是让我每次遇到这些错误坚持在坚持的动力,因为我自诩是个程序员,至少也要成为真正的程序员。

段错误

最怕看到这样的错误提示Program received signal SIGSEGV, Segmentation fault.段错误,段错误是指访问的内存超出了系统给这个程序所设定的内存空间,例如访问了不存在的内存地址、访问了系统保护的内存地址、访问了只读的内存地址等等情况而引起的。

那么我们实际中最首先想到的有可能是数组越界,用了迷途指针引起的,所以我当时遇到这个错误的时候,第一反应是检查我程序中所有用到数组的地方确定我是否控制对了边界条件,导致了越界,然后第二步检测我所用到的指针,是否是一个未初始化的指针或者是迷途指针,导致访问了不该访问的内存,但是这二者我都没有。

想到数组越界就想到了一个关于字符串拷贝与内存拷贝的问题,这里有一对函数strcpy与strnspy,了解二者是如何拷贝的,尤其是二者的结束,对于二者的参数传递的选择,这样确保不会越界。同时也要考虑memcpy最后一个参数的确定。于是我查看了关于我所有字符串拷贝与内存拷贝的地方,也都确保没有问题。

我采用gdb结合core文件进行定位,错误在一个链表的删除函数中,链表的元素变成了一个非法的指针,指向的内存地址是不可访问,我一步步跟踪调试发现某一个操作后,指针突然变成了无效的地址,而却不是进行链表操作的过程导致的。这让我很郁闷,我不知所措,因为我gdb调试不是很懂,所以最后我采用了笨办法,分块排除,我一段段代码注释掉运行,最后定位到某一个函数的调用,发现问题出在,我释放了一个锁,但是我其实并没有申请锁,于是我释放了一段未知的内存而这段内存就是之前链表元素所指的地方。最终这个段错误就这样解决了。

最后总结遇到段错误可以从数组越界,迷途指针,释放不存在的内存这几个方面去找问题所在。当然要说到如果是一位gdb高手,可以动态观测被修改的变量是在那个操作后被修改的,这样就比较快,容易的定位到问题的所在,缩小范围。

内存泄露

刚刚前面说到的段错误是因为多释放了一个锁导致,那么内存泄露则是相反极可能是因为未释放一把锁导致。记得当时第一次测试的同学给出测试报告中,有一个图是一条RES增长的曲线,当时不是很懂,后来了解到RES是实存,实存增长意味着我的程序内存泄露了,当时是一个特别小的模块,我的修改只是很简单的几处地方,于是按照内存泄露最可能出现的原因分配/申请了一片内存而没有释放,我仔细看了自己增加的部分代码,没有这种情况。

我又分析了一些系统可能用到申请内存未释放的函数,当时很怀疑是网络连接引起的,认为是新建了一个连接没有释放,虽然最后证明这些连接在最后整个程序退出时释放了,不是这里引起的,但是这个想法给了我后来查看内存泄露的一个方向。因为这个想法,才让我再第二次遇到内存泄露的时候想到可能是数据库连接引起的解决了当时的问题。

最后虽然第一次内存泄露是配置文件中出错的问题引起的,但是那个过程让我第二次遇到内存泄露时可以淡定地从几个可能的方向出发,查找原因。这可能就是一种所谓的经历过就不害怕了。当时这里要说一下就是最开始提到的未释放一把锁也是可能导致内存泄露的问题,有很多时候未释放一把锁会导致程序死锁,陷入死循环,而有些时候虽然没有导致死锁,却可能产生其他的问题。

还有时候内存泄露不是因为这些问题产生的,也可以这么说,并不一定是内存泄露,但是测试的过程中出现的实存曲线图却是增长的,这个可能是流水程序堵住,导致流水通道中存放了很多新建的数据,由于没有处理也无法释放内存。记得我当时的程序因为写数据库的那级流水很慢,而我把程序写成了同步等待数据库写,这样完全导致所有的数据堵在最后一级流水的链表结构中,无法释放而实存疯涨。

从上面的两个问题中我们可以看出配对的函数一定要记得配对使用,申请内存,释放内存;申请锁,释放锁;创建连接,断开连接。

性能瓶颈

以前写程序总是能处理完成,给出结果就OK了,因为需要处理的数据量极小,就那么几百几千都不到的数据。所以也会有疑问,老师们讲的那些算法啊数据结构的强大。而直到第一次老大给出指标需要一分钟处理10000个请求的要求,我的程序才能处理5000左右就怎么也无法提升了。

第一反应是对自己感觉可能影响性能的流水进行优化,想到可能是多线程锁的问题,锁的粒度太大,降低了并发度,就这个猜测进行了修改。依然没有太大的改善。这时候又以为是写磁盘的那级流水影响所致,但是后来否定这个想法,因为结果输出在这级流水之前,不会产生影响。当然要注意,对于写磁盘和数据库的操作,最好做成异步的,以免影响程序的性能。

后来老大给了建议,在日志中输出每个重要模块处理的时间,然后看占比高的在那个部分,然后再定位可能是哪个模块的哪个函数引起,输出函数的处理时间,果然这样就很快找到了,有时候问题不能太想当然,我没有想到竟然是在接收信息,处理信息格式的那个模块,而且重点是慢在我用了一个string自己的find函数,导致了整个切割过程很慢。这时候才发现对于特殊的情况可以考虑特殊的算法。改掉这个地方后,在性能好的机器上面程序的性能达到20000次/s左右。

三件事情,以前遇到害怕的两个bug,以后遇到的时候不怕了,虽然不一定能很快找出问题所在。一个以前不曾考虑的非功能需求,以后写程序都应该时刻想到。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: