递归函数的优化步骤与方法
代码优化对一般函数来说可能紧迫性不大,然而对于递归函数,尤其是递归次数可能会较多的函数,由于受到线程内存和堆栈大小的限制,每增加一个局部变量,堆栈中就会增加一个元素,内存占用也随之增加,直到其所在的函数执行结束,所以代码优化就显得非常重要。
下面以实际代码为例,谈谈递归函数优化的步骤与方法。该函数是数据流分析中的一个核心函数,作用是找到到达函数结束行的所有路径。源代码如下:
voidCDataFlow::FindAllUltiPathToSonFunEndLine(stringsFunUseListStr,vector<int> stlFunUseLineList,DstNodednOriCurrNode,DstNode dnSrcNode,int nFunEndLine)
{
inti;
intnSize;
intnCurrLine;
intnFromLine;
intnDstCount;
intnLen;
intnSolvedState;
intnPathSize;
intnNodeType;
intnPos;
intnFunSeq;
boolbFind=false;
boolbInLoop=false;
boolbDstInLoop=false;
boolbIfLoopBlockStartLine=false;
boolbReachPath=false;
boolbIfFunuseLine=false;
vector<DstNode>stlToNodeList;
PathNodeDatapndNode;
DstNodednNode;
DstNodednCurrNode=dnOriCurrNode;
map<int,PathNodeData>::iteratoriterNode;
multimap<string,YTEXPVALLIST> stlDataSlice;
multimap<string,YTPathExpData>stlConExpList;
multimap<string,YTPathExpData>::iteratoriterExp;
vector<string>stlConList;
vector<DstNode>stlFunUsePathNodeList;
map<int,int>::iteratoriterVisitNode;
stringsConStr;
YTPathExpDatapePathExp;
CStringstrInfo;
FunctionInfofiFun;
boolbHaveFunuse=false;
intnDstIndex=-1;
boolbDstStaVarLoopRelated=false;
boolbDstInPathLoop=false;
boolbIfLeafFun=false;
////////////////////////////////////////////
map<int,PathNodeData>stlPathNodeList;
nLen=sFunUseListStr.length();
if(nLen==0)
{
bIfLeafFun=false;
}
else
{
bIfLeafFun=true;
}
nFunSeq=GetCurrFunSeqByFunUseListStr(sFunUseListStr);
nFromLine=dnSrcNode.nLine;
nCurrLine=dnCurrNode.nLine;
nNodeType=dnCurrNode.nNodeType;
strInfo.Format("currline:%d",nCurrLine);
////AfxMessageBox(strInfo);
if((nCurrLine>0)&&(nCurrLine<nFunEndLine))
{
if(nFromLine==nCurrLine)
{////函数入口行
stlDataSlice=dnSrcNode.stlDataSlice;
}
else
{
if(dnSrcNode.nNodeType!=FUNUSESONLINE)////避免重复处理函数调用语句
{
stlDataSlice=GetDataSlice(sFunUseListStr,dnSrcNode,nCurrLine,dnCurrNode.nType,dnCurrNode.nNodeType);
}
else
{
stlDataSlice=dnSrcNode.stlDataSlice;
}
}
nSize=stlDataSlice.size();
dnCurrNode.stlDataSlice=stlDataSlice;
dnCurrNode.sConStr="";
PushFunCurrPathOneNode(sFunUseListStr,dnCurrNode);//加入当前路径中
UpdateFunCurrVisSetOneLine(sFunUseListStr,nCurrLine,0);
bDstInLoop=m_bDstInLoop;
bInLoop=IfLineInPathLoop(sFunUseListStr,nCurrLine);
m_nCurrExpLine=nCurrLine;
if(((nNodeType==DOBLOCK)||(nNodeType==WHILEBLOCK)||(nNodeType==FORBLOCK))&&(dnCurrNode.nType==CONTINUTOPOINT))
{////todo start line from outside
StorePreIntoLoopPath(sFunUseListStr,nNodeType,nCurrLine);
}
else
{
////nonothing
}
stlPathNodeList=GetCurrFunNodePath(nFunSeq);
iterNode=stlPathNodeList.find(nCurrLine);
if(iterNode!=stlPathNodeList.end())
{////
pndNode=iterNode->second;
stlToNodeList=pndNode.stlToLineList;////获得当前节点的目标节点
nDstCount=stlToNodeList.size();
////////////////////////////////////////////////////////////////
if((nNodeType==WHILEBLOCK)||(nNodeType==FORBLOCK)||(nNodeType==DOBLOCK))
{
bIfLoopBlockStartLine=IfLoopBranchLine(nCurrLine);
}
else
{
////donothing
}
if(bIfLoopBlockStartLine)
{////循环块起始节点
if(m_nRootFunSeq==-1)
{////根函数
nPathSize=GetFunPathSize(sFunUseListStr);
if((nPathSize>0)&&(!m_bConfirmedTrue))
{////在根函数中已经找到一条可达路径,停止搜索
bReachPath=true;
}
else
{
////nonothing
}
}
if(!bReachPath)
{
PassLoopStartNode(sFunUseListStr,stlFunUseLineList,stlToNodeList,dnCurrNode,nFunEndLine,nFromLine);
}
else
{
////停止搜索
}
}
else
{////不是循环起始节点
if(!bInLoop)
{////当前行不是循环中的语句
CBaseData*bdBaseData=GetCurrFunBaseDataObj(sFunUseListStr);
if(bdBaseData!=NULL)
{
ContinueDatacdContinue=bdBaseData->GetFunContinueByLine(nCurrLine);
if((cdContinue.nLine>0)||(nNodeType==CONTINUELINE))
{
DealLastContinueNode(dnCurrNode,stlToNodeList,nFunEndLine,0,"");
}
else
{
DealNodeOutOfLoop(sFunUseListStr,stlFunUseLineList,dnCurrNode,stlToNodeList,nFunEndLine,pndNode.nSrcNodeType);
}
}
else
{
////donothing
}
}
else
{////当前行是循环中的语句
DealNodeInLoop(sFunUseListStr,stlFunUseLineList,dnCurrNode,stlToNodeList,nFunEndLine,0,"",pndNode.nSrcNodeType);
}
}
}
else
{
////donothing
}
PopFunCurrPathOneNode(sFunUseListStr);
////m_stlVistedNodeList.erase(nCurrLine);
UpdateFunCurrVisSetOneLine(sFunUseListStr,nCurrLine,1);
}
else
{////return语句或者exit语句或者函数尾
m_stlFunReachEndList.insert(pair<string,int>(sFunUseListStr,1));
if(bIfLeafFun)
{////叶子函数,不回溯
YTPathReturnDataprdRetData;
intnParentFunSeq;
intnStaLine;
intnFunUseLine;
DstNodednNextNode;
DstNodednOriSrcNode;
vector<int>stlParentFunUseLineList;
map<int,DstNode>::iteratoriterStaNode;
map<int,DstNode>::iteratoriterSrcNode;
stringsParentFunUseListStr;
vector<string>stlActParamListStr;
FunUseInfofuiFunU;
CBaseData*bdBaseData;
sParentFunUseListStr=GetParentFunUseListStr(sFunUseListStr);
nParentFunSeq=GetParentFunSeqByListStr(sFunUseListStr);
bdBaseData=GetFunBaseDataObjByFunSeq(nParentFunSeq);
////nStaLine=bdBaseData->GetStaLineByFunUseLine(nFunUseLine);
////stlActParamListStr=bdBaseData->GetActParamListByFunUseLine(nFunUseLine);
nSize=stlFunUseLineList.size();
nFunUseLine=stlFunUseLineList[nSize-1];
fuiFunU=bdBaseData->GetFunUseInfoByFunUseLine(nFunUseLine);
nStaLine=fuiFunU.nUseStatementLine;
stlActParamListStr=fuiFunU.stlActualParamValueList;
nSize=stlFunUseLineList.size();
nFunUseLine=stlFunUseLineList[nSize-1];
////处理返回值
if(nCurrLine==nFunEndLine)
{////到达函数结束行,当前路径没有return
prdRetData=GetReturnUltimateStr(sFunUseListStr,nFromLine,"yt_no_return",nFunUseLine,stlActParamListStr);
UpdateFunUseRetDataList(sFunUseListStr,nFunUseLine,prdRetData);
UpdateMomTailByFunUseRetData(sParentFunUseListStr,prdRetData);////用返回的数据更新母函数路径队列的尾节点
////回到父函数
dnOriSrcNode=GetFunCurrPathNode(sParentFunUseListStr);
stlParentFunUseLineList=stlFunUseLineList;
stlParentFunUseLineList.pop_back();
////只能是纯粹的函数调用语句,不可能出现在分支条件中
PassLastSonFunInSta(sParentFunUseListStr,stlParentFunUseLineList,nParentFunSeq,nStaLine);
}
else
{////return或者exit行的目标节点
bdBaseData=GetCurrFunBaseDataObj(sFunUseListStr);
if(bdBaseData->IfFunReturnLine(nFromLine))
{
ReturnDatardReturn;
stringsRetStr;
rdReturn=bdBaseData->GetFunReturnByLine(nFromLine);
if(rdReturn.nLine>0)
{
sRetStr=rdReturn.sValue;
nLen=sRetStr.length();
if(nLen>0)
{
prdRetData=GetReturnUltimateStr(sFunUseListStr,nFromLine,sRetStr,nFunUseLine,stlActParamListStr);
UpdateFunUseRetDataList(sFunUseListStr,nFunUseLine,prdRetData);
UpdateMomTailByFunUseRetData(sParentFunUseListStr,prdRetData);////用返回的数据更新母函数路径队列的尾节点
stlParentFunUseLineList=stlFunUseLineList;
stlParentFunUseLineList.pop_back();
fuiFunU=GetNextStaFunUse(sParentFunUseListStr,nFunUseLine);
if(fuiFunU.nSeq==0)
{////是函数调用语句中最后一个被调用的函数
////回到父函数
dnOriSrcNode=GetFunCurrPathNode(sParentFunUseListStr);
if((dnOriSrcNode.nNodeType==FUNUSELINE)||(dnOriSrcNode.nNodeType==FUNUSESONLINE))
{////纯粹的函数调用语句
PassLastSonFunInSta(sParentFunUseListStr,stlParentFunUseLineList,nParentFunSeq,nStaLine);
}
else
{////分支语句中的函数调用语句
PassLastSonFunInBraExp(sParentFunUseListStr,stlParentFunUseLineList,nParentFunSeq,nStaLine);
}
}
else
{////还有其它函数调用要处理
////进入弟弟函数
PassSpecSonFun(sParentFunUseListStr,stlParentFunUseLineList,fuiFunU.nServerNO,fuiFunU.nUseFunLine,fuiFunU.stlActualParamValueList);
}
}
else
{
prdRetData=GetReturnUltimateStr(sFunUseListStr,nFromLine,"yt_no_return",nFunUseLine,stlActParamListStr);
UpdateFunUseRetDataList(sFunUseListStr,nFunUseLine,prdRetData);
UpdateMomTailByFunUseRetData(sParentFunUseListStr,prdRetData);////用返回的数据更新母函数路径队列的尾节点
////回到父函数
dnOriSrcNode=GetFunCurrPathNode(sParentFunUseListStr);
stlParentFunUseLineList=stlFunUseLineList;
stlParentFunUseLineList.pop_back();
////只能是纯粹的函数调用语句,不可能出现在分支条件中
PassLastSonFunInSta(sParentFunUseListStr,stlParentFunUseLineList,nParentFunSeq,nStaLine);
}
}
else
{
////donothing
}
}
else
{
////donothing
}
}
}
else
{////根函数
////回溯即可
}
}
}
第一步:去除无用变量
没有用还占用内存和堆栈,这样的变量当然要去掉。然而,没用的变量也有多种形式,有些是不容易发现的。最简单的是定义了没使用,根据编译提示将这些变量去掉即可,静态分析阶段可直接在函数中搜索并分析变量的使用情况即可。示例代码中inSolvedState、nPos、stlConExpList、iterExp、stlConList、stlFunUsePathNodeList、iterVisitNode、pePathExp、fiFun、bHaveFunuse、nDstIndex、bDstStaVarLoopRelated、bDstInPathLoop、dnNextNod、iterStaNode、iterSrcNode就是这种情况。另外一种情况是赋了一次值以后就不在使用了,示例代码中的bFind就是这种情况,这就要仔细检查了。还有一种情况是尽管使用了,但是使用该变量的语句是无用的语句,式例代码中的nDstCount、bDstInLoop、strInfo就是这种类型。因此,要检查每个变量,确认该变量的确有用。
第二步:变量定义内移
变量的作用域要尽量的小,不要“浪费”。如何变量的作用域超出实际需要,很可能会造成不必要的变量堆栈的扩充。变量定义域浪费也有多种形式,最普通的一种形式就是将变量定义在实际使用语句的前辈块中,nPathSize就是这种情况。另外一种情况是无用的语句扩大了变量的作用域,示例代码中的nSize就是这种情况,经过分析,nSize=stlDataSlice.size()是无用的语句,该语句却导致了nSize作用域的扩大。所以进行变量定义内移前最好前将无用的语句去除,以免造成最小作用域的认知错误。第三种形式是变量应用语句中存在多个变量,但是由于目标变量外的其它变量存在问题,导致了目标变量定义域的浪费,示例代码中的nLen就是这种情况。还有一种形式是最不易发现的,由于变量应用语句的位置不当,导致了变量变量作用域的浪费,bIfLeafFun就是这种情况。所以要仔细分析以确认每个变量的最小作用域。
第三步:将辅助变量放到子函数中
辅助变量是指同递归函数的参数及返回值无关的变量,既然无关,就要尽量将其放到子函数中,子函数执行结束,子函数中的变量也就释放了,因此辅助变量就不用占用堆栈了。
在实例代码中,这样的变量很多,限于篇幅,仅举一例。示例代码中的变量stlDataSlice是dnCurrNode的一个属性,dnCurrNode同递归参数直接相关,stlDataSlice是间接相关,所以将其放到子函数中,这样在递归函数中就省掉了stlDataSlice。
第四步: 最小化对象变量的定义
一个对象通常都要包括多个属性,每个属性都要占用内存和堆栈,一般情况下,总是对象的部分属性对递归参数或者返回值又作用,部分没有作用。因此,要尽量减少对象变量的使用,具体方法就是将对象变量的每个属性拆解成为独立的变量,无用的属性就不用在专用堆栈了。示例代码中dnOriSrcNode就是这种情况,实际应用中仅仅使用了其nNodeType属性,所以没有必要定义该对象,仅需要定义nNodeType就足够了。
第五步:去除无用参数,最小化参数存储空间
就堆栈来说,参数同局部变量的作用相同,因此,可参照前面局部变量的优化过程来优化参数。经过分析,示例代码中的参数dnOriCurrNode可以拆分为三个参数int nCurrLine,int nNodeType,int nType三个参数。
第六步:作用语句最近化
语句的位置会直接影响变量的作用域,因此,要将语句放在距离其后续应用的最近的地方。示例代码中nFunSeq=GetCurrFunSeqByFunUseListStr(sFunUseListStr);就是这样的语句,将其后移,变量nFunSeq的作用域也可以缩小。
第七步:减少递归相关的函数调用层次
递归函数之间每多一层函数调用,就多一次参数及变量的堆栈占用。换句话说,每减少一个层次,就减少了相应的变量的堆栈占用。示例代码中的一条路径的递归调用关系是这样的:FindAllUltiPathToSonFunEndLine----DealNodeOutOfLoop---PassFunUseStatment---PassSpecSonFun---FindAllUltiPathToSonFunEndLine,总共四层调用,经过分析及扁平化,将其优化为FindAllUltiPathToSonFunEndLine----DealNodeOutOfLoop---PassSpecSonFun---FindAllUltiPathToSonFunEndLine共三层。
总结
优化步骤并不是固定的,有时候还需要反复。示例代码在没有优化前递归200次左右既崩溃,随着不断的优化,递归的次数注解增加到300次、400次、500次乃至正常运转完成,进行上述这些优化是必要且有意义的。事实上,不仅仅是递归函数,其他函数也应该进行类似的优化,但优化也不是没有代价的,就拿示例代码来说,为了优化的目的,不得不创建了一些“小函数”,同时也会产生一些“大函数”,参数个数有可能会增加,这显然损失了易读性。总之,系统优化要在保证正常运转的前提下综合衡量代码质量的各个因素以进行恰当的优化。
附录:优化后的代码(可能仍然可继续优化,但目前已经能正常运转了)
voidCDataFlow::FindAllUltiPathToSonFunEndLine(stringsFunUseListStr,vector<int> stlFunUseLineList,int nCurrLine,intnNodeType,int nType,DstNode dnSrcNode,int nFunEndLine)
{
intnFromLine;
nFromLine=dnSrcNode.nLine;
if((nCurrLine>0)&&(nCurrLine<nFunEndLine))
{
intnSrcNodeType=0;
boolbIfLoopBlockStartLine=false;
DstNodednCurrNode;
vector<DstNode>stlToNodeList=GetDstNodeListByLine(sFunUseListStr,nCurrLine,nSrcNodeType);
dnCurrNode.nNodeType=nNodeType;
dnCurrNode.nLine=nCurrLine;
dnCurrNode.nType=nType;
dnCurrNode=GetInitCurrNode(sFunUseListStr,dnSrcNode,dnCurrNode);
PushFunCurrPathOneNode(sFunUseListStr,dnCurrNode);//加入当前路径中
UpdateFunCurrVisSetOneLine(sFunUseListStr,nCurrLine,0);
m_nCurrExpLine=nCurrLine;
if(((nNodeType==DOBLOCK)||(nNodeType==WHILEBLOCK)||(nNodeType==FORBLOCK))&&(dnCurrNode.nType==CONTINUTOPOINT))
{////todo start line from outside
StorePreIntoLoopPath(sFunUseListStr,nNodeType,nCurrLine);
}
else
{
////nonothing
}
////////////////////////////////////////////////////////////////
if((nNodeType==WHILEBLOCK)||(nNodeType==FORBLOCK)||(nNodeType==DOBLOCK))
{
bIfLoopBlockStartLine=IfLoopBranchLine(nCurrLine);
}
else
{
////donothing
}
if(bIfLoopBlockStartLine)
{////循环块起始节点
intnPathSize;
nPathSize=GetFunPathSize(sFunUseListStr);
if((nPathSize<=0)||m_bConfirmedTrue)
{////没找到,继续搜索
PassLoopStartNode(sFunUseListStr,stlFunUseLineList,stlToNodeList,dnCurrNode,nFunEndLine,nFromLine);
}
else
{////在根函数中已经找到一条可达路径,停止搜索
////donothing
}
}
else
{////不是循环起始节点
boolbInLoop=false;
bInLoop=IfLineInPathLoop(sFunUseListStr,nCurrLine);
if(!bInLoop)
{////当前行不是循环中的语句
intnContLine;
nContLine=GetContinueByLine(sFunUseListStr,nCurrLine);
if((nContLine>0)||(nNodeType==CONTINUELINE))
{
DealLastContinueNode(dnCurrNode,stlToNodeList,nFunEndLine,"");
}
else
{
DealNodeOutOfLoop(sFunUseListStr,stlFunUseLineList,nCurrLine,nFunEndLine,nSrcNodeType);
}
}
else
{////当前行是循环中的语句
DealNodeInLoop(sFunUseListStr,stlFunUseLineList,nCurrLine,nFunEndLine,nSrcNodeType);
}
}
PopFunCurrPathOneNode(sFunUseListStr);
UpdateFunCurrVisSetOneLine(sFunUseListStr,nCurrLine,1);
}
else
{////return语句或者exit语句或者函数尾
intnLen;
m_stlFunReachEndList.insert(pair<string,int>(sFunUseListStr,1));
nLen=sFunUseListStr.length();
if(nLen>0)
{////叶子函数,不回溯
////YTPathReturnDataprdRetData;
intnParentFunSeq;
intnStaLine=0;
intnFunUseLine;
////DstNodednNextNode;
vector<int>stlParentFunUseLineList;
stringsParentFunUseListStr;
vector<string>stlActParamListStr;
int nSize;
sParentFunUseListStr=GetParentFunUseListStr(sFunUseListStr);
nParentFunSeq=GetParentFunSeqByListStr(sFunUseListStr);
nSize=stlFunUseLineList.size();
nFunUseLine=stlFunUseLineList[nSize-1];
stlActParamListStr=GetCurrFunUseActStrList(nParentFunSeq,nFunUseLine,nStaLine);
////处理返回值
if(nCurrLine==nFunEndLine)
{////到达函数结束行,当前路径没有return UpdateSonFunRetData(sFunUseListStr,"yt_no_return",nFromLine,nFunUseLine,stlActParamListStr);
////回到父函数
stlParentFunUseLineList=stlFunUseLineList;
stlParentFunUseLineList.pop_back();
////只能是纯粹的函数调用语句,不可能出现在分支条件中
PassLastSonFunInSta(sParentFunUseListStr,stlParentFunUseLineList,nParentFunSeq,nStaLine);
}
else
{////return或者exit行的目标节点
intnServerNO=0;
intnUseFunLine=0;
stringsRetStr;
intnRetLine=0;
sRetStr=GetReturnInfoByLine(sFunUseListStr,nFromLine,nRetLine);
if(nRetLine>0)
{
nLen=sRetStr.length();
if(nLen>0)
{////条件不为空
UpdateSonFunRetData(sFunUseListStr,sRetStr,nFromLine,nFunUseLine,stlActParamListStr);
stlParentFunUseLineList=stlFunUseLineList;
stlParentFunUseLineList.pop_back();
stlActParamListStr=GetNextStaFunUse(sParentFunUseListStr,nFunUseLine,nServerNO,nUseFunLine);
if(nServerNO==0)
{////是函数调用语句中最后一个被调用的函数
////回到父函数
intnNodeType;
nNodeType=GetLastNodeType(sParentFunUseListStr);
if((nNodeType==FUNUSELINE)||(nNodeType==FUNUSESONLINE))
{////纯粹的函数调用语句
PassLastSonFunInSta(sParentFunUseListStr,stlParentFunUseLineList,nParentFunSeq,nStaLine);
}
else
{////分支语句中的函数调用语句
PassLastSonFunInBraExp(sParentFunUseListStr,stlParentFunUseLineList,nParentFunSeq);
}
}
else
{////还有其它函数调用要处理
////进入弟弟函数
PassSpecSonFun(sParentFunUseListStr,stlParentFunUseLineList,nServerNO,nUseFunLine,stlActParamListStr);
}
}
else
{////条件为空
UpdateSonFunRetData(sFunUseListStr,"yt_no_return",nFromLine,nFunUseLine,stlActParamListStr);
////回到父函数
stlParentFunUseLineList=stlFunUseLineList;
stlParentFunUseLineList.pop_back();
////只能是纯粹的函数调用语句,不可能出现在分支条件中 PassLastSonFunInSta(sParentFunUseListStr,stlParentFunUseLineList,nParentFunSeq,nStaLine);
}
}
else
{
////donothing
}
}
}
else
{////根函数
////回溯即可
}
}
}