2010년 1월 13일 수요일

STL로 범한 죄악

신입때 opengl로 2D 엔진을 개발할때였다.

한글을 찍기 위해 조합형 한글 방식을 취했는데
한글자당 자음 모음 자음 형태로  찍어야 했으므로 글자당 필요한  인덱스가 3개가 필요 했었다

그리곤 , 아무 생각없이 인덱스를 받아  해당 택스쳐 위치를 찍어 한글을 만들어 냈는데...
글짜를 찍는 순간부터 cpu가 무려10%이상 오르는  황당한 경험하게되었다
( 디버깅 모드였던걸로 기억한다, 그래도 터무니 없긴 마찬가지;;)

아무리 봐도 글자가 이렇게 큰 부하가 걸릴거라고는 생각못했는데 , 너무 많이 부하가 걸리는것이었다
의심되는 코드를 찾아보아도 딱히 그럴만한 작업을 한게 없었다

아무리 눈으로 코드를 보더라도 , 눈으로는 확인할수 없어
나름 허접한 프로파일링 끝에 아래와 같은 코드가 모든 악의 근원인것을 알게되었다


// 겉보기에는 문제 없는 코드
void GetXXIndex( std::vector<int>* pIndexVec)
{
     pIndexVec->push_back(1)
     pIndexVec->push_back(2)
     pIndexVec->push_back(3)
}

겉 보기에는 아무런 문제가 없고 멀쩡한 함수처럼 보인다

하지만 저 함수가 일으킨 문제는 로직부하의 30%가 넘을만큼  터무니 없는 수치인것이다

도데체 왜 문제가 된것일까?

일단 가장 핵심적인 사항으로는 글자를 찍는 만큼 저 함수가 호출되므로 , 매 프레임당
수십에서 수백번까지 호출되어야 했다는 것이다
결국  글자가 많아질수록 저 함수 호출은 늘어나는 구조고 , 그 무지막지한 부하가 비례 했다는것이
결정적인 문제였다

하지만 단순히 함수호출이 많다고해서 부하가 크게 걸리린다는건 어불성설이고

일단  가장 상식적인 것으로는 컨테이너에 담길 갯수가 3개라는 것을 알면서도
가변 크기를 담는 vector를 선택한게 문제가 아닐까라는 결론에 이르게 된다 ,

물론 STL에서 권장하는 vector의 reseve 함수를 생략하긴 했다 [이놈의 귀차니즘...]
인지하고 있던 사항이지만  , 그래도 터무니 없지 않나 라는 생각이 들었던것이다

아니 이런 생각부터 잘못됐다 , 3개라는 걸 알고 있었으면 고정 크기 배열을 넘겼어야 하는것이
원칙인것이다

그렇다 정답을 알면서도 대충 vector를 남용한 결과를 다시 확인하기 위해
vector 의 소스를 보며  메모리 할당 정책을 다시 훌터봐야 했다

물론 STL책에서 이미 알고 있던 사항들을 재 확인하는것에 불과 했지만
[
   _Capacity + _Capacity / 2; // try to grow by 50%  
   그래도 작으면 늘어난 size만큼 capcity를 늘림
 ]
즉 현재 크기가 메모리 할당량보다 크면 , 메모리 할당양이  50% 크기가 증가하는 코드였던것이다
[이건 STL 구현마다 다르다]

따라서 3번의 push_back 모두  메모리 재할당이 일어나는 코드가 된것이다
재할당은 new delete를 필연적으로 불러오고 결국 저 함수는 new1[1] delete ,  new[2]  delete ,
new[3] delete 를 반복한 코드와 같다
[ 참고로 어느정도 크기가 되야 , 저런 무식한 할당에서 피할수 있게 된다 ,
예를 들어 100 이면 다음 크기는 150이 되므로 50번의 push_back으로부터  추가적인 메모리 할당을
피할수 있게 된다]

그런데 수십에서 수백번까지 지속적으로 반복해서 저 함수를 불리었으니  
매 프레임당 수백번까지 메모리 재할당를 하게 된것이다
[ 플레폼마다 힙메모리 관리가 다르기도 하고 , 성능 차이가 존재하기도 하기에
  힙관리의 부하의 차이는 당시 플레폼에 의존한다  ]

이 처참한 결과를 확인하고 단순히 vector를 고정 배열로 넘기자  그 무지막지한 cpu 사용량은 거의 사라져 버렸다
 그리고 그 함수의 호출 갯수에 비례해 늘어나던 cpu 소모량은 거의 발생하지 않았다
한마디로 고정배열을 넘기게 함수를 바꾸자    부하가 아예 없는 수준으로 바뀌었다

이 같은 경험으로 힙메모리의 무분별한 사용은  상상 이상의 부하를 초래한다는것을 깨달았고
말로만 듣던 new delete의 폐해를 당시 처음으로 느꼈었다
[그렇다고 , 어느 자료구조든 같은 문제를 앉고 있기때문에 STL만의 문제는 아닐것이다
문제는 역시 쓰는 사람일것이다]


오랜 고민 끝에 이 같은 문제에서도 ,  vector와 같은 인터페이스로 문제를 해결하고 싶은 욕심이났다
그래서 만든것이 fix_vector이다

[fix_vector 자료실 링크가 깨진관계로 다시 여기에 올립니다]

 개인적으로 라이브러리를 완벽하게 만드는건 어렵기때문에  기왕이면 검증된  라이브러리를
가져다 쓰려노력한다 , 하지만  fix_vector 같은건 절대 만나볼수가 없어 , 결국 직접 만들어버렸다

나름 배열이 사용되어야 할곳에 적절히 쓰이면 쓸만한거 같았다



댓글 없음:

댓글 쓰기