memory를 가장 많이 사용하는 module 알아내기


(C관련 재미없는 내용입니다)

memory가 부족하다는 메시지가 나옵니다.
RAM을 말하는 거죠. Dynamic Memory allocation입니다.
Program상에서 memory를 많이 사용하는 것인데, 가장 많이 사용하는 부분을 찾아내서 
개선하는 것이 가장 효율적일 것 같다는 생각이 듭니다. 근데, 그 부분을 어떻게 알아내나요?

일단 Application에서 사용하는 '전체 memory의 크기'를 알아내기는 쉽습니다.
ps, top, 작업관리자를 사용하면 알 수 있죠.
근데, main내에서 어떤 코드가 memory를 얼마만큼 사용하고 있는지를 아는 것은
그리 간단한 일이 아니더군요.

관련 Tool을 찾아보았는데, 아직 찾지를 못했습니다.
누군가 알고 계시면, 부탁 드리고요.


제가 사용하는 방법이 도움이 될까 하고 적어봅니다.
저희 팀은 module 단위로 개발을 합니다.
하나의 소프트웨어는 수십, 수백 개의 module을 합쳐서 만들죠. Library라고 불러도 됩니다.
대부분 내부 Source이지만, 외부 Library도 사용합니다.
알고 싶은 것은 memory를 많이 사용하는 module을 순서대로 찾아내는 겁니다.

원리는 간단합니다.
Runtime상에 malloc과 free가 호출될 때마다 address와 size를 모두 기억해놓고,
특정 시점에서 해당 module에서 사용하는 malloc의 size를 합치면 됩니다.
그리고 Sorting해서 보여주기만 하면 되는 거죠.



[ malloc과 free를 intercept 하기 ]
malloc은 standard library 내부에 구현되어 있습니다.
Library는 malloc을 reference 하도록 되어 있죠.
이 중간에 code를 집어넣으려고 하는 겁니다.
Standard library를 수정하던가, Library의 reference를 수정하던가.
후자가 더 쉽습니다. Library를 하나 복사해서 malloc의 reference를 Malloc으로 바꾸어 줍니다.
ELF format을 공부하시면 됩니다. 그리 복잡하지 않습니다.
혹은 단순하게 malloc string을 찾아서 변경해도 정밀하지는 않지만 대부분 동작할 겁니다.

그리고 Malloc 코드를 작성하여 link시에 합쳐줍니다.
그러면 해당 module에서 malloc을 호출할 때마다 우리가 작성한 Malloc()이 불리게 됩니다.

같은 방법으로 free, realloc, strdup등등 필요한 것을 추가해줍니다.



[ malloc을 누가 호출했는지 ]
우리가 관심 있는 건 malloc을 사용하는 module의 이름이죠.
이것을 알아내는 것도 간단합니다.
Compiler의 도움을 받으면 되는데, 아래를 사용하면 됩니다. (gcc 경우)
__builtin_return_address(0)
__builtin_return_address(1)
__builtin_return_address(2)

이런 식으로 부르면 call stack을 쉽게 얻을 수 있습니다.



[ source code 알아내기 ]
근데, call stack에서 얻은 것은 address이죠.
Module을 알려면 file 정보가 있어야 합니다.

debug option을 켜놓으면 실행 file 내부에 debug 정보가 포함됩니다.
gcc라면 DWARF라는 format을 사용하는데, 이건 ELF보다 조금 더 복잡합니다.
공부하시는 것 추천하지 않고요.
대신에, 아래의 명령어를 사용하면 디버깅정보를 이용하여 filename과 lineno를 알 수 있습니다.

addr2line -e main <addr>

여기서 <addr> 이란 위의 __builtin_return_address()에서 받은 값입니다.
이제 필요한 정보를 모두 얻었습니다.
이 정보들을 합치면 어떤 module 혹은 source에서 사용하는 memory의 크기를 알 수 있습니다.



좀 더 자세히 설명할 수도 있지만,
서로의 개발 환경이 달라서 원리만 설명하는 것이 좋을 것 같아 간단하게 적습니다.
게다가 비워져 있는 부분을 스스로 채우는 것이 더 창의적이적인 것 같기도 하고요.

도움이 필요하시면 리플이나 메일로 알려주시고요~
조언도 환영입니다.

고맙습니다.

by 제임스 | 2012/01/18 20:22 | 메인스토리 | 트랙백 | 덧글(18)

트랙백 주소 : http://jamestic.egloos.com/tb/2893639
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
Commented by Terzeron at 2012/01/18 23:26
혹시 valgrind의 component인 massif를 써보시는 건 어떨까요...
http://valgrind.org/docs/manual/ms-manual.html
Commented by 제임스 at 2012/01/19 16:21

늘 사용하는 valgrind에 이런 기능이 있는줄 몰랐습니다.
특히 snapshot 개념이 인상적이군요. call stack별로도 정보를 보여주니 딱 맞지는 않더라고 충분히 사용할 수 있을 것 같습니다. call stack 의 어디부터 어디까지가 module에 해당하는지는 사람이 판단해야 할 것 같은데, 이건 사실 valgrind 할 수 있는 영역이 아닌거죠. 이 부분을 개발환경과 연결할 수 있는지 생각해 봐야 겠습니다.

valgrind는 intstruction code emulation을 하기 때문에 execution time이 영향을 받는 것 빼고는 매우 훌륭한 방법인 것 같습니다. 감사하고요 Terzeron님.
Commented by 김민장 at 2012/01/19 02:27
http://www.pintool.org/downloads.html Pin이라는 dynamic binary instrumentation toolkit으로 말씀하신 바를 훨씬 쉽게 할 수 있습니다. 특정 모듈, 함수 시작과 끝을 instrumentation해서 시작할 때와 끝날 때의 heap 양을 체크하면 간단히 만들 수 있을 것 같네요. malloc/free 역시 Pin으로 간단히 가로챌 수 있습니다. 공부 좀 해야하지만 가장 편리하고 적은 오버헤드로 할 수 있고요. Valgrind에 이미 관련 모듈이 있다면 그걸 쓰면 더 편할 것 같네요.
Commented by 제임스 at 2012/01/19 16:29
오 이런 것도 있군요. Pin이라… 게다가 Intel에서 프로젝트를 지원을 하는 군요.
민장님은 모르시는 것이 없군요. 자세히 보지는 못했지만, valgrind와 같이 instruction code emulation을 하는 것으로 보이네요. Code insertion을 할 수 있다는 것이 매력적으로 보입니다. Purify 같은 tool도 만들 수 있겠는데요. 근데, 모듈의 시작과 끝으로 하는 것은 조금 문제가 있더라고요. gcc가 linking해놓을 것을 보면 library별로 linear하게 하는 것이 아니라 뒤죽박죽으로 만들어 놓더라고요. 이걸 방지하는 option이 있는지는 모르겠네요.

어쨌든, 시간내서 공부하면 상당히 재미있을 것 같은 녀석입니다. 민장님, 고맙습니다.
Commented by 김민장 at 2012/01/19 16:47
Pin은 인텔에서 만들었고 지금도 계속 지원하고 있습니다. Valgrind와 목적은 비슷한데 그 보다 오버헤드가 훨씬 낮고 사용하기가 간편합니다. 원래 프로그램을 건들이지 않고 재컴파일이 없이 동적으로 바이너리를 직접 인스트루멘테이션해서 편리하죠. 명령어를 에뮬레이션을 하는 것이 아니라 바로 코드를 넣고 동적으로 재컴파일해서 돌리는 구조입니다.

모듈의 정확한 정의가 뭔지 궁금하군요. 디버깅 정보만 있으면, 함수 시작/끝을 감지할 수 있습니다. (그런데 최적화가 심하면 함수 끝이 ret가 아니라 점프로 대체되는 경우도 있어서 완벽하게 탐지가 되는 것은 아닙니다.) 즉, 함수별, 좀 더 저수준으로 가면 basic block 별로 메모리 사용률도 체크하는 Pin 프로그램을 쉽게 만들 수 있습니다.

말씀하신 Purify 같은 거, 더 나가 메모리 버그나 데이터 레이스 감지 툴도 Pin으로 많이 만들고 또 만들어져있습니다.
Commented by 제임스 at 2012/01/19 17:49
그렇게 동작하는 방식이군요. Native code라니, 더욱 더 흥미로워 집니다.

module은 library이라고 생각하시면 됩니다. Library별로 담당자가 있으니, 1차적으로는 어떤 library를 줄이는 것이 좋을지 먼저 선택을 하는 것이 우선이고요. Library는 여러 개의 함수로 만들어져 있는데 link map을 보면 이 함수들이 다 분산되어 나타납니다. Map file을 보고 library의 모든 함수의 주소를 찾아 Code를 넣어주면 해결이 될 것 같기는 합니다.

민장님, 혹시 PowerPC 계열이나 MIPS, AVR에도 유사한 tool이 있나요?
사실 Intel 계열도 embedded환경상에서는 동일한 문제가 발생할 것 같기는 한데, hex code를 직접 변경시킨다고 하니, 적용될 수 있을 것 같아서요. 현재는 방법이 없어서 linux나 solaris에서 source를 recompile해서 emulation으로 valgrind를 사용합니다.
Commented by 김민장 at 2012/01/19 18:08
Pin은 다이나믹하게 코드를 넣고 빼야하니 가벼운 VM이 필요는 합니다. JIT을 하는데 그래도 코드캐시가 있으므로 성능 손실은 상당히 가벼운 편입니다.

함수 단위로 프로파일이 가능하니 함수 단위로 뽑으면 그 함수가 속해있는 라이브러리별도 통계가 쉽게 나오리라 생각합니다. 참고로 말씀대로 static library라면 resolve해야하는 심볼 순서에 따라 여러 라이브러리에서 코드를 배치할 수 있으므로 분산되어 나타나는 것이 일반적입니다.

PPC, MIPS는 아는 바가 전혀 없습니다; 크로스컴파일, 에뮬레이션에 Valgrind까지 쓰면 속도가 무척 느려지겠군요. 이종구조 간에 바이너리 트랜슬레이션이 중요한 문제입니다.
Commented by 제임스 at 2012/01/20 10:21
Pin이 valgrind보다 속도면에서는 충분한 장점을 가지는 군요. 좋습니다.
PPC, MIPS용은 아마도 없는가 보군요. 아무래도 host 환경이 아니다 보니, 제한적인 것 같습니다. Target의 Binary code를 emualtion하는 것은 아니고요. Source를 Recompile해서 작업을 합니다. 이런 방법으로도 버그의 90%는 linux환경에서 잡을 수 있죠. Valgrind는 문제가 발생할 때만 사용을 하는데, 예상대로 속도때문이고요. Pin을 사용하면 상시 emulation에도 쓸 수 있겠다는 생각이 듭니다. 민장님 설 잘보내시고요~
Commented by Kimdo at 2012/01/19 14:28
소개하신 내용은 좀 오래된 된 테크닉이고 요즘에 소개된 툴들이 많습니다.
Commented by 제임스 at 2012/01/19 16:30
Kimdo님, 네 열심히 공부하겠습니다~ ^^
Tool 한두개정도 알려주시고요...
고맙습니다
Commented by 오리™ at 2012/02/06 16:57
안녕하세요. 맨날 눈팅만 하다가 아마도 처음 댓글 쓰는거 같은데요.
http://dev.paran.com/2011/05/12/last-free-lunch-facebooks-memory-allocator-jemalloc/
이거 읽어 보시면 좀 도움이 되실런지도 모르겠습니다. 성능 향상도 있다고 하고요. 구글의 tcmalloc 같은 경우 힙 프로파일러가 있다고 하는데, 원하시는게 이런게 아닐까 싶어요.
C/C++ 코딩 안한지 10년이 넘은지라 제가 직접 써 보진 못했습니다.
좋은 글 늘 잘 읽고 있습니다. :)
Commented by 제임스 at 2012/02/07 12:53
오리님, 이거 멋진데요.
LD_PRELOAD가 마술이군요.
dynamic loading할 때, 이를 replace할 수 있는지는 처음 알았습니다.
linux라고 한정진다면 제가 설명한 것보다 훨씬 간편하게 작업을 할 수 있겠네요. 많이 배웁니다. ^^
(리플에는 -ltcmalloc을 쓰라는 이야기도 있네요)

Heap Profiler가 비슷한 일을 하겠지만, 결국 module 개념을 이해하는 것은 추가적인 정보가 있어야 하기 때문에 general한 solution으로는 애당초 한계가 있는 일이죠. 그래도 충분히 훌륭할 것 같습니다.
세상에는 참 똑똑한 분들이 많습니다. 하하
Commented by 박철민 at 2012/02/09 12:54
혹시 binary hacks라는 책을 보셨는지요~ ㅎㅎ
추천드립니다. LD_PRE LOAD와 같은 마술들이 깨알같이 소개되어 있습니다.
Commented by 제임스 at 2012/02/09 21:43
와우, 일본에서도 이런 류의 책이 나오는 군요.
목차만 살펴봤는데, 매우 흥미로울 것 같습니다.
APUE 보았을 때 설레이는 그 느낌을 다시 받을 것 같아 좋네요.
작은 선물하려다가 종합 선물 세트를 받았습니다. 하하
고맙습니다~ 철민님
Commented by 오리™ at 2012/02/10 08:49
우왕 binary hacks 라는 책도 있군요. 역시 나누면 더 큰것을 얻는 법입니다. :)
APUE 언급을 보고 반가운 마음에 또 댓글을 답니다. R. stevens 아저씨의 명작이죠.
Unix Network Programming, TCP/IP Illustrated 시리즈와 더불어
책을 보는 사람의 마음을 늘 설레게 했던... 하하.. 행복했던 옛 기억이 떠오르네요.
Commented by 제임스 at 2012/02/12 23:41
APUE 하고 친하신것보니 오리님은 대단한 고수님인가 봅니다. 저는 아주 작은 부분 이해하기도 버거운 책이더라고요. 이해하고, 구현하고, 돌아서면 잊어버리는 그런 책이랄까요? 하하 그나저나 xinuguru의 xinu가 OS를 말하는거 맞나요? 정말 오랜만에 들어보는 이름이라... ^^
Commented by 오리™ at 2012/02/14 14:43
아는거 별로 없으면서 주워 들은 것만 많은 허접입니다.
제임스님 글 보면 공력이 느껴지던데요 ^^

'xinu' == 'unix'[::-1] 입니다. 요즘 파이썬 열공하고 있어서요. 헤헤헤..
Commented by 제임스 at 2012/02/15 11:40
하하, 네 안그래도 오리님이 궁금해서 블로그 보고 파이선 공부하시는 것 봤습니다.
사실 XINU라는 OS가 있어요. UNIX를 거꾸로 한 것인데 Linux 나오기 한 10여년전에 있던 OS죠. 저희 선배님들이 사용하시던거라서, 오리님 연배가 그렇게 많으신가 했었는데, 30대 이시더라구요. 그외도 즐겁게 봤구요 ^^

:         :

:

비공개 덧글

◀ 이전 페이지          다음 페이지 ▶