단기간에 많은 처리를 하는 선행조건을 가지는 함수의 분리


선행학습으로 인라인함수(Efficient C++)과 80-20의 법칙(Efficient C++, More Effective C++, 기타)을 읽어보시면 이해가 빠르십니다. 


어떤 함수가 처리되는데 특정한 조건이 필요한 경우를 보자. 그런데 그 함수가 단시간에 자주 호출된다면, 명령어 하나라도 최적화할 필요가 있다.

파란색 - CQuadTreeNode의 소스
연두색 - CQuadTree의 소스


예를 들어 쿼트트리내 오브젝트들을 가져오는 경우 해당 노드에 다음과 같은 함수를 호출할 수 있다.

VOID CQuadTreeNode::CheckInFrustumAndNotifyToNode( CFrustum* pFrustum );

위 함수는 노드내 오브젝트와 절두체의 충돌판정 후 절두체와 충돌한다면 해당 오브젝트의 렌더링 콜백함수를 호출하는 함수로써 다음과 같은 형식을 가지고 있다. 하지만 선행조건이 걸려있다. m_iObjectCount <= 0, 오브젝트의 개수가 0보다 같거나 작다면 더이상 진행하지 않고 함수를 끝낸다.

CQuadTreeNode
VOID CQuadTreeNode::CheckInFrustumAndNotifyToNode( CFrustum* pFrustum )
{
  if( m_iObjectCount <= 0 )
  {
      return;
  }

  for( SCENENODELIST::iterator it = m_sceneNodeList.begin() ;
     it != m_sceneNodeList.end() ;
     it++ )
  { 
     if( pFrustum->IsAABBInFrustum(*(*it)->GetWorldBoundAABB()) )
       (*it)->AddedRenderEntryArrayCallBack();
  }
}


이 함수는 쿼드트리클래스의 CQuadTree::CheckInFrustumAndNotifyToNode함수에서 다음과 같은 형식으로 호출될 수 있다.

CQuadTree
VOID CQuadTree::CheckInFrustumAndNotifyToNode( CFrustum* pFrustum )
{
    // 쿼드트리의 영역 계산
    ...

    for 쿼드트리의 영역 순회
    {
       CQuadTreeNode* node = GetNode( .. );
       node->CheckInFrustumAndNotifyToNode( pFrustum );
    }
}


렌더링 쿼드트리의 특성상 아주 작은 시간에도 매우 자주 호출됨에 따라 단 하나의 명령어도 간과해서는 안된다. 즉, CQuadTreeNode::CheckInFrustumAndNotifyToNode 함수의 함수호출 명령어조차도 최소화 시켜야하는데 이 얘기는 inline으로 선언해야한단 얘기다. 하지만 inline으로 하기에는 소스의 길이가 다소 길다.(싱글톤메소드라고 해서 예외사항은 있다.) 만약 위 소스는 절대 inline이 될 수 없다 가정하면,
오브젝트의 개수 부분만을 인라인화 해서 아예 이 함수가 호출되지 않게끔 방지할 순 있다
. 다음을 보자. 수정된 부분은 굵은글씨로 표시해두었다.
 
CQuadTreeNode
inline BOOL CQuadTreeNode::IsExistNode()
{
  return m_iObjectCount>0 ? TRUE:FALSE;
}

VOID CQuadTreeNode::CheckInFrustumAndNotifyToNode( CFrustum* pFrustum )
{
  // if검사부분을 아예 없애버렸다.
  // 이 경우는 다소 위험한 방법이긴한데 릴리즈시 약간의 오버헤드를 없앨 수 있다.
  // 만약, CQuadTree::CheckInFrustumAndNotifyToNode함수에서만 호출된다면,
  // 아래 코드는 99%안전한 코드다.
  // 특정 위치의 한곳에서만 호출되는 것을 싱글톤 메소드라고 하는데 이 경우는 FORCEINLINE 매크로로
  // 강제 인라인화 할 수 있다.(VS에서는 지나치게 긴 메소드를 인라인화에서 제외시킨다.)
  assert( m_iObjectCount > 0 && "오브젝트가 하나도 없는데 호출됐네요." );

  for( SCENENODELIST::iterator it = m_sceneNodeList.begin() ;
     it != m_sceneNodeList.end() ;
     it++ )
  { 
    if( (*it)->IsInFrustum( pFrustum ) )
       (*it)->AddedRenderEntryArrayCallBack();
  }


그리고 쿼드트리에서도 약간의 수정이 필요하다. 수정된 부분은 굵은글씨로 표시해두었다.

CQuadTree
VOID CQuadTree::CheckInFrustumAndNotifyToNode( CFrustum* pFrustum )
{
    // 쿼드트리의 영역 계산
    ...

    for 쿼드트리의 영역 순회
    {
       CQuadTreeNode* node = GetNode( .. );
       if( node->IsExistNode() )
         node->CheckInFrustumAndNotifyToNode( pFrustum );
    }
}



성능이 비약적으로 상승한다.

'C/C++ > 패턴 및 리팩토링' 카테고리의 다른 글

파일간 #include 설계에 대한 생각  (0) 2010.07.07