您的位置:首页 > 其它

一点由字符串匹配引发的思考

2012-11-02 10:36 351 查看
    起因是我的Feature太过简单,在项目组的大家还在忙碌不堪时,我已然开始打酱油了。显然,打酱油比干活累,所以我向老大汇报了。于是乎,老大亲自操刀,review我的代码。结局是:很惨!

    接下来的日子,就开始了我的漫漫代码改进之路。

    感想是,不过是小小的一个字符串匹配的问题,竟然调了这么多调,而且越往下挖,就越发现更多的细节问题,都是原来不曾考虑过的。短短一周多的调试,收获颇多,遂记于此。

    最早版本的代码,调试glibc的库函数,strstr(),结果被批得好惨。要求重改,不要调用系统函数。

    当初,我们认为strstr()是用最简单的匹配方法,就是从前往后,每次进行一次后移的方法,显然无论怎么写都理应比这个效率高一些。   

    方法1:用memcmp来代替strstr,测试结果并不理想。老大提供方法2,针对SIP文本协议解析的一种特殊方法。事实上是,对于一段有意义的,人能读懂的文本,这种方法都适用。即,扫描整个文本,每遇到分隔符做一次暂停,总对当前扫描出的word与token进行匹配,可用匹配方法可以调用memcmp()或strcmp(),也可自己实现。结果,对于第二种算法,无论我们如何改进,都达不到直接用strstr()的性能。遂读strstr()代码,发现其实现并不是我们想的那样简单。作者在注释里写到他的这个算法可以打败绝大多数的字符串匹配算法——我/抠鼻——好吧。作为底层函数的效率,GNU的确做得很好,他们追求的就是效率最高化。首先大量的goto引发的阅读困难,各种奇怪的操作,只为了提高一点点的效率,当大量运行的时候,就可以看出他们的好来。但,结果就是不容易读懂。参考网址:http://blog.chinaunix.net/uid-368446-id-2414244.html

    总结几点收获,首先要尽可能地进行自动化测试,减少手工的工作。任何多余的操作都有可能引起效率问题,很多时候可以尽可能地用指针的移动对内存区进行操作,减少其余的赋值操作,以免浪费时间,浪费空间。写代码的过程中,边界条件很重要,这是我以前好容易忽略,并引起系统bug的原因之一。
    附上最终源代码,此次性能测试到一段落,接下来,老大push大家趁这段比较空闲的时间,共同学习Effective C++,并给每人分配一个章节作为主讲,主要工作是讲解自己学到的内容,需要注意的地方,遇到的问题,并引发大家进行讨论和思考。一个很好的锻炼机会。如果每个release之间的空余时间,都能找一本书进行这样的学习,也是一个不错的选择。

void strstrMethod_2(unsigned char * pBlob, int var_len)
{
if(NULL == pBlob)
return;

reasonHeaderQ850Present_ = 0;
responseCode_ = 0;
char *pQ850 = "Q.850";
char *pCause = "cause";

char *p = (char *)pBlob;
bool q850FindFlag = false;
while(!reasonHeaderQ850Present_ && *p)
{
char *pToken;
if(!q850FindFlag && !(pToken = strstr(p, pQ850)))
return;

q850FindFlag = true;
if(q850FindFlag && !(pToken = strstr(pToken+TokenLenConst, pCause)))
return;

pToken += TokenLenConst;
while(*pToken && ('=' == *pToken || ' ' == *pToken))
++pToken;

if(*pToken && (*pToken < '0' || *pToken > '9'))
{
p = pToken;
q850FindFlag = false;
continue;
}

while(*pToken && *pToken >= '0' && *pToken <= '9')
{
reasonHeaderQ850Present_ = 1;
responseCode_ = responseCode_ * 10 + (*pToken++ - '0');
}
}

return;
}
void wordDelimiter_4(unsigned char * pBlob, int var_len)
{
if(NULL == pBlob)
return;

reasonHeaderQ850Present_ = 0;
responseCode_ = 0;
char *pQ850Token = "Q.850";
char *pCauseToken = "cause";
char *pToken = pQ850Token;

char *pTokenEnd = (char*)pBlob;
bool q850FindFlag = false;
while (*pTokenEnd)
{
--pTokenEnd;
do
if(!*++pTokenEnd)
return;
while(*pTokenEnd != *pToken);

char *pTokenStart = pTokenEnd;
while((';'!=*pTokenEnd)
&& (' '!=*pTokenEnd)
&& (':'!=*pTokenEnd)
&& ('='!=*pTokenEnd)
&& ('\t'!=*pTokenEnd)
&& ('\n'!=*pTokenEnd))//delimitate
++pTokenEnd;

if(pTokenEnd - pTokenStart != TokenLenConst)
{
++pTokenEnd;
continue;
}

char tmp_char = *pTokenEnd;
*pTokenEnd = '\0';
if(!strcmp(pTokenStart, pToken))//memcmp() is slower than strcmp()
{
*pTokenEnd = tmp_char;
if(!q850FindFlag)
{
q850FindFlag = true;
pToken = pCauseToken;
continue;
}
else
{
//To get cause value
while (*pTokenEnd && ((' '==*pTokenEnd) || ('='==*pTokenEnd) || ('\t'==*pTokenEnd)))//delimitate
++pTokenEnd;

if(*pTokenEnd && (*pTokenEnd < '0' || *pTokenEnd > '9'))
{
q850FindFlag = false;
pToken = pQ850Token;
continue;
}

while (*pTokenEnd && *pTokenEnd >='0' && *pTokenEnd <= '9')
{
reasonHeaderQ850Present_ = 1;
responseCode_ = responseCode_ * 10 + (*pTokenEnd++ -'0');
}

if(reasonHeaderQ850Present_)
return;
}
}//if(!memcmp(pTokenStart, pToken, TokenLenConst))
*pTokenEnd = tmp_char;
}//while
return;
}


    本来到这里应该就是结束了,这篇文章是前天写的,不过今天又有更新,在两者的基础上继续改进,新代码性能不错:
void wordDelimiter_8(unsigned char * pBlob, int var_len)
{
const int LEN_Q850 = 5;
const int LEN_CAUSE = 5;
const char TOKEN_Q850[LEN_Q850+1] = "Q.850";
const char TOKEN_CAUSE[LEN_CAUSE+1] = "cause";

unsigned char *p = pBlob;
unsigned char *pEnd = pBlob+var_len;
reasonHeaderQ850Present_ = 0;
responseCode_ = 0;
int i = 0;

while (p<pEnd)
{
while(p<pEnd && *p++!=TOKEN_Q850[0])
;
for (i=1; i<LEN_Q850;)
if (*p++==TOKEN_Q850[i])
++i;
else
break;
if (i==LEN_Q850)
{
//++p;
while(p<pEnd && *p++!=TOKEN_CAUSE[0])
; //++p;
for (i=1; i<LEN_CAUSE;)
if (*p++==TOKEN_CAUSE[i])
++i;
else
break;
if (i==LEN_CAUSE)
{
while (p<pEnd && (*p==' ' || *p== '\t' || *p=='='))
++p;
unsigned char * pValue = p;
while (p<pEnd && *p >='0' && *p <= '9')
responseCode_ = (responseCode_ * 10) + (*p++ -'0');
if (p != pValue)
{
reasonHeaderQ850Present_ = 1;
return;
}
}
}
}
//return;
}

    来看看用g++加上-O3编译选项之后,出来的测试结果吧:

    测试用例如下:

const int TC_LEN = 6;
unsigned char TC_Data[TC_LEN][100000] = {
//1. Q.850 in header (large string)
"Q.850 ;cause=16 ;\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\n",
//2. Q.850 in tail (large string)
"ReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nQ.850 ;cause=16",
//3. Normal Case
" SIP ;cause=200\nSIP ;cause=200\nSIP ;cause=200\nSIP ;cause=200\nSIP ;cause=200\nSIP ;cause=200\nSIP ;cause=200\nSIP ;cause=200\nSIP ;cause=200\nSIP ;cause=200\nSIP ;cause=200\nSIP ;cause=200\nSIP ;cause=200\naaaaaaaaaaaaaaaaaaaaaaaaaaaaa Q.850 ;cause=16 ;text=\"Terminated\"\nQ.850 ;cause=18 SIP ;cause=200\nSIP ;cause=200\nSIP ;cause=200\nSIP ;cause=200\nSIP ;cause=200\naaaaaaaaaaaaaaaaaaaaaaaaaaaaa SIP ;cause=200\nSIP ;cause=200\nSIP ;cause=200\nSIP ;cause=200\nSIP ;cause=200\nSIP ;cause=200\nSIP ;cause=200\n",
//4. Without Q.850
"ReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReaso
a5dd
nHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\nReasonHeader: SIP ;cause=200\n",
//5. Special case for strstrMode
"Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;Q.85x ;cause=200\nQ.850 ;cause=17 ;",
//6. Q.850 with error cause
" SIP ;cause=200\nSIP ;cause=200\nSIP ;cause=200\nSIP ;cause=200\nSIP ;cause=200\nSIP ;cause=200\nSIP ;cau\nSIP ;cause=200\nSIP ;cause=200\nSIP ;cau\nSIP ;cause=200\nSIP ;cause=200\nSIP ;cau\nSIP ;cause=200\n\nQ.850 ;cause=a SIP ;cause=200\n Q.850 ;cause=17 SIP ;cause=200\nSIP ;cau\nSIP ;cause=200\nSIP ;cause=200\nSIP ;cau\nSIP ;cause=200\nSIP ;cause=200\nSIP ;cau\nSIP ;cause=200\nSIP ;cause=200\nSIP ;cau\nSIP ;cause=200\nSIP SIP ;caudafsdafafdsadsaf832738922737*&%$&(^%^&*(*&^%$%^&*(*&^%^&*&^%$%^&*&^%$%^&*dafsdafafdsadsaf832738922737*&%$&(^%^&*(*&^%$%^&*(*&^%^&"
};

char TC_Desc[TC_LEN][100] =
{
"Q.850 in header (large string)",
"Q.850 in tail (large string)",
"Normal Case",
"Without Q850",
"Special case for strstrMode",
"Q.850 with error cause"
};

    将这三个method循环运行10,000,000后计算运行时间,测试结果如下:
Test result 

TC       StrstrMode             DelimiterMode4          DelimiterMode8

--------------------------------------------------------------------------

 1       0.27                   0.58                    0.21 (Q.850 in header (large string))

 2       4.04                   4.32                    3.94 (Q.850 in tail (large string))

 3       2.02                   2.25                    1.90 (Normal Case)

 4       5.64                   3.80                    3.80 (Without Q850)

 5       8.10                   17.51                   3.62 (Special case for strstrMode)

 6       2.80                   2.78                    2.15 (Q.850 with error cause)

--------------------------------------------------------------------------

    给力~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: