오픈 소스 소프트웨어의 특성 때문에 많은 리눅스 응용 프로그램은 응용 프로그램을 수행하기 전에 빌드를 거쳐야 하는 원시 코드를 포함한 "타볼(tarball)"로 배포된다. 대규모 응용이라면 빌드에만 여러 시간이 걸린다. 이 기사에서는 분산 C 컴파일러인 distcc를 사용해 이런 대규모 원시 코드 컴파일 속력을 올려 좀 더 빨리 사용하는 방법을 보여준다.
몇몇 리눅스 응용은 RPM(Red Hat Package Manager) 파일로 배포된다. 이런 파일은 일반적으로 최종 사용자가 응용을 빨리 구해 사용하는 데 도움이 된다. 오픈 소스 소프트웨어 세상에서는 종종 사용자가 빌드해야 하는 원시 코드를 포함한 .tar.gz 옵션("tarball")이 있다. RPM 사촌과는 달리 타볼 기법에는 설정 과정이 필요하지만 다음과 같은 몇 가지 장점이 있다.
- 응용은 일반적으로 /usr/local/에 설치되므로, 기계에 설치된 파일을 쉽게 제거할 수 있는 동시에 기존 응용 프로그램도 보존할 수 있다.
- 코드를 필요에 맞춰 상세 조율할 수 있다.
- 다양한 최적화가 가능하다.
마지막 장점이 가장 마음에 든다. 고급 사용자로서, 컴파일 시점에서 애슬론 XP 프로세서를 최대로 활용하라고 프로그램에 지시하고 싶다. 여기서 주의 사항이 하나 있다. 최적화를 켜면 빌드 시간이 늘어난다. 컴파일러는 루프를 따라가고 상수를 제거하는 등 똘똘하게 작업하려고 시도한다. 최종 결과로 빌드 시간을 희생해 엄청나게 빠른 코드가 나온다.
OpenSSH라는 전형적인 응용을 하나 살펴보자. 웹 사이트에서 openssh-3.7p1.tar.gz를 막 내려받았으며(링크는 참고자료 참조), 테스트 응용으로 이를 활용하겠다.
먼저 타볼을 풀어보자.
me@mymachine:~> tar xvzf openssh-3.7p1.tar.gz
여기서 x는 tar 파일을 풀며, v는 추가 정보를 출력하며, z는 tar 명령어가 파일 압축을 풀기 위해 gunzip을 사용하며, f는 풀려는 tar 파일을 지정한다는 뜻이다. .tar.bz2 파일을 푼다면 z 대신 j를 붙여 gunzip이 아니라 bunzip을 사용하도록 지시한다. tar 자체에 대한 정보와 추가 옵션 정보가 필요하면 man tar 명령을 내려 나오는 결과를 살펴보자.
새롭게 만든 openssh 디렉터리로 들어가보자.
me@mymachine:~> cd openssh-3.7p1
원시 파일을 빌드하는 데 사용하는 gcc에 몇 가지 컴파일러 옵션을 명세해야 한다. 여기서 기계 특성을 최대로 살리도록 지시한다. bash를 사용하므로 export 명령을 내린다(tcsh 계열에서는 setenv 명령어를 사용한다).
me@mymachine:~/openssh-3.7p1> export CFLAGS="-O3 -march=athlon-xp \
-funroll-loops -fexpensive-optimizations"
me@mymachine:~/openssh-3.7p1> export CXXFLAGS=$CFLAGS
-march 플래그에 주목하자. 내가 사용하는 워크스테이션은 AMD 애슬론 XP 프로세서이므로, gcc 3.x에서 -march=athlon-xp 스위치만 붙이면 자동으로 SSE 같은 프로세스에서 지원하는 최적화 기능을 켠다. 또한 -march=pentium4나 -march=pentiumpro를 사용할 수도 있다. 사용 가능한 최적화 목록과 설명은 gcc 매뉴얼 페이지를 참조한다.
컴파일러 옵션 추가가 끝났다. 이런 옵션에 익숙하지 않다면, ~/.bashrc 파일 내부에 export 코드를 놓아두고 항상 기본값으로 사용하면 편할 것이다.
다음으로 SSH 빌드에 포함할 옵션을 줘서 기계에 최적화된 빌드용 환경 설정 프로그램을 돌릴 차례다. 다음과 같은 방법으로 옵션을 살펴보자.
me@mymachine:~/openssh-3.7p1> ./configure --help
원하는 옵션 전부나 몇 가지를 포함하면 되지만 지금은 기본값으로 충분히 만족한다. 따라서 configure 명령만 내린다.
me@mymachine:~/openssh-3.7p1> ./configure
이제 원시 코드 빌드를 위한 준비가 끝났다. make 명령으로 빌드를 시작하자.
me@mymachine:~/openssh-3.7p1> make
빌드에 시간이 걸리므로 커피 한 잔 마실 시간은 충분하다. 일단 빌드가 끝나면 필요한 OpenSSH 패키지 준비도 끝난다. 결과로 컴파일러에 요청한 최적화 기법으로 OpenSSH 이진 파일이 생성되었다. 빌드에 걸리는 시간을 측정해보니 2분 25초였으며, 커피 한 잔 마시기에 충분한 시간이다.
하지만 이런 시간이 너무 아깝다. 내 컴퓨너는 다른 작업을 할 수도 있었던 2분 25초 동안 바쁘게 돌아갔다. 2분이 그리 긴 시간이 아닐지도 모르겠지만 OpenSSH가 아주 작은 응용이라는 사실을 고려하자. 훨씬 더 큰 프로그램을 빌드하거나 하루에 직접 개발한 코드를 수십번 컴파일한다면 빌드하느라 하루에 여러 시간을 들여야 한다. 나는 바쁜 사람이므로 이런 시간도 아깝기에 distcc를 사용하겠다.
distcc는 자그마한 응용 프로그램으로 gcc 컴파일러에 훅을 걸어 distcc가 설치된 다른 기계에서도 컴파일을 진행하도록 만든다. 첫 단계는 distcc를 워크스테이션에 설치하는 작업이므로, 웹 사이트에서 최신 버전을 가져온다(링크는 참고자료 참조).
수세 리눅스를 사용한다면, 수세에서 패키지를 얻거나 설치 매체를 사용한다. 젠투 리눅스를 사용한다면 emerge distcc 명령을 내린다. 데비안을 사용한다면 apt-get install distcc 명령을 내린다. 필요하다면 FreeBSD 포트도 있다.
(나처럼 타볼을 좋아하는 개발자라면) .tar.gz 파일을 구해서 다음과 같이 빌드한다.
me@mymachine:~/distcc-2.12.1> ./configure --with-gtk
me@mymachine:~/distcc-2.12.1> make
그러고 나서 시스템 관리자 권한으로 설치한다.
me@mymachine:~/distcc-2.12.1> sudo make install
이렇게 하면 필요한 모든 파일 설치가 끝나고 distcc 데몬(distccd)은 시스템 시동 과정에서 알아서 자동으로 동작하도록 /etc/init.d/distccd에 들어간다. rc.d 디렉터리에 적절한 링크가 생기지 않으면 직접 링크를 걸자.
컴퓨터 재시동 대신에(결국 우리는 1초도 아까운 사람들이다), 지금 당장 수동으로 데몬을 띄우자.
me@mymachine:~/distcc-2.12.1> sudo /etc/init.d/distccd start
시스템 관리자 권한이 없을 경우에도 distcc 데몬을 실행하는 작업이 가능함을 알아두자. distcc 데몬은 시작하는 기계에 무관하게 사용자 이름으로 동작한다.
distcc를 기계 한 곳에만 설치해서는 의미가 없다. 아무런 이익도 주지 않기 때문이다. LAN에 물린 리눅스 기계를 운영하는 친구 세 명에게 부탁해 흥미가 있는지 물어본다. distcc를 설치한 모든 사람이 "풀"에서 이익을 얻기 때문이다.
동작하는 gcc 버전 이외에는 기계와 관련해 나머지 공통 요구 사항이 없다는 사실에 주목하자. 시스템 파일, 헤더 파일, 라이브러리, 심지어 리눅스 커널이나 배포판까지도 똑같을 필요가 없다.
이런 작업이 끝나면, distcc에 사용이 가능한 기계 목록을 알려줄 필요가 있다. 세 기계를 각각 "flim," "flam," "jabberwocky"라고 하자. 다시 export 명령을 사용해서 이번에는 DISTCC_HOSTS라는 환경 변수를 설정하자(역시 ~/.bashrc에 기록해 영구적으로 사용할 수 있게 만들자).
me@mymachine:~> export DISTCC_HOSTS="mymachine flim flam jabberwocky"
하지만 내 기계는 flim이나 jabberwocky만큼 빠르지 않으므로, 목록에서 flim과 jabberwocky를 위로 올린다. distccd는 처음 만나는 항목부터 먼저 작업을 진행하는 듯이 보인다.
me@mymachine:~> export DISTCC_HOSTS="flim jabberwocky mymachine flam"
이제 설정이 끝났다. 다시 한번 OpenSSH 빌드를 진행해 기계 한 대보다 세 대로 돌리면 얼마나 빠른지 살펴보자.
me@mymachine:~> cd openssh-3.7p1
CFLAGS, CXXFLAGS, DISTCC_HOSTS 환경 설정 변수를 이미 export 했으므로, 그냥 수행하면 된다. 자동으로 설정하도록 ~/.bashrc에 기록하지 않았다면 설정값을 기억해야 한다.
직전 빌드를 정리하고 새로운 도화지에서 작업을 시작하자.
me@mymachine:~/openssh-3.7p1> make clean
시작하기 전에 한 가지 추가 작업이 필요하다. distcc 프로그램은 모니터가 따라오므로 어떤 기계에서 어떤 원시 파일을 컴파일하는지 확인이 가능하다. distcc를 빌드했을 때 --use-gtk 옵션을 사용했기에, distccmon-text와 distccmon-gnome이라는 두 가지 선택이 가능하다. 지금은 콘솔 버전을 사용하자. 새로운 터미널 세션을 연 다음에 다음 명령을 내리자.
me@mymachine:~/openssh-3.7p1> distccmon-text 2
이렇게 하면 2초마다 갱신한다. (내가 선호하는) 다른 방법도 있다.
me@mymachine:~/openssh-3.7p1> watch distccmon-text
둘 다 똑같은 결과를 보여준다. 2초마다 분산 컴파일 상태를 스냅샷으로 보여준다. 이제 준비가 끝났으므로 configure 명령을 내리자. configure에 옵션을 줘서 (내 리눅스 시스템에서 다른 지시를 내리지 않을 경우 기본값인) 일반적인 gcc를 사용하지 않도록 알려주자.
me@mymachine:~/openssh-3.7p1> CC=distcc ./configure
distcc 모니터는 다소 불규칙하게 움직인다. 환경 설정 중에 한두 부분에서 일어나는 자그마한 활동이 다른 기계에서 진행되기 때문이다. configure가 끝나고 나면 실제 컴파일 작업 준비가 다 되었다.
me@mymachine:~/openssh-3.7p1> make -j 12
여기서 -j 옵션을 make에 넘겼다. -j 플래그는 distcc에만 밀접한 옵션이 아니라 동시에 얼마나 많은 컴파일 작업을 수행할지 gcc에 알려준다. distcc를 수행하지 않는 기계에서도 make -j 명령을 내려도 무방하다. 단일 CPU에 -j를 2로 설정하면 속력이 올라가기도 한다(크게 올라가지는 않는다). 하지만 12를 지정하면 가능한 12개 원시 파일을 한번에 빌드하라고 지시한다.
distcc 모니터를 들여다봐서 어떤 일이 일어나는지 확인하자.
Listing 1. distcc 명령행 모니터
5366 Preprocess serve.c flim[0] 5338 Compile minilzo.c flim[1] 5363 Preprocess prefork.c flim[2] 5360 Compile ncpus.c jabberwocky[0] 5352 Compile dparent.c jabberwocky[1] 5356 Compile dsignal.c jabberwocky[2] 5349 Compile dopt.c mymachine[0] 5279 Compile trace.c mymachine[1] 5375 Preprocess srvnet.c mymachine[2] 5342 Compile access.c flam[0] 5346 Compile daemon.c flam[1] 5371 Preprocess setuid.c flam[2] |
distcc 모니터를 사용해서 어떤 노드에서 어떤 파일이 컴파일되고 있는지 확인이 가능하다. 오른편 노드 이름 뒤에 나오는 숫자는 n 번째 동시 컴파일이 진행중임을 나타낸다. 여기서 네 노드에 -j로 작업 12개를 지정했으므로, 기계마다 파일 셋을 컴파일한다. 이렇게 할 경우 상당히 의미가 있는 이유는 필요한 파일을 뒤섞기 위한 네트워크 부하 때문이다. 여기서 노드당 컴파일 하나만 진행한다면(다시 말해, -j 4), CPU가 쉬는 시간이 상당히 늘어난다.
이렇게 기계를 설정해놓은 환경에서 시간을 측정하면 컴파일 과정이 9초로 짧아진다. 대략 열여섯 배 속력 개선이 있었다. distcc로 컴파일하면 자기 노드보다 훨씬 더 빠른 다른 노드를 활용해 개인 워크스테이션에 맞춰 최적화된 응용을 만들어낼 수 있다.
다양한 -j 값에 따른 효과를 살펴보기 위해 다음과 같이 값을 바꿔가며 빌드해보자.
Listing 2. 동시 컴파일 숫자가 빌드 시간에 미치는 영향
-j 값 빌드 시간(초)
4 19.5
8 10.5
12 8.9
16 8.5
20 8.6
|
-j 값을 변경하면 이득을 얻는다는 사실을 확인했다. 설정마다 결과가 다르게 나오므로 실험할 만한 가치가 있다. 여기서 distcc 클러스터가 수십 대 이상이면 지역 기계를 목록에서 제외하는 편이 바람직하다는 사실에 주목하자. 지역 기계는 다양한 기계에 원시 파일을 배분하고 만들어진 목적 파일을 받아 처리해야 하므로 컴파일 작업까지 맡기에는 너무 바쁘다. 따라서 목록에 넣은 상태로 남겨두면 빌드 과정을 느리게 만들지도 모른다.
버전과 관련해 마지막으로 주의해야 할 사항을 짚고 넘어간다. distcc 프로그램은 distcc 클러스터에 속한 모든 기계에 부 버전 번호까지 똑같은 gcc를 유지할 때 가장 잘 동작한다. 부 버전이 다르면 불안한 빌드 결과를 낳거나 빌드 과정이 완전히 멈춰버린다. gcc 일부만 바뀌어도 이런 일이 충분히 일어날 가능성이 있다. 예를 들어, 상기 예에서 mymachine, flim, jabberwocky는 gcc 3.3.1을 돌리지만 flam은 gcc 3.2.2를 돌리면 OpenSSH 빌드는 성공할지도 모르며, 특정 기계에서 빌드하는 부분에 따라 실패할지도 모른다. 이런 상황에서 심지어 빌드를 성공적으로 끝냈을지라도 이런 구성 환경이 예측대로 동작하리라 기대하면 곤란하다.
똑같은 부 버전을 유지하는 정책은(예를 들어 gcc 3.3.4와 gcc 3.3.1은 둘 다 gcc 3.3.x이므로 서로 섞어써도 무방할 것이다) 멋지고 안정적인 빌드를 가능하게 만들기에 문제가 생기더라도 distcc 관련 문제가 아닐 가능성이 높아진다.
- 컴파일 작업을 원하는 모든 기계에 distcc를 설치하자.
- 기계마다 distcc 데몬을 시작하자.
- DISTCC_HOSTS 환경 설정 변수에 모든 기계 이름을 붙여 export 하자.
- distcc 모니터를 시작하자(어떤 작업이 일어나는지 확인한다!).
./configure대신에CC=distcc ./configure로 configure 작업을 진행하자.make나make -j 2로make하는 대신에make -j n으로 make하자. n은 DISTCC_HOSTS에 열거한 기계 숫자에 2나 3을 곱한다.
최적화로 이익을 얻는 프로그램이 있다면, 원시 코드에서 이진 파일을 직접 "빚어내는" 편이 유리하다. 대규모 빌드를 진행할 때 시간 관점에서 상대적으로 손해를 볼지는 모르겠지만, distcc를 사용하면 distcc 클러스터에 속한 모든 사람이 네트워크 상에서 노는 CPU 사이클을 활용해 최대로 빨리 응용 프로그램을 수행할 수 있다.
- 삼바 홈페이지에서 distcc 타볼을 내려받고, OpenSSH 사이트에서 OpenSSH를 내려받자.
- distcc는 GNU C 컴파일러(gcc)와 함께 동작한다.
- Laurence는 RPM 대신에 tar 파일에서 원시 코드를 풀어 빌드하는 방법을 선호한다. 원시 코드에서 프로그램을 빌드하는 방법은 A beginner's guide to compiling programs under Linux(Linux Users of Victoria)에 잘 나와 있다. 이 지침서는 Kim Oldfield가 쓴 실전 지침서로 전에 리눅스에서 한번도 컴파일해보지 않은 사람을 위한 내용을 담고 있다. JC Pollman이 쓴 Compiling Programs on Linux(Linux Gazette, 1999년)도 위에서 소개한 지침서에 있는 내용에 몇 가지 문제 해결 힌트를 제공한다(또한 매뉴얼 페이지 숙독을 잊어버리지 말자!).
- Daniel Robbins가 쓴 IBM developerWorks 튜토리얼인 "Compiling and installing software from sources"(developerWorks, 2000년)도 읽어보자.
- 최적화 플래그로 컴파일하면 이진 파일을 좀 더 빨리 동작하도록 만든다. 추가 자료로 Paul Hsieh가 쓴 Programming Optimization(a zillion monkeys, 2002년)을 비롯하여 Safe flags to use for gentoo-1.4와 Experimental flags to use for gentoo-1.4(freehackers.org, 2002년)를 읽어본다.
- Benjamin Meyer가 쓴 distcc 최적화 기사는 작은 컴파일러 팜을 만드는 데 distcc를 활용하는 방법을 기술한다. Benjamin은 ccache와 unsermake와 연동해 distcc를 사용하도록 권장한다.
- 리눅스 개발자를 위한 참고자료는 developerWorks 리눅스 영역을 참조하기 바란다.
- 온라인 서점: 다양한 기술 주제와 관련된 책을 찾아보자.
- developerWorks Subscription에서 내려받은 최신 IBM 도구와 미들웨어를 활용해 리눅스 응용 프로그램을 개발해 테스트해보자. WebSphere, DB2, Lotus, Rational, Tivoli를 포함하며, 12개월 소프트웨어 라이선스를 생각보다 적은 돈으로 구입할 수 있다.
- 리눅스에서 동작하는 선택된 developerWorks Subscription 제품 평가판을 무료로 내려받자. developerWorks의 Speed-start your Linux app에서 WebSphere Studio Site Developer, WebSphere SDK for Web services, WebSphere Application Server, DB2 Universal Database Personal Developers Edition, Tivoli Access Manager, Lotus Domino Server를 찾아보자. 또한 제품별 모음과 하우투 기사와 기술 지원을 제공한다.