记一次程序优化记录

忙了几个月的项目终于在生产环境上上线,虽然接下来还有不少工作需要跟进,但终究不像上段时间那么忙了,因此也就能利用周末的时间,对之前优化程序性能的过程作下记录,也当是个小小的经验总结。

程序的应用场景是往 Redis 写入具有大量记录的二维表,或者从 Redis 读取具有大量记录的二维表数据。由于二维表的记录数量巨大,比如有些二维表的记录数达几十万条,因此对于程序的性能有较高的要求。

程序使用 hiredis 来写入 redis 数据,由于二维表的数据量巨大,且没有采用 pipeline 的方式每次写入一行记录而是采用一次性写入所有记录的方式,因此,需要构造一个具有较大容量的字符串缓冲区来发送二维表数据。在初始化缓冲区时,使用了memset函数来将字符串缓冲区所有位置为0:

1
memset(buff, '\0', 10240000);

上面语句的作用是将buff所指向的缓冲区初始化为0,10240000表示缓冲区的大小约为10MB。

程序运行后,发现性能很低,通过定位,发现性能的瓶颈正好是这个memset函数的使用,下面的代码可以测试memset的执行时间:

1
2
3
4
5
6
7
clock_t start, finish;
double duration;
start = clock();
memset(buff, '\0', 10240000);
finish = clock();
duration = (double)(finish - start) / CLOCKS_PER_SEC;
printf("memset run time: %f\n", duration);

发现程序执行memset时,不仅耗时而且还耗CPU,故需要对代码进行优化。通过分析,实际上自己并不需要将缓存区buff每个字节都置为0,在上面的例子中buff会被当成C语言的字符串来使用,因此,每次使用前,只需要将buff的末尾置为0就行了,例如:

1
2
3
4
// 初始化
buff[0] = '\0';
// 对buff进行赋值后,再将其末尾置为0
buff[n] = '\0';

去除了memset的使用,发现程序的性能有了大大的提高。

在写下这篇文章时,自己百度了一下memset的性能,发现这哥们也有类似的看法,参考链接:http://blog.csdn.net/wwp3321/article/details/4961993

程序优化的第二个地方是strcat函数的使用。strcat用于字符串的拼接,且要求目的位置需要有足够的空间来存储拼接后的字符串。
在往 Redis 写入数据时,需要循环使用strcat来拼接构造二维表记录的索引字符串(用来支持快速查找二维表记录),代码示例如下:

1
2
3
4
5
6
7
8
9
for (list<unsigned>::iterator itList = listRowIndex.begin(); itList != listRowIndex.end(); itList++) {
sprintf(sRowIndex, "%u\0", *itList);
if (posList == 0) {
strcat(sList, sRowIndex);
} else {
strcat(sList, SPLIT_FLAG);
strcat(sList, sRowIndex);
}
}

上述代码的功能是构造二维表索引字符串,使用sList来存放构造的索引。由于调用strcat时,每次都需要遍历目的字符串,找到目的字符串的结束字符\0,然后再将新的索引字符串添加到其末尾,因此,在listRowIndex元素数量很大时,上述的操作会导致出现大量重复的遍历字符串的操作,导致性能低下。

strcat函数的功能可以使用下面的函数来代替:

1
2
3
4
5
6
7
8
unsigned appendstr(char *buff, unsigned pos, const char *tail)
{
for (int i = 0; tail[i] != '\0'; ++i) {
buff[pos++] = tail[i];
}
buff[pos] = '\0';
return pos;
}

appendstr函数同样是将字符串添加到目的字符串的末尾,但与strcat不同的是,appendstr可以返回目的字符串的结束位置,这样一来,在不断地循环调用appendstr时,再也不用每次都重复遍历目的字符串以找到结束位置,提高了程序的效率。

1
2
3
4
5
6
7
8
9
for (list<unsigned>::iterator itList = listRowIndex.begin(); itList != listRowIndex.end(); itList++) {
sprintf(sRowIndex, "%u\0", *itList);
if (posList == 0) {
posList = appendstr(sList, posList, sRowIndex);
} else {
posList = appendstr(sList, posList, SPLIT_FLAG);
posList = appendstr(sList, posList, sRowIndex);
}
}

通过使用appendstr函数代替strcat,程序的性能有了质的飞跃。

这篇文章记录的两个优化点都是C语言字符串的操作函数,没想到这些函数还有不少需要注意的地方,看来以后使用时还得小心谨慎。