출처 : http://sunjinyang.wordpress.com/2009/06/18/debugging-memory-leaks-with-tcmalloc-google-perftools/


리눅스에서 메모리 침범이나 메모리 누수, 혹은 복잡한 메모리 접근 관련 오류를 디버깅할때는 대부분 Valgrind 도구를 이용합니다. 하지만 Valgrind는 많은 메모리를 사용하고 실행 속도가 현저하게 느려지기 때문에, 별도의 타겟 장비에서 제한된 조건으로 동작하는 어플리케이션에는 조금 무리가 있습니다. 게다가 Valgrind의 메모리 검사 도구는 프로그램이 종료된 시점에서 누수된 메모리만 찾기 때문에, 실행 중에는 엄청나게 메모리를 사용하다가 정상적인 객체 해제 루틴이 호출되면 모든 메모리가 정리되어 찾을 수 없는 경우는 발견하지 못합니다.

구글 성능 도구를 다시 검토하고 사용하게 된 이유도 바로 여기에 있습니다. 현재 디버깅 중인 프로그램의 문제가 대략 이렇습니다. 몇날 며칠이고 문제없이 가동해야 하는 프로그램이 특정 설정을 적용한 후에는, 자고 일어나면 10~20메가씩 메모리 점유율이 한꺼번에 올라갑니다. 하지만 Valgrind 도구를 사용해도 어느 부분이 문제인지 찾을 수가 없습니다. 지난 글에서 TCMalloc 메모리 할당자로 교체한 후 문제가 해결된 줄 알았던 바로 그 패턴이기도 합니다. 그래서 이번에는 프로파일 기능을 이용해 직접 메모리 누수를 디버깅 해보고, 나중을 위해 그 과정을 정리해 보았습니다.

디버깅 환경

디버깅 환경은 우분투 9.04 x86_32 플랫폼입니다. x86_64 플랫폼에서는 이상하게 호출 그래프가 그려지지 않아서 일단 무시했습니다. 사용한 버전은 공식 홈페이지에서 다운로드 받은 1.2 버전입니다. 1.3 버전은 이상하게 프로파일 기능이 동작하지 않아 역시 무시했습니다.

TCMalloc 도구 설치 및 연결

호출 그래프를 생성하기 위해 dot 프로그램이 필요한데 이 프로그램은 graphviz 패키지에 들어있으므로 설치해야 합니다.

$ sudo apt-get install graphviz

우선, 공식 홈페이지에서 google-perftools 압축 파일을 다운로드 한 뒤 다음과 같이 빌드하고 설치합니다. [2011.05.02 추가]INSTALL 문서에도 명시되어 있듯이, x86_64 환경에서는 libunwind 라이브러리를 미리 설치한 뒤 빌드해야 정상적으로 동작합니다.

$ cd google-perftools*
$ ./configure --prefix=/usr
$ make
$ sudo make install

TCMalloc 라이브러리를 연결하는 방법은 공식 문서에도 나와 있듯이 디버깅할 프로그램 링크 마지막에 ‘-ltcmalloc‘ 옵션을 추가하거나, 실행할때 프로그램 앞에 ‘LD_PRELOAD=/usr/lib/libtcmalloc.so execute-file‘ 처럼 라이브러리를 먼저 로드해주면 됩니다. 저는 첫번째 방법을 사용했습니다. [2011.05.02 갱신] 언제부터인지는 확실치 않지만 첫번째 방법은 동작하지 않고 두번째 방법으로 해야 메모리 프로파일이 정상적으로 동작합니다.

참고로, 디버깅할 프로그램을 컴파일할때는 디버깅 심볼 옵션(-g)이 있어야 호출 그래프에서 정확한 함수 이름이 표시됩니다. 또한 최적화 옵션(-O2 등)을 사용 안하면 더 정확한 함수 호출 그래프를 얻을 수 있습니다.

프로파일 데이터 얻기

TCMalloc 라이브러리를 연결해도 기본적으로 프로파일 기능은 동작하지 않습니다. HEAPPROFILE 환경변수에 프로파일 정보를 주기적으로 덤프할 파일 이름 접두사(prefix)를 지정해야만 동작합니다.

$ HEAPPROFILE=/tmp/profile execute-file

접두사는 파일 절대 경로로 디렉토리를 포함할 수 있습니다.

만일 덤프 파일이 너무 자주 생성되거나 반대로 너무 드물게 생성된다면 환경 변수를 통해 간격을 조절할 수 있습니다.

$ HEAPPROFILE=/tmp/profile 
  HEAP_PROFILE_ALLOCATION_INTERVAL=107374182400 
  execute-file

더 자세한 옵션은 공식 문서를 확인해 보시기 바랍니다.

위 예제에서 지정한 방식대로 프로그램을 실행하면 /tmp/profile.0001.heap/tmp/profile.0002.heap/tmp/profile.0003.heap 등과 같은 프로파일 덤프 파일이 실행 도중 계속 생성됩니다.

결과 그래프 얻기

포스트스크립트(PostScript) 파일 형식으로 메모리 프로파일 정보를 포함한 함수 호출 그래프를 얻으려면 다음과 같이 형식으로 pprof 프로그램을 실행하면 됩니다.

$ pprof --ps --lines 
    execute-file 
    /tmp/profile.0001.heap 
    > profile-0001.ps

프로파일 덤프 파일에 대하여 하나씩 그래프를 생성해 직접 눈과 손으로 비교하는 것도 나쁘지 않지만, 두 프로파일의 차이점만 그래프로 만들어주는 옵션이 있으므로 이를 사용하면 더 편리합니다. 즉, 시간대별 메모리 사용량의 달라진 부분이 정확하게 어느 함수 호출 때문인지 알 수 있게 해줍니다.

$ pprof --ps --lines 
    --base /tmp/profile.0001.heap 
    execute-file 
    /tmp/profile.0002.heap 
    > profile-0002-diff.ps

물론, PDF(--pdf), GIF(--gif) 등과 같은 다른 형식으로 그래프 파일을 얻을 수도 있습니다. 더 자세한 옵션은 ‘pprof --help‘를 참고하시면 됩니다.

결론

며칠간 디버깅에 적용해보니 문제가 발생한 패턴이 무엇이었는지도 찾아내고, 간과했던 작은 메모리 누수 버그들도 함께 발견할 수 있었습니다. 하지만, 언제나 그렇듯이, 디버깅 사태까지 오기 전에 더 튼튼하게 설계하고, 더 꼼꼼하게 프로그래밍하고, 더 철저하게 코드 리뷰와 테스트를 거치는 게 정도임을 새삼 깨닫습니다.

출처 : http://blog.daum.net/jchern/13756799


download : http://code.google.com/p/google-perftools/downloads/list

toturial : http://google-perftools.googlecode.com/svn/trunk/doc/cpuprofile.html

 

설치방법

tar -zxvf google-perftools-1.6.tar.gz

cd google-perftools-1.6

./configure

make

su

make install

 

 

사전 작업

1. 프로파일링을 워하는 프로그램 컴파일시에 -lprofiler를 같이 컴파일한다.

2. <google/profiler.h>를 include한다

 

bash >

export LD_LIBRARY_PATH=/usr/local/lib

export CPUPROFILE=output.txt( CPUPROFILE로 설정된 파일명으로 출력파일이 생성된다 )

 

특정 함수에 대해서만 프로파일링을 하기 위해서는

ProfilerStart()와 ProfilerStop()를 사용하면 된다.

ProfilerStart()에는 함수명을 파라미터로 준다.( ProfileStart("test_func"); )

여기까지 설정한후에 실행을 하면

CPUPROFILE에 설정한 파일명으로 파일이 하나 생성이 된다.

이파일은 바이너리 포맷이라서 그냥 볼수는 없고

 

/usr/local/bin/pprof 라는 파일로 볼수 있다.

/usr/local/bin/pprof [실행파일명] [output.txt]

(pprof)라고 프로프트가 뜨면 top를 입력

 

(pprof) top

 

상위에 노출되는 함수들이 실행시간이 오래걸리는 함수들이다.

 

 

 

특정 함수에 대한 프로파일링 :

pprof    --gv --focus=함수명   실행파일 출력파일

 

특정 함수를 제외한 프로파일링

pprof   --gv --ignore=함수명  실행파일 출력파일


 

좀더 자세한 내용은 요기서 ; http://google-perftools.googlecode.com/svn/trunk/doc/cpuprofile.html

 

Memory Allocator for Multithreaded Applications

출처 : http://www.mimul.com/pebble/default/2011/05/15/1305469792430.html 

 

응용 프로그램에서 메모리 사용은 필수 불가결한 거죠. 그런데도 메모리에 대한 사용상 효율, 성능을 잘 고려하지 않고 그냥 있는 malloc을 사용하고 말죠. 그런데 트래픽이 많은 서비스가 요즘 많아지다보니 메모리 사용에도 신경을 써야하는 경우가 많습니다.
그래서 메모리 할당에 관해 정보를 정리하고 공유합니다.


메모리를 할당받기 위해 사용하는 malloc()함수는 일반적으로 glibc에 포함된 메모리 할당자에서 구현이 되어 있습니다. 그리고 메모리 할당자는 ptmalloc입니다.
메모리 할당자의 역할은 brk/sbrk/mmap등을 사용해서 시스템으로부터 큰 메모리 영역을 할당 받아서, 이것을 적절하게 분할하여 어플리케이션이 요청하는 메모리 할당을 처리하게 됩니다.
하지만, 빈번하게 메모리를 할당/해제하고 수십 개의 쓰레드가 동작하는 프로그램에서는 어쩔 수 없이 메모리 단편화(Memory Fragmentation)가 발생하여 메모리 사용량이 늘어나게 되죠.
그래서 규모가 큰 어플리케이션들은 glibc의 기본 메모리 할당자인 ptmalloc이 메모리 단편화 문제가 심하고, multi thread, multi processor에 대한 고려가 적어서 performance bottleneck이 존재하여 다른 메모리 할당자를 사용하는 경우가 있습니다.


데이터 섹션(메모리 사용 구조)

 - 전역 메모리(Global Memory) : 지역 함수(Local Function), 전역 변수(Global Variable), 정적 변수(Static Variable)
 - 스택 메모리(Stack Memory) : 지역변수와 매개 변수가 저장된 곳. 컴파일 타임에 크기 결정. 메모리 생명 주기를 알기 때문에 단편화가 안일어남.
 - 힙 메모리(Heap Memory) : 동적 메모리 할당을 위한 곳. 프로그래머가 할당 및 해제해야 함. 런타임에 크기 결정. 메모리 생명 주기를 모르기 때문에 단편화가 일어남.

컴파일러 및 링커가 메모리 할당 기능을 수행할 때에는 메모리 단편화가 일어나지 않습니다. 왜냐하면 컴파일러가 데이터의 수명을 알고 있기 때문입니다.
메모리 할당자는 기본적으로 오버헤드, 내부 단편화, 외부 단편화 등 3가지 측면에서 메모리를 낭비합니다.

메모리 낭비 종류

 - 오버헤드 : 메모리 할당자는 할당 상태를 설명해 주는 일부 데이터를 저장해야 하는데. 즉, 모든 자유 블록의 위치, 크기, 소유정보, 그리고 내부 상태와 관련된 정보를 저장해야 합니다. 일반적으로 메모리 할당자가 이러한 오버헤드(부가) 정보를 저장하기에 가장 좋은 장소는, 할당자가 자체 관리하는 메모리.
 - 내부 단편화 : 모든 메모리 할당 작업은 프로세서 아키텍처에 따라 4, 8, 16으로 나뉠 수 있는 주소에서 시작되어야 함. 이렇게 미리 정의된 크기로만 클라이언트에게 블록을 할당하는 데는 또 다른 이유가 있을 수 있다. 만약 클라이언트가 41바이트 블록을 요청하면 42, 48 또는 그 이상의 바이트를 얻게 된다. 클라이언트가 요청한 크기를 반올림한 결과 남게 되는 여분의 공간. 이것이 내부 단편화.
 - 외부 단편화 : 애플리케이션이 연속해서 세 블록을 할당한 후, 가운데를 제거하면 외부 단편화가 발생.


Memory Allocator 종류

 - tcMalloc : Google에서 만들었고 오픈 소스며, 메모리 프로파일 기능도 제공함. Linux, 작은 사이즈의 allocation에 최적화 된 것, MacOsx, Windows 지원. 레퍼런스 가장 많음. 임베드 시스템에도 많이 활용됨, NUMA 아키텍처 지원
 - jemalloc : 오픈 소스며, Linux ,MacOsx에 최적화 되었고 Windows도 지원. FireFox, Facebook에서 쓰여 레퍼런스가 늘어가고 있음. NUMA 아키텍처 지원은 아직 레퍼런스 찾지 못함
 - nedMalloc : 오픈소스며 간단하고 Windows에 최적화. dlmalloc을 뿌리로 둠.

싱글 스레드 환경에서는 ptmalloc과 위의 메모리 관리자와의 성능과 효율이 크게 다르지 않을 수 있고 멀티 쓰레드 환경에서 위의 메모리 할당자를 사용하므로써 많은 성능 향상을 보았다고 함.


tcMalloc 소개

 1. 작동 방식(성능 향상 방식)
  - 중앙 메모리 관리자와 쓰레드별 메모리 관리자를 구분하고 작은 크기(32K 이하)의 메모리 할당/해제 요청은 쓰레드별 메모리 관리자가 처리하고, 부족할 경우 중앙 메모리 관리자에서 얻어오는 방식으로 처리함.
  - 큰 메모리(32K 이상)는 전역 관리자에서 페이지 크기(4K) 단위로 클래스를 나누어 mmap()을 이용하여 할당하는 함.
 2. 지원 환경
  - Linux (32 and 64 bit), Mac OSX, Windows (32 bit only), Solaris
  - NUMA-aware TCMalloc(NUMA아키텍처 지원됨)
 3. 사용 사례
  - Webkit, MySQL, HyperTable, Memcached, Redis, Nginx


jemalloc 소개

 1. 작동 방식(성능 향상 방식)
  - 쓰레드별 메모리 관리자 Arena와 작은 단위의 잦은 메모리 할당의 경우, arena를 참조 하지 않고, 바로 malloc을 할 수 있도록, 각 스레드에게 thread cache라는 영역을 가지고 있어 성능 향상을 줌.
 2. 지원 환경
  - Linux(32, 64 bit), Windows, MacOSX
 3. 사용 사례
  - FireFox, FaceBook


nedMalloc 소개

 1. 작동 방식(성능 향상 방식)
  - dlmalloc에서 출발했고, 오픈 소스며 내용은 간단하지만 성능도 좋다는 평으로 알려짐.
 2. 지원 환경
  - Windows(32) 최적화됨, MacOSX, Linux
 3. 사용 사례
  - 소소한 개인 사용자 위주. 윈도우 개발자들이 많이 채택함. 일부 Javascript 엔진에 사용


SLAB Allocator

 - 슬랩은 내부 단편화 문제를 해결할 뿐만이 아니라 커널 내에서 흔히 일어나는 dynamic memory 할당의 overhead를 줄이기 위하여 캐싱하는 역할을 하여 성능 개선에도 큰 도움을 주고 있음.
 - 캐시는 관리가 필요한 오브젝트 종류별로(예를 들면task_struct, file, buffer_head, inode 등) 작성되고 그 오브젝트의 슬랩을 관리하고 슬랩은 하나 이상의 연속된 물리 페이지로 구성되어 있으며, 일반적으로 하나의 페이지로 구성된다. 캐시는 이러한 슬랩들의 복수개로 구성됨.
 - 자주 사용되는 오브젝트들을 미리 할당하여 놓고 사용자 요구가 있을 때 마다 바로 반환하는 것이다. 이것은 물리 메모리를 확보하기 위하여 검색 및 회수, 반환과 같은 긴 여행을 떠날 필요가 없으므로 시스템의 성능을 향상시킨다. 또한 다 사용된 오브젝트들을 반납받아 커널의 메모리 할당자에게 반환하지 않고 보관했다가 재사용하기 때문에 시스템의 성능을 향상시킬 수 있게 됨.


응용 예(MySQL-tcMalloc)

 - MySQL 성능 향상 위한 tcMalloc 적용 예


// 64bit 머신일 경우에만 필요함. libunwind
wget http://download.savannah.gnu.org/releases/
  libunwind/libunwind-0.99-alpha.tar.gz
tar zxvf libunwind-0.99-alpha.tar.gz
cd libunwind-0.99-alpha/
CFLAGS=-fPIC ./configure
make CFLAGS=-fPIC
make CFLAGS=-fPIC install

wget http://google-perftools.googlecode.com/
   files/google-perftools-1.7.tar.gz
tar zxvf google-perftools-1.7.tar.gz
cd google-perftools-1.7/
./configure
make && make install

vi /etc/ld.so.conf
/usr/local/lib 라인 추가
/sbin/ldconfig

vi /usr/local/mysql/bin/mysqld_safe
export LD_PRELOAD=/usr/local/lib/libtcmalloc.so


[참고 사이트]

 

1. http://code.google.com/p/gperftools/wiki/GooglePerformanceTools 에 들어가서 최신 다운 경로를 가지고 옴

 (지금 2013년 2월 기준으로 http://gperftools.googlecode.com/files/gperftools-2.0.tar.gz 이거이니까..)

 

 

 

 

 

2. 압축푼곳에 들어가서 ./configure 실행

 

 

 

3. 잘되면 모르겠는데 안되면 해당 로그를 찾아 의존성 패키지를 설치하자.

내 경우에는 g++가 없다는 에러가...

 

 

4. 이후

#make

#make install

 

* 주의점

  INSTALL 파일에 의하면 64비트 환경에서 설치할땐 에러가 있다고 한다.

(https://groups.google.com/forum/?fromgroups#!topic/google-perftools/ZUvzAkYI3G0)

  libunwind를 살포시 설치해 주자.

 

 

=============================================================================================================

64비트 리눅스라면...

 

1. http://pkgs.org/centos-6-rhel-6/epel-x86_64/gperftools-libs-2.0-3.el6.2.x86_64.rpm.html

여기로 들어간다.

libunwind 설치 페이지가 있는데 rpm를 wget을 이용해서 다운로드 한다.

 

이후.. rpm 설치...

=============================================================================================================

 

요약...

 

이러면 centos 에서 설치가 된다..... ㅠㅠ

+ Recent posts