본문 바로가기

공부/컴퓨터

자바 프로그램 성능 개선 방법(박재현씨의 글)

반응형
원글 : http://www.javaservice.net/~java/bbs/read.cgi?m=qna&b=discussion&c=r_p&n=1034535273&d=tb#1034535273

과거 C 언어가 어셈블러를 대체한 것은 결코 C 언어가 어셈블러보다 빠르기 때문이 아니다. 마찬가지
로 C++언어가 C 언어를 대체하고 있는 상황도 C++이 C보다 빨라서가 아니다. 실제 C++보다는 C가 C
보다는 어셈블러가 훨씬 빠르다. C와 C++이 확산된 배경에는 속도가 아니라 프로그램이 쉽고 이식성
이 좋기 때문이다. 마찬가지로 자바는 C++보다는 느리지만 “Write Once, Run Anywhere”로 대표
되는 이식성과 코드의 안전성 등 개발상의 많은 이점을 제공하면서 급속히 확산되고 있다. 이러한 과
정에서 자바는 항상 두 가지 문제점을 지적 받아왔다.
하나는 편리한 개발 툴이 부족하다는 것이고 다른 하나는 성능상의 문제로 인해 중요한 응용 시스템을
개발하는 데는 부족하다라는 것이다. 그러나 이미 주지하다시피 자바 개발 툴의 경우 볼랜드의 J 빌더
를 비롯하여 마이크로소프트의 비주얼J++ 등 많은 개발 회사들이 수없이 많은 개발 툴을 소개하고 있
기 때문에 오히려 어떤 개발 툴을 선정할 것인가라는 고민을 안길 정도로 성숙된 상태이다. 그러나 성
능 상의 문제는 아직 완벽하게 해결된 상태는 아니다. 이러한 배경하에서 이번 호에서는 자바의 성능
문제를 보다 근본적으로 이해하고 이를 바탕으로 프로그래밍 측면에서 자바의 성능을 개선하기 위한
배경지식을 제공하고자 한다.

1. 자바의 성능 저하 원인들

멀티쓰레드, 가베지 컬렉션, 런타임 바인딩 등, 자바에서 제공하는 아주 유용하고 편리한 기능들은 실
제 자바 프로그램을 수행할 때 수행 속도를 느리게 하는 요인들이다. 왜냐하면 이 들 기능을 지원하기
위해서는 보다 많은 자원과 계산을 필요로 하기 때문이다. 자바의 성능 문제를 보다 정확하게 이해하
기 위해 앞서 살펴본 자바 클래스의 구조를 숙지하면서 어떤 요인들이 자바의 성능을 저해하는 지 살
펴보자.
          동적 바인딩과 동적 클래스 로딩

정적 바인딩을 사용하는 C 컴파일러는 컴파일시 함수 호출이 발생하는 곳에 해당 함수들의 포인터를
기록해둔다. 따라서 런타임에 복잡한 함수 호출을 해결하기 위한 오버헤드를 피할 수 있다. 이에 반해
동적 바인딩을 사용하는 자바는 런타임시 필요한 클래스들을 바인딩한다.
이 과정에서 자바 런타임은 상속관계에서 발생하는 오버로딩된 메소드나 오버라이딩된 메소드 호출을
풀기위한 작업을 수행한다. 이러한 이유에서 자바는 C에서의 함수 호출보다 느리다. 자바와 마찬가지
로 C++은 가상 함수를 런타임에 바인딩하기 위한 별도의 작업을 수행한다.
따라서 C++이 C보다 느리다. 동적 바인딩과 아울러 자바 가상 머쉰은 프로그램 수행중 동적으로 클래
스 파일을 로드할 수 있다. 이때 로드된 클래스 파일은 자바 가상 머쉰에 의해서 안전한지 검사되고
초기화되야 한다. 따라서 동적 바인딩과 동적 클래스 로딩은 성능 저하를 유발한다.

          가베지 컬렉션

C와 C++ 로 프로그램을 작성할 때 가장 주의를 기울여야 하는 부분이 바로 메모리 관리 부분이다.
따라서 자짓 포인터를 잘못사용하거나 메모리 관리를 잘못하면 엄청난 낭패에 직면한다. 자바는 이러
한 문제를 해결하고자 포인터 기능을 제거했으며 배열의 바운드 검사, 내부 널 포인트 검사, 0에 의
한 나누기 연산 등 보다 엄격한 타입 검사 기능을 수행한다. 또한 메모리 관리에 있어 가베지 컬렉터
라는 시스템 쓰레드를 통해 사용하지 않는 객체의 메모리를 자동으로 가용 자원으로 되돌려 준다. 특
히 가베지 컬렉터의 경우 백그라운드 쓰레드로 수행되기 때문에 성능저하의 한 요인이다.

          멀티쓰레딩

자바 언어의 강력한 기능중의 하나가 바로 멀티쓰레드이다. 멀티쓰레드 사용에 있어 가장 비용이 발생
하는 기능이 동기화이다. 동기화란 쓰레드간의 충돌을 방지하여 공유 자원에 대한 일관성을 유지하는
중요한 메커니즘이다. 동기화를 위해 자바 프로그래머는 synchronized 라는 키워드를 사용한다. 쓰
레드 모니터는 synchronized로 설정된 메소드와 코드 블록이 한 순간에 한 번만 사용되도록 하기
위해 사용되는 모든 쓰레드들을 관리해야 한다. 특히 JDK 라이브러리에서 제공하는 많은 메소드들이
synchronized로 선언되어 있기 때문에 쓰레드 모니터는 항상 백그라운드에서 쓰레드 관리 작업을 수
행하고 있다. 이러한 동기화 작업은 자바의 성능을 저하시키는 요인중 하나이다.

     2.자바의 성능 개선을 위한 여러가지 시도들

2.1 자바 컴파일러

자바의 가장 취약한 약점인 성능 문제는 JIT(Just-in-time)컴파일러와 보다 빠른 인터프리팅 기술
을 통해 상당히 개선되었다. 이젠 초창기의 자바 프로그램이 기존의 프로그램보다 3배 내지 4배가 느
리다라는 불평은 다소 누그러진 것 같다. 그럼에도 불구하고 자바 프로그램의 성능 문제를 완전히 해
결하리란 다소 어려워 보인다. 왜냐하면 자바 자체가 기존의 C나 C++과는 다른 인터프리터 언어이기
때문이다. 현재 5가지 종류의 컴파일러가 존재한다 : 소스 코드 컴파일러, 바이트코드 최적화기,
JIT 컴파일러, 동적 컴파일러, 정적 컴파일러.
소스 코드 컴파일러는 별도의 최적화 방법없이 자바 소스 코드를 바이트 코드로 컴파일한다. 가령, 현
재 JDK에서 제공하는 javac는 ?o 옵션을 제외한 별도의 최적화 기능없이 바이트 코드로의 컴파일 기
능만을 제공한다. 바이트코드 최적화기는 컴파일된 바이트 코드를 최적화된 형태의 바이트 코드로 다
시 컴파일한다. 대표적인 제품으로는 프리엠프티브(Preemptive)사의 DashO 제품이 있다.

JIT 컴파일러는 인터프리터를 거치지 않고 바이트코드를 머쉰 코드로 컴파일하기 때문에 앞선 컴파일
방법보다 휠씬 빠르다. 실제 JIT 컴파일러는 자바 가상 머쉰이 컴파일된 바이트 코드를 메모리상에
로드한 다음 이 코드를 머쉰 코드로 변환한다. 만일 새로운 클래스가 로드되면 JIT는 새로운 바이크
코드를 다시 컴파일한다. 그러나 JIT 컴파일러는 해당 클래스의 크기가 큰 경우 실행과정에서 심각한
지연 시간이 발생한다. 이러한 문제의 해결 방법은 전체 클래스를 컴파일하지 않고 개별적인 메소드
단위로 컴파일하는 것이다. 현재 볼랜드의 J빌더가 이러한 컴파일 방식을 제공한다.

동적 컴파일러는 프로그램의 프로파일을 생성하고 컴파일한 후 성능상의 문제가 있는 부분을 찾은 후
이 부분을 다시 컴파일하여 성능상의 문제를 해결한다. 이러한 동적 컴파일러의 대표적 기술이 많은
자바 프로그래머들이 기대하고 있는 핫스팟(HotSpot) 이라는 코드명의 기술이다
(http://self.smli.com/). 이 기술은 기존의 C++이 제공하던 성능과 대등하거나 보다 월등한 성
능을 제공할 것이다. 선사는 핫스팟 기술을 확보하기 위해 롱뷰(LongView) 테크널리지사라는 회사를
사들였다. 이 회사는 동적 컴파일 기술을 연구했던 스탠포드 대학의 연구원들이 설립한 회사이다.

정적 컴파일러는 C/C++의 컴파일러처럼 초기 단계에 자바 소스 코드나 바이트 코드를 직접 머쉰 코드
로 변환한다. 따라서 정적 컴파일러로 작성된 애플릿은 이식성을 보장받지 못한다. 왜냐하면 애플릿
코드 자체가 머쉰 코드이기 때문에 여러가지 플랫폼의 부라우져에 내장되어 있는 가상머쉰에서 수행할
수 없다. 그러나 자바 응용 프로그램은 빠른 속도를 보장받을 수 있다. 현재 시멘텍, 슈퍼시드,
IBM, 마이크로소프트, 볼랜드 등 많은 회사에서 정적 컴파일러를 제공하고 있다.

                2.개선된 프로그래밍 방법

자바 가상 머쉰이나 컴파일러의 개선을 통한 성능 향상 외에 보다 자바에 적합한 프로그래밍 방법을  
통해 많은 성능 향상 효과를 얻을 수 있다. 따라서 프로그래밍 기술을 통해 성능 향상을 도모할 수
있는 주요한 코딩 기술을 숙지하는 것이 필요하다. 다음은 주요한 프로그래밍 방법을 정리한 것이다.
 
          final 사용을 통한 성능 분석 

final은 선언된 객체에 변경을 가하지 못하도록 강제하는 키워드이다. 다시 말해, final로 선언된
클래스는 더 이상 하위 클래스를 갖을 수 없으며 final로 선언된 메소드는 오버라이드 할 수 없다.
또한 final로 선언된 변수는 초기값을 변경할 수 없다(참고로 final static으로 변수를 선언하여
C++의 const와 같은 효과를 볼 수 있다).

이처럼 final은 성능과 보안에 많은 영향을 끼친다. 과연 어떻게 영향을 미치는 것일까. 먼저 보안
측면에서 final로 선언된 클래스는 다른 클래스에서 상속을 할 수 없기 때문에 해당 클래스의 내용을
완전히 숨길 수 있다. 또한 성능 측면에서 final 클래스는 컴파일러에 의해서 하위 클래스에 의해서
오버라이딩이 불가능한 클래스로 인식되어 컴파일시 동적 메소드 호출 기능을 제거하여 메소드 호출을
최적화한다(자바에서 모든 메소드는 동적 호출에 의존한다). 따라서 일반 메소드 호출보다 휠씬 빠르
다.

그러나 final을 클래스 전체에 사용하는 것은 아주 섬세한 고려가 필요하다. 가령, Integer같은 래
퍼 클래스나 시스템 보안상의 문제 등을 유발하는 클래스처럼 꼭 필요한 경우가 아니면 클래스 전체를
final로 설정하지 않는 것이 좋다. 이를 위해 메소드 단위로 final을 사용하는 것이 효율적이다.
 
          String보다 StringBuffer가 빠르다. 

String은 자바 가상 머쉰에 의해서 StringBuffer 객체로 변환되어 처리된다. 특히 String 결합
(concatenation) 연산은 내부에서 StringBuffer들로 변환된 후 함께 결합된 후 다시 String 객
체로 바뀌기 때문에 가장 많은 비용이 발생한다. 따라서 String보다는 StringBuffer나 char 배열
을 사용하는 것이 빠르다.

          가급적 임시 객체의 생성을 피하라.

루프나 자주 사용하는 메소드내에서 생성하는 임시 객체들은 가베지 컬렉터에게 보다 많은 부담을 안
긴다. 따라서 가급적 루프나 자주 사용하는 메소드내에서 임시 객체의 생성을 피하는 것이 좋다.

3 자바 성능 분석 방법

자바의 성능을 향상시킬 수 있는 또 하나의 방법으로 성능 분석 툴을 통해 메소드 호출이 집중되는 메
소드를 찾고 적절히 대처하는 방법을 들 수 있다. 이러한 성능 분석 툴로는 JDK의 자바 인터프리터
(java)에서 제공하는 것과 다음의 상업용 툴을 이용할 수 있다: 선테스트의 JavaScope, KL그룹의
Jprobe, 인튜이티브 시스템의 Optimizelt, 뉴메가의 TrueTime, 내셔널 소프트웨어의 Visual
Quantify 등이 있다.

자바 인터프리터(java)에서 제공하는 성능 분석 방법을 좀 더 자세히 살펴보자. 자바 클래스 파일을
실행시킬 때 java_g 인터프리터에 -prof 옵션을 주면 가상 머쉰이 해당 바이트 코드를 수행하면서
java.prof 라는 파일에 성능에 대한 프로파일을 생성해낸다. 가령, TestProf 라는 파일을 수행시
킨다고 할 때 다음과 같이 명령할 수 있다.(JDK1.1 버전에서는 최적화되지 않은 자바 인터프리인
java_g와 javaw_g와 함께 사용해야만 프로파일을 얻을 수 있다. 참고로 JDK1.2에서는 java의 추
가 옵션 항목으로 -Xprof 라고 지정하여 직접 사용할 수 있다.)

C/> java_g -prof TestProf

만일 java.prof 라는 기본 프로파일 파일외에 다른 이름의 프로파일을 생성하고자 한다면 다음과 같
이 프로파일의 이름을 지정할 수 있다.

C/> java_g -prof:Test.prof TestProfClass

생성된 프로파일의 내용을 이해하기 위해 다음의 간단한 코드의 성능 분석을 수행해 본다.

// TestProf.java

class TestProf{

void Method1(){this.Method2(); this.Method3(); System.out.println("METHOD1"); }

void Method2(){this.Method3(); System.out.println("METHOD2");}

void Method3(){System.out.println("METHOD3"); }

public static void main(String[] args)

{

TestProf tp = new TestProf();

int COUNT = 10000;

for(int i=0; i <= COUNT ; i++)

{

System.out.println("COUNT :" + i );

tp.Method1();

tp.Method2();

tp.Method3();

}

}

}

위의 파일을 컴파일 한 후 (javac TestProf.java) 생성된 바이트 코드(TestProf.class)를 다
음과 같이 실행시켜 본다.

C/> java_g -prof TestProf

실행 후 현재 디렉토리에 java.prof란 파일이 생성된다. 이 파일은 일반 텍스트 파일이다. 따라서
일반 편집기를 통해 내용을 확인할 수 있다. 해당 프로파일의 내용은 다음과 같다( 아래 파일 내용은
전체 결과중에서 TestPro 의 메소드 호출에 관련된 것들만 모은 것이다. 실제 더 많은 내용이 포함
되어있다.)

count callee caller time

………

20002 TestProf.Method3()V TestProf.Method2()V 259260

10001 TestProf.Method2()V TestProf.Method1()V 279033

10001 TestProf.Method2()V TestProf.main([Ljava/lang/String;)V 248303

10001 TestProf.Method3()V TestProf.main([Ljava/lang/String;)V 130936

10001 TestProf.Method1()V TestProf.main([Ljava/lang/String;)V 609583

10001 TestProf.Method3()V TestProf.Method1()V 138555

1 TestProf.<init>()V TestProf.main([Ljava/lang/String;)V 0

1 TestProf.main([Ljava/lang/String;)V <unknown caller> 1208300…………

…..

[ java.prof의 결과 ]

java.prof 프로파일은 메소드 실행에 대한 정보를 제공한다. 다시 말해 메소드간의호출 회수
(count)와 소요된 시간(time)에 대한 정보 등을 호출한 메소드(Caller)와 호출당한 메소드
(Callee) 간의 관계를 통해 제공한다. 가령, 위의 결과중 첫째 줄은 Method2()가 Method3()을
20002번 호출했으며 걸린 시간은 259260 밀리초 만큼 소요되었다는 사실을 알려준다. 마찬가지로 다
른 줄을 분석하면 Method3()은 main()에서 10001번, Method1()에서 10001번 총 40004번 호출
되었으며 , 총 소요 시간도 528751 밀리초 만큼 소요되었다.
따라서 한번 호출시 약 132마이크로초 만큼 소요된 것을 알 수 있다. 이러한 정보를 이용하면 해당
프로그램에서 집중적으로 호출되는 메소드를 찾아 불필요한 코드나 객체 생성을 제거하여 프로그램의
성능을 높일 수 있다.
 

지금까지 자바의 성능 문제와 관련된 여러 사항에 대해 살펴보았다. 이젠 더 이상 자바의 성능 문제가
자바의 도입과 사용을 하는 데 있어 가장 큰 장애가 아님을 알 수 있었을 것이다. 우리는 이식성있는
자바 코드를 정적 컴파일러나 JIT 컴파일러를 통해 C++과 유사한 성능을 내는 다양한 플랫폼에 적합
한 자바 프로그램을 작성할 수 있다. 특히 반가운 사실은 올 하반기 핫스팟 컴파일러와 JDK1.2 버전
이 발표될 예정이라는 사실이다. 특히 JDK1.2는 앞서 정리해 보았던 자바의 성능 상의 문제들인 새
로운 가베지 컬렉션과 멀티쓰레드 모니터 등에서 발생하는 문제를 개선했으며 JIT 컴파일러를 지원할
예정이다. 아울러 올 하반기 본격적으로 소개될 자바 칩은 자바 바이트 코드를 머쉰 언어 차원에서 수
행하게 될 것이다. 아울러 향후 출시될 CPU들은 자바 칩을 내장할 예정이기 때문에 자바의 성능 문제
는 근본적으로 해결될 수 있을 것이다. 이제 여러분은 C++과 같은 성능을 내는 이식성있고 생산성있
는 자바 프로그램을 작성할 수 있게 되었다.

[ 참고 문헌 ]

     1.How to Java Soup Up Java, May , 1998 , BYTE

     2.Java Guide , APRIL, 1998, PC MAGAZINE

     3.자바 프로파일 ,
http://java.sun.com/products/jdk/1.1/docs/tooldocs/solaris/java.html

     4.핫스팟, http://java.sun.com/javaone/sessions/slides/TT01/tt01_43.htm

< 박스 기사 >

다음은 현재 가장 광범위하게 자바가 사용되고 있는 미국내 상황을 조사한 자료들이다. 이들 자료를
통해 자바의 내일을 엿볼 수 있을 것이다.

 

     1.질문 : “어떤 종류의 응용 프로그램에 자바를 이용할 것인가?”

          대상 : 미국내 1,000개의 회사

          출처 : 포레스트 리서치

          결과 : 1997년 1999년

          중요 응용 시스템 4% 56%

          연구 및 파일롯 시스템 36% 24%

          중요도가 낮은 시스템 12% 12%

          없다. 48% 8%
        
     2.질문 : “자바가 제공하는 이점들은 무엇인가?”

          출처 : 포레스트 리서치

          결과 :

          교차 플랫폼 지원 42%

          인터넷/인트라넷에 보다 적합 16%

          생산성 증가 12%

          보다 강건한 응용 시스템 작성 12%

          이점 없음 10%   

     3.조사 : 1998년 2월 현재 자바 애플릿을 채용한 웹사이트 현황은?

          출처 : 가멜란

          결과 : 9,045개의 사이트에서 자바를 채용하고 있으며 채용된 자바 응용 프로그램의

          종류는 다음과 같다.

          비즈니스/재정 분야 ?244개, 오락 및 예술 분야-329개, 게임 분야-1515개
반응형