'자바튜닝'에 해당되는 글 1건

  1. 2009.06.26 자바 튜닝 자료
01.JAVA/Java2009. 6. 26. 23:14
반응형
1. 프로그램의 최적화 원칙

q        80/20 : 이것은 프로그램 수행 시간의 80% 실제 프로그램 코드의 20% 잡아먹는다는 것을 의미합니다. 때문에 실제 프로그램 코드 수행 시간의 80% 차지하는 일부 코드를 찾는 일이 중요하다는 것입니다.

q       빠른 알고리즘: 같은 프로그램이라도 수행하는 프로그램을 번만 수행해도 되도록 코딩 한다면 훨씬 빠르겠죠. 평소에 다양한 로직으로 프로그램을 보고, 어떻게 하면 간결하고 빠르게 돌아가는 코드를 만들 있을지를 많이 궁리해야 하겠습니다.

q       가벼운 데이터 구조: 리소스를 적게 사용하는 것이 훨씬 빠르게 작동한다는 것은 당연한 이치일 것입니다. 불필요하게 메모리를 많이 쓰는 코딩은 지양합니다.

q       가독성과 최적화: 프로그램의 최적화를 중요시 할지, 가독성을 중요시 할지를 판단해야 합니다. 다음 예제 1 가독성과 최적화의 딜레마라 있습니다.

   예제 1

   x >> 2 또는 x / 4,

   x << 1 또는 x * 2

<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /> 


 

1.1.  자바에서 String 생성되면 변하지 않는 성질(immutable) 가지고 있습니다.

따라서 스트링들 사이에 + 연산을 수행하면 새로운 스트링을 생성하고,

양쪽 스트링의 내용을 복사한 후 앞의 스트링을 가비지 컬렉션합니다.

이에 따른 부하가 많아지므로 스트링의 연산이 필요할 경우,

String 대신 고정적이지 않은 StringBuffer를 사용하는 것이 좋습니다

(String 결합은 과도한 쓰레기와 array 복사를 일으킨다.)

 

q     String s = “hi ” + “Mr. ” + “ ” + “Buddy!”;

문자열을 컴파일할 때 완전히 결정 할 수 있다면, 연결연산자가 StringBuffer 사용보다 효율적입니다.

컴파일 할 때 결정할 수 없다면(런타임시 결정) StringBuffer를 이용하는 것이 효율적입니다.

 

q     StringBuffer 버퍼 크기 초기값을 생략하면 불필요한 resizing 일으킬 있습니다.

(정확한 크기를 모른다면 그냥 두는 것이 나음)

 

StringBuffer sb = new StringBuffer();

sb.append("aaa" + "\n");

는 불필요한 스트링 연결작업으로  성능을 저하시킵니다

sb.append("aaa").append("\n");

 

sb.append("\n");

String을 추가하는 것보다 char를 추가하는 것이 빠릅니다

sb.append('\n');

 

setLength라는 메쏘드를 통해서 객체를 새로 생성하지 않고서 계속 사용할 수 있습니다.

StringBuffer sb=new StringBuffer();

sb.append("Hello");

System.out.println(sb.toString());

sb.setLength(0);

sb.append("World");

System.out.println(sb.toString());

 

StringBuffer는 동기 StringBuilder는 비동기방식이다

 

q     문자열을 분석해서 분할해야하는 경우. 보통은  StringTokenizer 객체를 쓰게된다. 하지만 클래스는 내부적으로.. 굉장히 무거운 클래스이다.. 왜냐하면. 이전에 분할한 정보를 가지고 있기 때문이다.. 따라서 단순히 앞으로 나가면서 잘라내기만을 시도할 문자열은 간단히 스스로 구현하여 쓰도록 하자


 

스트링비교할때

 

if (name.compareTo("John") == 0) ...

if (name == "John") ...

if (name.equals("John")) ...

if ("".equals(name)) ...

 

위의 비교중 잘못된 것은 없지만 좋은것도 없다.

q     compareTo 메소드는 너무 거창하고 장황하다.

q     == 연산자는 object identity 테스트하는 것으로 (--> 동일한 object인지 체크) 아마도 여러분이 원하는 것이 아닐 것이다.

q     equals 메소드가 가야할 길이지만, 상수와 변수를 거꾸로하면 name null 때도 안전하며 추가로 loop안에서 사용될 경우 equals 메소드가 항상 동일한 object에서 호출되기 때문에 속도상의 이점을 있다.

q     빈문자열을 테스트할 때는 길이가 0인지 체크하는 것이 빠르다. 이는 equals에서 처음에 hash value 먼저 계산할수도 있기 때문이다.

(--> Sun JDK에서는 길이를 먼저 체크한 후 문자열의 char를 비교하며 hash값을 체크하지 않는다.)

 

if ("John".equals(name)) ...

if (name.length() == 0) ...

 

 

 

 

 

 

 


 

1.2.  for 루프에서 객체를 생성금지. 재사용 매개변수를 보내는 것이 중요하다.

 

임시 객체를 빠른 캐시에 저장하는 C C++와 달리, 자바에서는 메모리의 힙 영역에 객체를 저장합니다.

따라서 임시 객체를 생성할 때마다 힙에 액세스해야 하므로 속도가 느려집니다.

또한 임시 객체에 대한 가비지 컬렉션의 오버헤드가 큽니다.

 

가능한 루프안에서는 객체를 생성하지 말라

스트링 객체를 더하지도 말라 같은 오버헤드가 발생한다

 

루프문이나 자주 불리워지는 메쏘드에서의 객체 생성을 피합니다.

한 개의 employee 객체에 계속 값을 할당하고 지우고 하는 식으로 사용합니다.

for ( int i = 0; i < 100; i++ ) {

             Employee employee = new Employee();

             list.add( employee );

} // end for i

 

Employee employee = null;

for ( int i = 0; i < 100; i++ ) {

             employee = new Employee();

             list.add( employee );

} // end for i

 

 

for 문의 증가값이나. 기본적인 정수의 사용에 있어 int 보다 작은 byte, char, short

사용할려는 경향이 있다 하지만 이것은 오산이다.

 

또한 반복문 내의 비교 상대값을 메쏘드 호출해서 계산해 오는 것이라면 다음처럼 미리 지역 변수에 그 값을 저장한 후에 그것을 사용하는 것이 좋습니다.

for(int k=0; k< s.length(); k++) 보다는

int limit = s.length();

for(int k=0; k < limit; k++)

 

 

 

 

 

1.3. 기타 등등

1.3.1.  Overkill initialization

public class B {

    private String name = null;

}

이 프로그래머는 C로 코딩하는데 익숙하다. 그래서 자연스럽게 모든 변수가 적절하게 초기화되기를 원했다. 하지만 이는 필요하지 않다. 자바 언어 명세서는 멤버 변수가 항상 0이나 null, false 등의 특정 값으로 자동으로 초기화되는 것을 보장한다. 이를 명시적으로 선언함으로써 프로그래머는 constructor에 앞서 클래스 initializer에서 수행되도록 만든다. 이는 불필한 과잉행동으로 피해야한다.

public class B {

    private String name;

}

 

1.3.2. Converting numbers to Strings

"" + set.size()

new Integer(set.size()).toString()

Set.size() 메소드의 반환형은 int이다. 여기서는 String으로 변환하고자 한다. 위의 두 예제는 사실상 변환을 하고 있다. 하지만 첫번째는 불필요한 String 연결 작업을 수행하고, 두번째는 임시로 Integer 래퍼를 생성한다. 변환을 하는 바른 방법은 아래와 같다.

String.valueOf(set.size())

 

1.3.3. Not taking advantage of immutable objects

zero = new Integer(0);

return Boolean.valueOf("true");

Boolean Integer는 불편값이다. 따라서 같은 값을 표시하는 다수의 개체를 생성하는 것은 의미없는 일이다. 이러한 클래스들은 내부에 자주 사용되는 인스턴스에 대한 캐쉬를 가지고 있다. Boolean의 경우 단지 두 개의 인스턴스만이 존재한다. 프로그래머는 이러한 사실을 활용할 수 있다.

zero = Integer.valueOf(0);

return Boolean.TRUE;

 

1.3.4.  etcetera

q     Strength reduction : Shift 연산은 비트 단위로 값을 이동하는 것입니다. 좌로 1비트 이동하면 곱하기 2, 우로 1비트 이동하면 나누기 2 됩니다. 이때 곱하기나 나누기보다 Shift 연산이 훨씬 빠릅니다

x >> 2 또는 x / 4, x << 1 또는 x * 2

(증감연산은 ++ -- +1, -1보다 훨씬 빠르고, shift 연산이 곱셈이나 나눗셈보다 빠릅니다.)

q     Unrolling loops: 반복문 내에서 이상의 연산을 수행함으로써 반복 회수를 줄여 반복 제어의 부하를 줄여 주는 것을 말합니다. 다음예제에서 x배열은 항상 2 배수이기 때문에 제어변수 i 2 증가시키면서 내부에서는 연산을 2 수행합니다. 이는 결과적으로 연산 회수는 같으나 반복제어 회수가 줄어든다는 이점이 있습니다.

double picosy = Math.PI * Math.cos(y);

for (int i = 0; i < x.length; i += 2) {

x[i] *= picosy;

x[i+1] *= picosy;

}

q     new 통한 생성자 호출대신 static 메쏘드를 사용합니다. 다음의 예제 9에서는 Integer 클래스를 생성한 다음 string에서 정수 값을 추출해냈습니다. 예제 10에서는 Object Instance 필요없는 static 메쏘드를 사용했습니다.

 

예제 9

String string="55";

int theInt=new Integer(string).intValue();

 

예제 10

String string="55";

int theInt=Integer.parseInt(string);

 

q     캐스팅의 지향

캐스팅을 하면 컴파일 시간에 그 타입이 결정될 수 없기 때문에 실행 시간을 느리게 만듭니다. 인터페이스를 캐스팅할 경우 더욱 많은 실행 시간을 필요로 하게 됩니다. 이를 위해서 같은 객체를 여러 번 캐스팅할 필요가 있을 경우 지역 변수에 저장해서 사용합니다. 캐스팅을 될 수 있는 한 피하고, 가장 빠른 변수 타입은 int이기 때문에 불가피한 경우를 제외하고는 int를 사용하는 것이 좋습니다.

 

q     빠른 변수 타입의 사용

변수의 성능은 그것의 범위와 타입에 의해서 결정됩니다. 가장 빠른 변수는 지역 메쏘드 변수이며, 가장 빠른 변수 타입은 int와 참조 변수입니다. 또한 어떻게 배열을 초기화시킬지가 중요한 요소가 됩니다. 다차원 배열로 정의할 경우 매번 생성자를 호출하기 때문에 꼭 필요한 경우가 아니면 다차원 배열로 정의하지 않습니다. 배열이 지역변수일 경우 메쏘드 호출시 매번 초기화를 수행하므로, 배열을 static으로 선언하면 초기화가 반복되는 것을 제거할 수 있습니다

 

 

1.4.  Collection 객체의 크기를 필요한 만큼 크게 잡는다

Ex) Vector의 크기를 증가하는 방법은

더 큰 크기의 내부 배열 객체를 생성 -> 이전 배열에서 새로운 배열로 새롭게 복제

 -> 이전 배열을 삭제

하는 방법으로 크기를 증가 시킨다 따라서 Colloection 을 가능한 큰 크기로 잡아두는 것이 좋다

 

 

1.5.  참조변수 유지보다 고정변수?

 

1.6. Thread Connection  오버헤드가 매우크므로 스레드풀과 커넥션풀을 사용하라
- VectorPoolManager - Collection
재사용

 

 

1.7.  Vector 사용후 Size 0으로 설정한다. 참조가 없기때문에 레퍼런스가 제거된다.

 

 

1.8.  객체의 정규화

  - 많은 객체의 사본을 몇 개의 객체로 대체한다.

  - Boolean의 정규화

  - 자주 사용하는 Integer의 정규화

  - "" 공백 또는 요소없는 배열, Date 객체

  - String.intern()을 통하여 정규화가 가능하다.

 

 

 

1.9.  Reference 객체 사용.

  - WeakReference를 사용하여 메모리가 부족하면 레퍼런스 객체는 가비지 컬렉션되면서 초기화된다.

  - 가비지 컬렉션이 되었다면 그 객체는 null이 된다.

  - 재사용하고 싶으나 곧바고 필요하지 않는 객체를 메모리에 유지할 수 있다 하지만 부족해지면

    우선적으로 가비지 컬렉션 된다.

 

1.10.  상수는 정수로 사용하자.

 

1.11.  String 정수를 int 변환시 객체 생성이 아닌 ParseInt 사용하자.

 

1.12.  자료형을 변환할때

 

"" + set.size()

new Integer(set.size()).toString()

 

Set.size() 메소드의 반환형은 int이다. 여기서는 String으로 변환하고자 한다.  위의 두 예제는 사실상 변환을 하고 있다.

하지만 첫번째는 불필요한 String 연결 작업을 수행하고,

두번째는 임시로 Integer 래퍼를 생성한다. (참고. Integer.toString() 함수는 static 함수가 아니다)

 

String.valueOf(set.size())

 

 

zero = new Integer(0);

return Boolean.valueOf("true");

 

Boolean Integer는 불편값이다. 따라서 같은 값을 표시하는 다수의 개체를 생성하는 것은 의미없는 일이다.

이러한 클래스들은 내부에 자주 사용되는 인스턴스에 대한 캐쉬를 내부에 가지고 있다.

Boolean의 경우 단지 두 개의 인스턴스만이 존재한다. 프로그래머는 이러한 사실을 활용할 수 있다.

 

zero = Integer.valueOf(0);

return Boolean.TRUE;

(이 메소드(valueOf) 가 빈번하게 요구되는 값을 캐쉬하므로 new Integer보다 비용이 훨씬 적음)

 

 

1.13.  기타등등

 

public class B {

    private int count = 0;

    private String name = null;

    private boolean important = false;

}

이 프로그래머는 C로 코딩하는데 익숙하다. 그래서 자연스럽게 모든 변수가 적절하게 초기화되기를 원했다.

하지만 이는 필요하지 않다. 자바 언어 명세서는 멤버 변수가 항상 0이나 null, false 등의 특정 값으로 자동으로 초기화되는 것을 보장한다.

이를 명시적으로 선언함으로써 프로그래머는 constructor에 앞서 클래스 initializer에서 수행되도록 만든다.

이는 불필한 과잉행동으로 피해야한다.

public class B {

    private int count;

    private String name;

    private boolean important;

}

 

 

Collection l = new Vector();

for (...) {

   l.add(object);

}

Vector는 동기화된 ArrayList이다. 그리고 Hashtable은 동기화된 HashMap이다. 두 클래스는 동시 접근이 필요한 경우만 사용되어야 한다.

만약 이러한 컬렉션 클래스가 로컬 변수로 일시적으로 사용될 때는 동기화가 필요없으며, 이는 성능을 저하시킬 수 있다.

Collection l = new ArrayList();

for (...) {

   l.add(object);

}

 

 

// returns [1]: Location, [2]: Customer, [3]: Incident

bject[] getDetails(int id) {...

 

설령 문서화되었다 해도, 이런 종류의 메소드의 값 반환은 다루기 난처하고 에러를 쉽게 유발한다.

여러분은 반드시 이 값들을 담는 작은 클래스를 선언해서 사용해야 한다. 이는 C struct와 유사하다.

Details getDetails(int id) {...}

 

private class Details {

    public Location location;

    public Customer customer;

    public Incident incident;

}

 

 

1.14. 성능 확인표

대부분의 제안은 병목점을 확인한 후에만 적용이 가능하다.

. 메모리 문제가 있는지 확인한다.

. 특히 루프에서 사용하는 임시 객체의 개수를 줄인다.

- 자주 사용하는 메소드에서 임시 객체의 생성을 피한다.

- 컬렉션 객체의 크기를 미리 설정한다.

- 가능하면 객체를 재사용한다.

- 재사용하기 전에 컬렉션 객체를 비운다(너무 크지 않다면 용량을 줄일 필요

는 없다) .

- 데이터 타입간의 변환에서 임시 객체의 생성을 줄일 수 있는 메소드를 사

용한다(특히 스트링과 스트림) .

- 메소드가 객체를 리턴하기 보다는 재사용 가능한 객체를 매개변수로 받도

록 정의한다.

- 가능하면객체를 정규화한다.'=='연산자로 정규화한 객체를비교한다.

- (그 개수가 별로 많지 않다면) 클래스가 필요한 개수의 객체만 생성한다.

- 스트링과 다른 객체를 int 상수로 대체한다. '=='연산자로 비교한다.

- 객체 대신에 원시 데이터 타입을 사용한다.

- 메소드를 사용하기 위해 객체 생성하는 것을 피한다.

- 내포하는 객체를 줄이기 위해 여러 객체를 묶는다.

- 큰 사이즈의 객체 컬렉션은 배열을 사용하여 미리 할당한다.

- 스트링 연결 연산자(+) 대신에S t r i n g B u ff e r를 사용한다.

- 사본을 생성하지 않고 직접 객체를 변경하는 메소드를 사용한다.

- 원시 데이터 타입을 직접 사용하는 클래스를 사용한다.

? T h r e a d L o c a l을 사용하여 싱글톤에 접근하는 것을 고려한다.

? f i n a l을 사용하여 변수의 변경을 방지한다.

? We a k R e f e r e n c e를 사용하여 정규화용 테이블을 유지한다(캐시는 S o f t R e f e r e n c e를 사용한다) .

? 객체 생성 병목점을 객체 생성 방식을 변경하여 줄인다.

─ 생성자를 간단하게 유지하고 상속을 너무 깊이 하지 않는다.

─ 한 번 이상의 변수 초기화를 피한다.

c l o n e() 메소드를 사용하여 생성자의 사용을 피한다.

─ 배열의 생성이 더 빨라질 수 있다면 복제한다.

─ 간단한 배열은 초기화가 더 빠르고, 복잡한 배열은 복제가 더 빠르다.

? 객체 생성에 의한 성능 저하는 생성 시간을 변경하여 제거한다.

─ 객체를 미리 생성하여 필요할 때까지 유지한다.

─ 사용하지 않는 객체나 변수가 있거나 나누어서 생성할 필요가 있을 경우

에는 추후 초기화 기법을 사용한다.

─ 디자인에서 필요하거나 추후 초기화로 성능을 향상시킬 수 있을 경우에만

추후 초기화를 사용한다.

Posted by 1010