记一次程序优化记录
忙了几个月的项目终于在生产环境上上线,虽然接下来还有不少工作需要跟进,但终究不像上段时间那么忙了,因此也就能利用周末的时间,对之前优化程序性能的过程作下记录,也当是个小小的经验总结。
程序的应用场景是往 Redis 写入具有大量记录的二维表,或者从 Redis 读取具有大量记录的二维表数据。由于二维表的记录数量巨大,比如有些二维表的记录数达几十万条,因此对于程序的性能有较高的要求。
程序使用 hiredis 来写入 redis 数据,由于二维表的数据量巨大,且没有采用 pipeline 的方式每次写入一行记录而是采用一次性写入所有记录的方式,因此,需要构造一个具有较大容量的字符串缓冲区来发送二维表数据。在初始化缓冲区时,使用了memset
函数来将字符串缓冲区所有位置为0:
1 | memset(buff, '\0', 10240000); |
上面语句的作用是将buff
所指向的缓冲区初始化为0,10240000
表示缓冲区的大小约为10MB。
程序运行后,发现性能很低,通过定位,发现性能的瓶颈正好是这个memset
函数的使用,下面的代码可以测试memset
的执行时间:
1 | clock_t start, finish; |
发现程序执行memset
时,不仅耗时而且还耗CPU,故需要对代码进行优化。通过分析,实际上自己并不需要将缓存区buff
每个字节都置为0,在上面的例子中buff
会被当成C语言的字符串来使用,因此,每次使用前,只需要将buff
的末尾置为0就行了,例如:
1 | // 初始化 |
去除了memset
的使用,发现程序的性能有了大大的提高。
在写下这篇文章时,自己百度了一下memset
的性能,发现这哥们也有类似的看法,参考链接:http://blog.csdn.net/wwp3321/article/details/4961993。
程序优化的第二个地方是strcat
函数的使用。strcat
用于字符串的拼接,且要求目的位置需要有足够的空间来存储拼接后的字符串。
在往 Redis 写入数据时,需要循环使用strcat
来拼接构造二维表记录的索引字符串(用来支持快速查找二维表记录),代码示例如下:
1 | for (list<unsigned>::iterator itList = listRowIndex.begin(); itList != listRowIndex.end(); itList++) { |
上述代码的功能是构造二维表索引字符串,使用sList
来存放构造的索引。由于调用strcat
时,每次都需要遍历目的字符串,找到目的字符串的结束字符\0
,然后再将新的索引字符串添加到其末尾,因此,在listRowIndex
元素数量很大时,上述的操作会导致出现大量重复的遍历字符串的操作,导致性能低下。
strcat
函数的功能可以使用下面的函数来代替:
1 | unsigned appendstr(char *buff, unsigned pos, const char *tail) |
appendstr
函数同样是将字符串添加到目的字符串的末尾,但与strcat
不同的是,appendstr
可以返回目的字符串的结束位置,这样一来,在不断地循环调用appendstr
时,再也不用每次都重复遍历目的字符串以找到结束位置,提高了程序的效率。
1 | for (list<unsigned>::iterator itList = listRowIndex.begin(); itList != listRowIndex.end(); itList++) { |
通过使用appendstr
函数代替strcat
,程序的性能有了质的飞跃。
这篇文章记录的两个优化点都是C语言字符串的操作函数,没想到这些函数还有不少需要注意的地方,看来以后使用时还得小心谨慎。