제공 : 한빛 네트워크
저자 : Swayam Prakasha
역자 : 주재경
원문 : Optimizing Linux System Performance
리눅스에서 성능 최적화는 우리가 생각할 수 있는 것을 항상 의미하는 것은 아니다. 속도만이 중요한 것이 아니다. 때때로 작은 용량의 메모리에 시스템을 최적화 시키기 위한 시스템 튜닝이기도 하다. 프로그래머라면 당연히 프로그램이 플랫폼에 상관없이 좀더 빠르게 동작하기를 원한다. 리눅스 프로그래머도 예외는 아니다. 어떤 이들은 성능향상을 위해 그들의 코드를 최적화 하는데 거의 광적으로 집착한다. 하드웨어가 더욱 빨라지고 값이 저렴해지고 풍부해짐에 따라 어떤 이는 성능 최적화는 그리 중요하지 않은 문제라고 말한다. 특히 소프트웨어 개발 완료 시기를 중요하게 생각하는 이들이 그렇다. 그렇지 않은 것이 심지어 가장 최근의 컴파일러 최적화 기술로 이루어진 가장 앞선 하드웨어도 작은 프로그램을 수정하여 혹은 좀더 전체적으로 다르고 좀더 빠른 디자인을 통해 얻을 수 있는 성능 향상에 가까이 갈 수 없다.
여러 가지 아이디어가 프로그램의 성능 향상을 위해 적용될 수 있다. 코드를 작성할 때 이를 염두에 두는 것으로 좀더 나은 그리고 좀더 빠른 프로그램을 기대할 수 있다. 성능에 관해 얘기할 때 여러 다른 것들을 염두에 둬야 한다. 이중 하나가 소프트웨어가 주어진 일을 완료하는데 걸리는 시간이다. 한가지 예를 들면 웹 서버가 클라이언트 요구를 잘 처리한 경우에도 서버가 클라이언트에게 매번 페이지 송신을 시작하기 전에 몇 초의 지연이 있을 수 있다. 이러한 경우 웹 서버가 작업을 마치는데 필요한 총 시간 면에서 적절하게 작업을 실행하는데 실패하고 있다.
또 다르게 생각해야 할 것이 프로그램이 필요한 총CPU 타임이다. CPU 타임은 컴퓨터의 프로세서가 코드를 실행하는데 사용하는 시간이다. 많은 프로그램이 입력 되거나 디스크에 쓸 출력등과 같이 무엇인가 발생하기를 기다리는데 대부분의 시간을 소비하는 경향이 있다. 기다리는 동안 CPU는 다른 요구를 처리할 것이고 그러므로 프로그램은 CPU타임을 사용하지 않는다. 그러나 어떤 프로그램은 CPU와 밀접한 관련성을 가진 프로그램일 수 있으며 그러한 프로그램에서 필요한 CPU타임 양을 줄이는 것은 전체 시간에서 결과적으로 상당한 양을 줄이는 것일 수 있다.
당신의 프로그램이 CPU 타임을 많이 사용하는 경우 시스템상의 모든 프로세스가 느려 질 수 있음에 유의하라. CPU타임은 좀더 세분하여 시스템과 유저 시간으로 구분될 수 있다. 시스템 타임은 커널이 사용하는 CPU타임이다. 이는 open()이나 fork()같은 함수를 호출하여 발생한다. 유저 타임 (즉 프로그램이 사용하는 CPU 타임)은 문자열 조작이나 계산에 사용되는 시간일 수 있다. 성능을 위해 고려해야 할 세 번째는 I/O동안 소비되는 시간이다. 네트워크 서버와 같은 프로그램을 생각해 보면 이 프로그램은 대부분의 시간을 I/O를 제어하는데 사용한다. 다른 프로그램은 I/O관련 작업에 시간을 거의 소모하지 않는다. 그러므로 I/O 최적화는 어떤 프로젝트에서는 아주 중요할 수 있고 어떤 프로젝트에서는 중요하지 않을 수 있다.
기본적으로 성능 최적화는 아래의 단계로 구성된다.
- 성능 문제를 정의한다.
- 병목 지점을 찾아내고 가장 근본적인 원인 분석을 한다.
- 적절한 방법으로 병목을 제거한다.
- 만족할 만한 해법을 얻을 때까지 2와 3을 반복한다.
병목 지점은 시스템의 다양한 지점에서 발생함에 유의하는 것이 중요하다. 병목 지점을 결정하는 것은 근본 원인의 폭을 좁혀 나가는 단계적인 절차이다. 성능 최적화 작업은 성능 문제를 분석하는 소스코드로 된 많은 유형의 정보의 상관관계가 필요한 상대적으로 복잡한 과정이다.
성능 최적화에 초점을 맞추면 시스템 관리자는 병목 지점을 찾기 위해서 뿐만 아니라 상황을 모니터링하기 위한 특정 도구가 필요하다. 리눅스에서 이를 위한 다양한 툴이 있다. Top은 좋고 작은 프로그램이며 시스템 레벨의 실시간 정보를 제공한다. top은 상호 대화적인 툴이며 실행되는 순간의 시스템의 스냅 샷을 제공한다. mtop은 MySQL을 모니터링 하는 top과 같은 유형의 유틸이다. Slow 쿼리를 보여주거나 SQL이 실행된 이 순간의 동작을 잠근다. sysstat, procfs, sysctl 그리고 sysfs는 리눅스 상에서 유용한 도구이며 리눅스 성능 측정과 튜닝을 위해 사용될 수 있는 설정 명령이다. 또 다른 툴 sar는 CPU활용율, 메모리 사용량, 네트워크와 버퍼 사용량 등 시스템 동작에 관한 정보를 모으는데 사용될 수 있으며 잠재적인 병목 구간을 찾는데 사용될 수 있다.
루프에서 발생하는 문제
먼저 루프가 야기하는 성능 문제를 살펴보자. 루프는 작은 성능 문제를 결과적으로 확대한다. 이것은 루프 내에 있는 코드가 여러 번 실행 되기 때문이다. 루프에서 매번 마다 실행될 필요가 없는 코드는 루프 외부로 코드를 옮겨야 한다.
아래 코드를 살펴보자.
main()
{
int five;
int cnt;
int end;
int temp;
for (cnt=0; cnt < 2* 100000 * 7/ 15 + 6723;
cnt += (5-2)/2
{
temp = cnt / 16430;
end = cnt;
five = 5;
}
printf("printing values of five=%d and end = %dn", five, end);
}
코드를 주의 깊게 살펴보면 여러 부분을 루프 외부로 옮길 수 있음을 알 수 있다. 루프 전체를 통해end값은 한번만 계산된다. 변수 five에 대한 할당은 죽은 코드이며 이를 루프 외부로 옮겨보면 이를 더욱 잘 이해할 수 있다.
성능 최적화에 관해 논할 때 반드시 사용될 필요가 없는 한 float, double과 같은 데이터 타입을 사용하지 않아야 함에 유의해야 한다. 이 데이터 타입은 정수형에 비해 연산에 시간이 더 걸리기 때문이다. 또한 아주 자주 호출되는 함수가 있다면 이 함수는 inline으로 선언되는 것이 좋다.
성능을 향상 시키는 또 다른 방법은 블록 크기를 증가 시키는 것이다. 우리가 알고 있는 바와 같이 많은 연산이 데이터 블록으로 이루어 진다. 블록 크기를 증가 시키면 한번에 좀더 많은 데이터를 전송할 수 있다. 이것은 좀더 시간이 많이 소모되는 호출 횟수를 감소 시킨다.
비용이 큰 호출 다루기
코드 최적화에 관심이 있다면 비용이 싼 호출로 비용이 큰 동작을 (시간이 많이 소모되는) 제거하기를 원한다는 것은 분명한 사실이다. 일반적으로 시스템 호출은 비용이 큰 동작이다. 비용이 큰 호출 몇 가지를 살펴보자.
- fork: fork 시스템 호출은 아주 유용하다. 느리진 않지만 자주 사용한다면 시간이 추가된다. 클라이언트의 새로운 요구마다 fork를 실행하는 web server를 생각해 보자. 이것은 그리 좋은 실례가 아니며 멀티플럭싱을 위해 select()를 사용할 수 있다.
- exec: 이것은 fork후 바로 사용될 수 있다. 이 호출은 새로운 프로그램이 라이브러리 적재와 같은 아주 긴 초기화 과정을 가져야 할 때 비용이 크다.
- system: 특정 명령어 실행을 위해 shell을 호출하며 shell호출은 아주 비용이 큰 호출이다. 그러므로 system을 자주 사용하는 것은 그리 좋은 생각이 아니다.
system(“ls /etc”)과 같은 코드를 살펴보면 이 코드가 얼마나 비용이 큰 코드인지 알 수 있다. 프로그램은 먼저 fork를 실행해야 하고 그 다음으로 shell을 실행해야 한다. shell의 초기화 과정 후 ls를 fork하고 실행한다. 확실히 원하는 코드가 아니다.
속도와 안정성 모두를 얻기 위한 최적화의 첫 단계는 필요한 device driver의 최신 버전을 조사하는 것이다. 병목 구간과 이를 어떻게 다룰지 이해하는 것도 또 다르게 중요한 사항이다. top과 같은 시스템 모니터링 명령어를 실행해서 다양한 병목지점에 관한 정보를 알 수 있다.
디스크 접근 최적화
최적화에서 디스크 접근은 항상 고려할 만 하다. 디스크 성능을 확연하게 향상 시키는 다양한 방법이 있다.
hdparm명령어를 살펴 보면 IDE디스크에 관한 많은 플래그와 모드를 설정할 수 있다는 것을 알 수 있다. 살펴볼 필요가 있는 옵션은 32bit I/O를 지원하는 –c 옵션과 using_dma 플래그를 enable/disable할 수 있는 –d옵션이다. 대부분의 경우에 이 플래그는 1로 설정되며 만약 그렇지 않은 경우라면 성능이 나빠질 수 있다. /etc/rc.d/rc.local파일의 끝 부분에 다음과 같은 명령어를 추가하자.
hdparm –d 1 /dev/hda
비슷하게 32bit I/O를 지원하기 위해서는 /etc/rc.d/rc.local파일의 끝 부분에 다음 과 같이 추가하자.
hdparm –c 1 /dev/hda
GNU 프로파일러(gprof)
코드 최적화가 충분히 이루어 지면 컴파일러 또한 최적화에 도움을 준다. 프로그램 실행을 분석하는데 사용할 수 있는 툴로 GNU 프로파일러가 있다(gprof). 이것으로 프로그램 어디에서 시간이 가장 많이 소모 되는지를 알 수 있다. 프로파일 정보로 프로그램의 어느 부분이 우리가 생각했던 것보다 더 느린지를 결정할 수 있다. 이 부분은 좀더 빠른 실행을 위해 다시 작성해야 할 부분으로 결정하는데 충분히 좋은 지점이 될 수 있다. 프로파일러는 프로그램이 실행되는 동안 데이터를 수집한다. 프로파일링은 소스코드를 이해하는 또 다른 방법으로 생각할 수 있다.
gprof를 사용하는 프로그램을 프로파일링하기 위해선 다음과 같은 것이 필요하다.
- 프로그램을 컴파일 및 링크하는 과정에서 프로파일링이 가능하도록 설정되어 있어야 한다.
- 프로그램이 실행되면 프로파일링 데이터 파일이 생성된다.
- 프로파일링 데이터를 분석한다.
gprof를 사용하기 위해선 프로그램이 시스템에 설치되어 있어야 한다. gprof로 프로그램을 분석하기 위해 특별한 옵션으로 프로그램을 컴파일 할 필요가 있다. Sample_2007.c라는 프로그램을 가지고 있다고 가정하면 컴파일은 다음과 같이 이루어진다.
$ gcc –a –p –pg –o sample_2007 sample_2007.c
-pg옵션은 gcc가 지원하는 기본적인 프로파일링을 지원을 가능하도록 한다. 프로그램은 프로파일링이 설정되지 않았을 때 보다 더 느리게 동작할 것이다. 이것은 프로파일링을 위한 데이터를 모으는데 시간이 소모되기 때문에 일어난다. 컴파일이 되면 gmon.out이라는 파일이 현재 디렉토리에 생성된다. 이 파일은 후에 코드 분석을 위해 gprof가 사용한다.
결과를 얻기 위해 아래의 명령어를 실행할 수 있다.
$ gprof sample_2007 gmon.out > output.txt
gprof는 다양한 루틴에서 시간이 얼마나 소모되는지 뿐만 아니라 어떤 루틴이 어떤 루틴을 호출하는지를 알아내는 데도 유용하다. gprof를 사용하여 코드의 어느 부분이 시간을 가장 많이 소모하는지를 알 수 있다. 프로그램 실행에서 어느 함수가 시간 소모에 대해 가장 많은 부분을 차지하는 지를 결정하는 가장 효율적인 방법으로 gprof를 사용한 소스 분석을 들 수 있다.
kprof에 관해 알아야 할 몇 가지
kprof는 gprof가 생성한 프로파일 정보를 보여주는 그래픽 도구이다. kprof는 리스트나 트리 모양으로 정보를 보여준다는 면에서 그리고 정보를 이해하기 쉽게 보여 준다는 면에서 아주 유용하다.
kprof는 다음과 같은 기능을 갖는다.
- flat 프로파일 뷰는 프로파일링 정보뿐만 아니라 모든 함수와 메쏘드를 보여준다.
- 계층적 프로파일 뷰는 함수나 메쏘드가 호출하는 다른 함수나 메쏘드의 트리를 보여준다.
- 그래프 뷰는 호출 트리를 그래프로 보여준다.
참고문헌
- Optimizing Linux Performance: A Hands-On Guide to Linux performance tools, by Philip G. Ezolt, Prentice Hall PTR
- Linux Debugging and Performance Tuning: Tips and Techniques, by Steve Best, Prentice Hall PTR
- http://www.gnu.org/software/binutils/manual/
- http://www.yolinux.com/TUTORIALS/LinuxTutorialOptimization.html
- Performance Tuning for Linux Servers, by Badari Pulavarty, Gerrit Huizenga, Sandra K. Johnson, IBM Press
저자 Swayam Prakasha는 주로 운영체제, 네트워킹, 네트워크 보안, 인터넷 서비스, LDAP, 웹서버와 같은 분야에 수년 동안 종사해 오고 있다. Swayam은 상업 출판용 많은 저작물이 있으며 학술대회에 논문을 제출하기도 했다. 현재 그는 Unisys Bangalor의Linux System Group에서 일하고 있다.
역자 주재경님은 현재 (주)세기미래기술에 근무하고 있으며 리눅스, 네트워크, 운영체제 및 멀티미디어 코덱에 관심을 가지고 있습니다.
* e-mail :
jkjoo@segifuture.com