koodev

TCMalloc 사용하여 TF 메모리 누수 방지

Programming

Tensorflow 사용시 메모리 누수가 발생하는 경우가 있어 오랬동안 고생하다 최근에 찾은 솔루션을 정리한다. 방법은 시스템 malloc 을 TCMalloc 으로 교체하는 것이다.

일부 시스템에서의 malloc 이 Tensorflow 와 함께 사용될 경우 반복적인 시스템 메모리에 접근하는 상황에서 메모리 회수가 잘 안되는 것 같다. 내 경우엔 다른 머신간에 다른 양상이 나타났는데, ① 메모리 누수가 안 발생, ② 시스템 메모리가 꽉 차서 해당 프로세스 kill, ③ 시스템 메모리가 꽉 차서 PC 먹통 등으로 나타났다(3번 경우 때문에 재택근무 중 사무실 출근이 필요했음).

시스템 malloc 을 TCMalloc 으로 간단하게 대체하기 위해서 LD_PRELOAD 트릭을 사용한다. LD_PRELOAD 는 환경변수로 넣어주는데, 프로그램이 로드되기 직전에 LD_PRELOAD 로 지정한 공유 라이브러리를 먼저 로드한다. 이렇게 함으로써 해당 라이브러리 심볼이 우선권을 갖게 된다. TCMalloc 이 정의된 공유라이브러리를 지정하면 실행하는 프로그램에서 malloc 이 호출되는 부분이 TCMalloc 으로 대체되는(override) 것이다.

우선 타겟 환경(도커 등)에 TCMalloc 을 설치한다.

$ apt install libtcmalloc-minimal4

그리고 타겟 환경에서 아래 명령어를 사용하여 libtcmalloc-minimal4.so.4 의 경로를 확인한다.

$ dpkg -L libtcmalloc-minimal4

그리고 타겟 환경에서 명령어를 실행할 때 LD_PRELOAD 환경변수를 지정해주면 된다.

$ LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libtcmalloc_minimal.so.4 train.py

내 경우에 메모리 누수현상은 사라졌지만 실행속도에서 큰 개선을 확인할 수는 없었다.

이 방법은 시스템 메모리 접근에 관련한 것이기 때문에 코드 자체를 tf.Function 으로 만 사용할 경우 누수 이슈가 안 나타날 수 있고 성능개선도 기대할 수 있을 것 같다. 내 경우에는 데이터셋 루프에서 시스템(cpu) 메모리로 가져와서 처리해야 편한 부분이 있어 이 방법이 현실적이고 빠른 해결책이다.

참고

Difference between BN and IN

Programming

It is well known that IN is simply 1-batch of BN. It is true in training phase, but not in inference.

In inference phase, BN uses popular mini-batch statistic for mean and standard variation, while IN uses them unchanged from training time, as noted in AdaIN paper below.

Reference: arxiv.org/abs/1703.06868

ML 프레임워크 GPU 연산 지원 확인하기

Programming

매 번 다시 검색해서 알아내는 것이라 여기에 정리해둔다.

Tensorflow (버전 2.2와 1.14에서 확인)

import tensorflow as tf
tf.test.is_gpu_available(
    cuda_only=False, min_cuda_compute_capability=None
)

PyTorch (버전 1.6에서 확인)

import torch
torch.cuda.is_available()

참고:

mkvirtualenv 실행시 ailed to find interpreter for Builtin discover of python_spec 에러

Programming

Ubuntu 18.04 를 새로 설치한 PC에 파이썬 가상환경을 만들기 위해 virtualenv와 virtualenvwrapper 패키지를 설치하였다. 그런데 가상환경을 만드려 하니 (mkvirtualenv) 아래와 같은 에러메시지가 뜨면서 생성이 되질 않았다.

RuntimeError: failed to find interpreter for Builtin discover of python_spec='python3'

'python3' 라고 나온걸 보고 python2 버전의 가상환경를 만들어 보니 이번에는 잘 생성이 되었다.

원인은 python3-dev 패키지가 설치가 되지 않아서인데, sudo apt install python3-dev 를 실행하고 가상환경을 만들면 문제 없이 실행된다.

참고: blog.cloudsys.co.kr/ubuntu-18-04-python3-venv-setup/

공용 .gitignore 를 건드리지 않고 .gitignore 설정하기

Programming

git을 사용하면서 .gitignore 를 편집해야 할 일이 생기는데, 리모트의 공용 .gitignore를 건드리기에는 부담스러운 상황이 있다. 그럴 경우 .git/info/exclude 파일을 .gitignore 와 같은 방식으로 사용하면 된다.

참고: stackoverflow.com/questions/1753070/how-do-i-configure-git-to-ignore-some-files-locally

Python 업그레이드 후 virtualenv 문제

Programming

최근에 Python 버전을 3.6에서 3.7로 업그레이드를 하였는데, Python3 버전을 사용하는 virtualenv가 꼬였는지 Python을 실행할 수 없게 되었다. 그래서 해당 virtualenv를 지우고 다시 생성해 보았는데 아래와 같은 메시지가 나오면서 생성이 되지 않았다.

$ rmvirtualenv myenv3
$ mkvirtualenv -p python3 myenv3
-bash: /usr/local/bin/virtualenv: /usr/local/opt/python/bin/python3.6: bad interpreter: No such file or directory

결국 virtualenv 자체를 지웠다가 다시 설치해야 했다.

$ sudo pip uninstall virtualenv
$ sudo pip uninstall virtualenvwrapper
$ sudo pip install virtualenv
$ sudo pip install virtualenvwrapper

참고: https://discourse.brew.sh/t/virtualenv-broken-after-python-3-7-update/2586/5

macOS 앱에서 dylib 이 로드되지 않는 문제

Programming

Xcode로 macOS 앱을 만들다가 외부 라이브러리를 사용할 일이 있어서 dylib을 링크하려 했는데, 해당 dylib을 로드할 수 없다는 런타임 에러메시지가 나왔다. 그 프로젝트는 이미 a 파일을 링크해서 쓰고 있었고, a 파일 때와 같은 방법으로 dylib을 셋팅하고 있었다.

생각해보니 dylib은 추가적으로 해 주어야 하는 일이 있었다. a 파일은 심볼이 타겟 바이너리에 복사되어 들어가기 때문에 그냥 쓰면 되는 것이었고, dylib은 so 같은것이기 때문에 타겟 머신(macOS 컴퓨터)에 dylib을 설치하거나(/usr/lib 같은데다), 아니면 번들로 앱 패키지에 심어 넣어야 하는 것이다.

설치를 하는 것은 번거로우니까 스킵하고, 번들로 심는 방법을 적어본다. Xcode의 Navigator 에서 프로젝트 파일을 누른 다음, General - Frameworks, Libraries, and Embedded Content 항목을 살펴보자. 안에 테이블에서 해당하는 dylib 파일의 Embed 속성에서 'Embed & Sign' 옵션을 선택하면 끝.

참고로 설치하는 방법은 install_name_tool 이라는 명령어를 사용한다. 다음 링크 참조: Embedding .dylib libraries in your application bundle.

참고

Places365-Challenge mean pixel value

Programming

Places365-Challenge train dataset의 평균 픽셀값을 계산해보면 (117.8, 114.4, 105.7) 이 나온다. 데이터량이 많아(476GB) 계산 과정이 꽤 오래 걸려서 여기에 기록해 둔다.

데이터셋 다운로드 페이지: http://places2.csail.mit.edu/download.html

PyTorch torch_shm_manager Runtime Error

Programming
RuntimeError: error executing torch_shm_manager at "/Users/koodev/.virtualenvs/pyto/lib/python3.6/site-packages/torch/bin/torch_shm_manager" at ../torch/lib/libshm/core.cpp:99

PyTorch 코드를 만들다가 위와 같은 런타임 에러가 났다. 원인은 현재 환경에서 해당 유틸(torch_shm_manager)에 실행 권한이 없어서였다. chmod +x 로 해당 파일의 권한을 수정해주면 해결된다.

참고: https://qiita.com/toshitanian/items/5137a68bbc9396817a3f

'Programming' 카테고리의 다른 글

macOS 앱에서 dylib 이 로드되지 않는 문제  (0) 2020.01.06
Places365-Challenge mean pixel value  (0) 2019.11.07
Tentative Symbol in C  (0) 2019.06.07
pip 설치 중 setuptools 관련 오류  (0) 2019.06.04
ARM A32 명령어셋 VZIP  (0) 2019.03.11

Tentative Symbol in C

Programming

예전에 다른 곳에 썼던 글을 그대로 가져온다. 본문에서 '요즘'은 2016년 11월 기준이다.

요즘 링커와 로더와 관련된 글을 읽고있다. 읽어본 글들 중 Ali Bahrami 라는 분의 글에 나온 tentative symbol 에 대한 내용을 정리해본다. 예제는 Ali 님의 글에서 그대로 가져왔고 내 맥북에서 실행(Apple llvm cc)해본 결과를 붙인다. tentative 심볼은 결코 좋은 코딩 스킬은 아니지만(goto 같은 존재?) C언어나 링커의 내부 동작을 참고하는데 도움이 되는 내용인 것 같아 정리해둔다.

“Tentative” 를 네이버 영어사전에서 찾아보면 “처리, 합의 등이 잠정적인” 이런 뜻의 형용사로 나와 있다. 즉, tentative 심볼은 (뭔가 부족한) 임시 심볼이라고 할 수 있다.

ELF 심볼 테이블의 엔트리에는 심볼 타입에 대한 속성이 있다. 가능한 속성값들 중 STT_OBJECT 는 일반 데이터에 대한 정의를 나타내고, STT_COMMON 은 tentative 데이터에 대한 정의를 나타낸다. 대부분의 변수 심볼은 STT_OBJECT 로 잡힐 것인데, 그럼 STT_COMMON 언제 잡히며, 왜 COMMON 하지도 않으면서 COMMON 이란게 붙었을까?

tentative 심볼은 글로벌 변수인데 크기나 초기값을 알 수 없는 변수를 추적할 때 쓰인다. 이런 심볼은 해당 오브젝트의 컴파일 시점에서는 저장될 위치가 결정되어 있지를 않다. 이런 심볼을 “common block” 이라고도 부른다. 이렇게 부르는 이유는 포트란의 COMMON block 에서 유래되었기 때문이다.

아래 C코드의 두 선언문을 살펴보자.

int foo;
int foo = 0;

링커와 로더나 Embedded Recipe 같은 책에 의하면 초기화되지 않은 변수는 BSS 영역으로 잡힌다. 따라서 둘 다 모두 초기값이 0인 foo라는 정수형 변수의 선언문일 것이다… 라고 생각하면 안된다! 초기화되지 않은 변수가 BSS로 할당되는 것은 맞지만 첫 번째 선언문만 봐서는 foo가 초기화 되었는지 안되었는지 알 수 없다. 즉, 다른 파일에서 초기화를 해버렸을 수도 있는데, 이 파일만 봐서는 그걸 알 수가 없는 것이다. 따라서 첫 번째 선언문의 foo값은 어떤 파일이 링크되느냐에 따라 달라지게 된다.

아래의 예제 t1.c 와 t2.c 를 살펴보자.

#include <stdio.h>

#ifdef TENTATIVE_FOO
int foo;
#else
int foo = 0;
#endif

int main(int argc, char *argv[])
{
  printf("FOO: %d\n", foo);
  return 0;
}
int foo = 12;

처음에는 t1.c 만 빌드해보자. TENTATIVE 매크로도 확인해보자.

$ cc -DTENTATIVE_FOO t1.c
$ ./a.out
FOO: 0
$ cc t1.c
$ ./a.out
FOO: 0

매크로를 주던 안주던 간에 foo 변수값은 0으로 확인된다. 자 이제 t2.c 를 링크해보자. 이번엔 결과가 좀 다르다.

$ cc -DTENTATIVE_FOO t1.c t2.c
$ ./a.out
FOO: 12
$ cc t1.c t2.c
duplicate symbol _foo in:
    /var/folders/0l/1xtwrc4j2978f6rhsk7jq4y80000gn/T/t1-1ba0e4.o
    /var/folders/0l/1xtwrc4j2978f6rhsk7jq4y80000gn/T/t2-3061bc.o
ld: 1 duplicate symbol for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

매크로를 줬을 경우, 즉, foo를 선언만 해놓고 초기화를 하지 않았을 경우 0으로 초기화가 되는 것이 아니라 t2.c 에서 초기화한 값을 그대로 가져온다. 그리고 매크로를 주지 않았을 경우, 링커(ld)에서 두 개의 foo 에 대한 non-tentative 하며 다른 값을 가진 정의에 대해 에러를 뱉어낸다.

C 문법에서는 명시적인 값을 지정하지 않은 변수의 초기값은 0이 할당된다고 되어 있다. 그러나 똑같은 이름의 글로벌 변수가 존재할경우 이것을 바꿀 수 있다. C 컴파일러는 컴파일중에는 파일 하나만 보기 때문에 이런 내용에 대해서 알 수가 없다. 따라서 컴파일러는 초기화되지 않은 변수를 만나게 되면 해당 심볼의 타입을 STT_COMMON 으로 해 두고 나중에 링커가 리졸브(resolve)하도록 내버려둔다. 링커는 파일 하나만 보는 것이 아니기 때문에 이런 심볼 리졸브가 가능하다. 그렇지만 링커가 프로그래머의 의도까지 잘 파악하는 것은 아니기 때문에 이렇게 초기화되지 않은 변수를 쓸 경우 동작이야 잘 하겠지만 그게 의도에 부합하는 올바른 동작일지는 명확하지 않을 것이다.

매크로를 주지 않았을 경우, 즉 변수 선언과 초기화를 했을 경우에는 t1과 t2 오브젝트 둘다 non-tentative 한 심볼이 생성된다(STT_OBJECT). 이 경우 링커에 따라 다른데 똑같이 에러를 내뱉는 경우도 있고(Apple llvm ld), Ali의 글에서는(Solaris ld) 선언문이 일치하는지를 검사한다고 한다. 둘 다 tentative 인 경우는 어떨까? 맥북에서는 이 경우 그냥 넘어갔다(0으로 초기화됨). 하지만 링커가 어떻든 간에 초기화를 하는 것이 견고하고 호환성좋은 코드인 것이다.

로컬 스코프에서 tentative symbol은 의미가 없다. 즉, 아래와 같이 static을 쓸 경우 t1.c에서의 참조는 로컬 스코프의 정의를 따라가기 때문에 foo는 tentaive 심볼이 아니다.

#ifdef TENTATIVE_FOO
int foo;
#else
static int foo = 0;
#endif

tentative 심볼을 쓰는것은 결코 좋은 코딩 스킬은 아니다. 어떤 파일에서의 변수선언이 다른 파일의 것을 건드릴 수 있기 때문이다. tentative 심볼은 포트란에서 나왔다. 포트란에서는 common block 이란 것을 사용하여 C의 union 같은 것을 만들 수 있다고 한다. 즉, 컴파일 타임에 지정된 데이터 타입이 아니라 그때그때 크기와 타입같은것들을 바꾸어 쓰기 위해 common block 이란 것을 사용했다.

글로벌 변수를 사용하는 것이 아주 좋은 디자인은 아니지만 가끔 글로벌이 필요할 때가 있다. 이 경우 선언과 초기화를 하나의 파일(오브젝트)에서만 해야 하고 이를 참조하는 레퍼런스는 초기화를 하면 안된다. C에서는 extern 이란 키워드를 지원하여, ‘이 심볼은 참조하는 것이다’ 라고 컴파일러에게 알려준다. Apple llvm cc는 extern 을 사용하고 초기화를 하면 워닝(warning)을 내뿜는다.

tentative 심볼은 extern이 붙은 심볼과는 또 다른데, extern의 경우 외부의 정의를 참조한다는 것을 명시하는 것인 반면, tentative 변수는 경우에 따라서 자기 자신이 0으로 초기화될 수 있다. 따라서 extern 글로벌 변수를 선언하고 아무데도 초기화를 안 해 놓으면 링커가 에러를 내뿜는다.

References

'Programming' 카테고리의 다른 글

Places365-Challenge mean pixel value  (0) 2019.11.07
PyTorch torch_shm_manager Runtime Error  (0) 2019.10.23
pip 설치 중 setuptools 관련 오류  (0) 2019.06.04
ARM A32 명령어셋 VZIP  (0) 2019.03.11
ARM A64 명령어셋 ZIP1, ZIP2  (0) 2019.03.11