Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- 폴안티스파이앱
- 스포티지r 풀체인지
- selenium #scraping #webscraping #jsoup #firefox
- svn
- Lambda Expressions
- 라곰프레임워크
- Subversion
- 책상
- 차이점
- CVS
- lagom framework
- 폴-안티스파이앱
- 라곰
- 폴-안티 스파이앱
- windows vista
- lagom
- 폴안티 스파이앱
- 윈도우즈 비스타
- 모니터
- 정통춘천닭갈비
- 스파이앱
- 폴-안티
- 명주
- TortoiseSVN
- 폴안티
- 썬
- java8 람다식
- volatile
- 설치
- 한강 #야경 #한강야경
Archives
- Today
- Total
장발의 개발러
Java 성능 개선을 위한 Programming 기법 본문
출처: http://warmz.tistory.com/603
Java 성능 개선을 위한 Programming 기법
JDK 1.3 버전 이후로 지원되는 Hotspot VM은 기본적으로 Heap에 동적으로 할당된 Object는 거의 회수 할 수 있다고 한다.
하지만 이 기능으로 인해서 VM은 엄청난 OverHead를 가지게 된다. 무리한 Object의 생성은 생성 당시에도 많은
OverHead를 초래하지만, 생성된 Object를 회수하기 위해서는 더 많은 작업이 요구된다.
이를 해결하기 위한 몇가지 Tip이 있는데, Avoiding Garbage Collection, Object 재사용, static instance variable의 사용에
의한 단일 클래스 인스턴스 구현 방법 등이 그것이다. 핵심은 가능한 Object 생성을 피하자는 것이다.
1. Avoiding Garbage Collection(static Method 사용)
1
2
3
4
5
6
7
8 |
에졔 1 ) String str = "55" ; int num = new Integer(str).intValue(); 예제 2 ) String str = "55" ; int num = Integer.parseInt(str); |
예제1) 에서는 Integer 클래스를 생성한 다음 str에서 정수값을 추출해냈다. Object를 생성하고 초기화하는 과정은
상당한 CPU 집얍적인 작업이고, Heap 영역에 Object가 생성되고 수집되기 때문에 가비지 컬렉터 작업을 수반한다.
가능한 예제2)처럼 Object의 Instance가 필요 없는 static메소드를 사용한다.
2. Avoiding Garbage Collection(임시 Object 생성 금지)
가장 흔한 예로 String Object의 append를 위해서 (+) 연산을 사용하는 것을 들 수 있다.
(+) 연산자를 사용해서 String Object를 append할 경우 우리가 생각하는 것보다 훨씬 더 많은 임시 Object가 생성되고,
가비지 컬렉터에 의해 다시 수집된다. String Object의 append 연산을 위해서는 StringBuffer 클래스를 사용한다.
어떤 메소드는 Object의 복사본을 반환하는 경우가 있다. 대표적인 예로 String 클래스의 trim() 메소드를 들 수 있다.(+) 연산자를 사용해서 String Object를 append할 경우 우리가 생각하는 것보다 훨씬 더 많은 임시 Object가 생성되고,
가비지 컬렉터에 의해 다시 수집된다. String Object의 append 연산을 위해서는 StringBuffer 클래스를 사용한다.
1
2
3
4
5
6
7
8
9
10
11 |
예제 1 ) String str = "Hello " ; str = str + "World" System.out.println(str); 예제 2 ) StringBuffer str = new StringBuffer(); str.append( "Hello " ); str.append( "World" ); System.out.println(str.toString()); |
trim() 메소드가 수행되면 기존의 Object는 수집되고, 기존 Object의 복사본이 사용되게 된다. 임시 Object 생성과
복사본을 반환하는 메소드가 루프 안에서 사용될 경우 무수히 많은 Object가 생성되고 수집되기 때문에 심각한
문제를 야기하게 된다. 루프 내부에서 Object를 생성하는 것은 가급적 피해야한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14 |
예제 3 ) for ( int i = 0 ; i < 1000 ; i++) { Date a = new Data(); ... } 예제 4 ) Date a; for ( int i = 0 ; i < 1000 ; i++) { a = new Date(); ... a = null ; } |
Object의 Instance 값을 재정의한다. API를 충분히 참조한 다음 지원 메소드가 없을 경우, 클래스를 상속 받아 요구에 적합한
메소드를 만드는 방법도 고려할 필요가 있다. 기존 API를 이용한 Object 초기화 방법은 아래 Object 재사용(메소드를 사용한
Object 초기화) 부분 참조.
3. Avoding Garbage Collection(primitive data type 사용)
Date 클래스나 String 클래스의 값들 중 int나 long형으로 표현하여 사용할 수 있는 경우가 있다. 예를 들어
1
2 |
String a = "1492" ; String b = "1997" ; |
Casting하는 작업이 오히려 시간이 더 많이 걸릴 수도 있는 것이다.
클래스의 인스턴스 변수로 기본 데이터형을 사용하면 Object의 크기도 줄어들고 Object 생성 시간도 줄일 수 있다.
4. Object 재사용(Pool Management)
Object 재사용 기법으로 흔히 Pool Management 기법을 사용한다. 이는 임의 갯수의 Object를 미리 생성해두고 이를
Vector 클래스를 사용해서 관리하는 방법이다. 해당 Object를 사용하기 위해서 Pool의 Vector에 있는 Object를 하나 가져오고,
다 사용하고 나면 다시 Pool에 반납한다. 이는 기존에 공개되어 있는 Hans Bergsten의 Connection-Pool의
PoolManger클래스에서 사용되고 있다.(Professional JAVA SErver Programming. 정보문화사. 2000.4.4. 제 9장 연결풀링 부분)
Object Pool을 사용할 경우, 반환되는 Object가 초기화되어 반환되지 않을 경우 다음에 Pool에서 Object를 가져와서 사용하게
되면 문제를 야기할 수 있기 때문에 초기 클래스 Design시 꼼꼼하게 따져봐야 한다.
5. Object 재사용(메소드를 사용한 Object 초기화)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 |
예제 1 ) StringBuffer sb = new StringBuffer(); sb.append( "Hello" ); out.println(sb.toString()); sb = null ; sb = new StringBuffer(); sb.append( "World" ); out.println(sb.toString()); 예제 2 ) StringBuffer sb = new StringBuffer(); sb.append( "Hello" ); out.println(sb.toString()); sb.setLength( 0 ); sb.append( " World" ); out.println(sb.toString()); |
예제2)와 같이 각 클래스에서 지원해 주는 메소드를 사용하여 Object를 재사용할 수 있다.
6. static instance variable의 사용에 의한 단일 클래스 인스턴스 구현
다음은 Hans Bergsten의 PoolManager 클래스 코드 중 일부다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 |
public class PoolManager { private static PoolManager instance; ... private PoolManager() { init(); } public static synchronized PoolManager getInstance() { if (instance == null ) { instance = new PoolManager(); } ... return instance; } private void init() { ... } } |
Object를 조사하고 만약 Object가 있으면 Object를 반환하고 없으면 생성자를 호출해서 PoolManager의 Object를
생성한 후 반환한다. 결국 JVM 내부에는 하나의 PoolManager Object가 존재하게 된다.
단일 클래스 인스턴스 기법을 사용할 경우 하나의 인스턴스를 사용하기 때문에 해당 인스턴스의 무결성 부분이 문제가 된다.
이를 위해 다양한 Synchronization 기법을 사용하게 된다. 아래 I/O 퍼포먼스 개선 부분 참조.
7. clone() 메소드 사용으로 Object 생성에 따른 OverHead를 피함
1
2
3
4
5
6 |
private static int [] data = new int [ 2 ][ 2 ]; int [] someMethod(){ int [] a = ( int []) this .data.clone(); return a; } |
대신 클래스의 생성자를 호출하지 않기 때문에 생성자 호출에 의한 OverHead를 피할 수 있다. clone() 메소드를 사용할 때의
trade-off 문제는 다음 예제를 참조할 것.
1
2
3
4
5
6
7
8
9
10
11
12
13
14 |
static int [] Ref_array1={ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 }; static int [][] Ref_array2={{ 1 , 2 },{ 3 , 4 },{ 5 , 6 },{ 7 , 8 }}; //faster than cloning int [] array1={ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 }; //slower than initializing int [] array1=( int [])Ref_array1.clone(); //slower than cloning int [][] array2={{ 1 , 2 },{ 3 , 4 },{ 5 , 6 },{ 7 , 8 }}; //faster than initializing int [][] array2=( int [][])Ref_array2.clone(); |
8. Method Inline에 의한 method 호출 감소
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 |
예제 1 ) public class InlineMe { int counter = 0 ; public void method1() { for ( int i = 0 ; i < 1000 ; i++) { addCount(); System.out.println( "counter=" + counter); } } public int addCount() { counter = counter + 1 ; return counter; } public static void main(String[] args) { InlineMe im = new InlineMe(); im.method1(); } } |
1
2
3 |
public void addCount() { counter = counter + 1 ; } |
즉 method1()이 실제 수행될 때는 다음과 같이 수행.
1
2
3
4
5
6 |
public void method1() { for ( int i = 0 ; i < 1000 ; i++) { counter = counter + 1 ; System.out.println( "counter=" + counter); } } |
9. 생성자 설계
예제1, 2, 3) 모두 같은 역할을 하는 생성자들로 구성되어 있다. 하지만 퍼포먼스 측면에서 보면 예제3)이 가장 효율적이다.
하지만 클래스를 설계 할 때는 예제1)과 같이 해야 할 때도 있다. 클래스가 요구하는 조건에 따라 생성자의 설계 방법이
다르겠지만, Object를 생성할 때 가능한 생성자를 적게 호출하는 방법을 사용하는 것이 퍼포먼스 면에서 좋다는 것은
당연한 일이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 |
예제 1 ) class SlowFlow { private int x, y; public SlowFlow() { this ( 777 ); } public SlowFlow( int x) { this (x, 778 ); } public SlowFlow( int x, int y){ this .x = x; this .y = y; } } 예제 2 ) class SlowFlow { private int x, y; public SlowFlow() { this ( 777 , 778 ); } public SlowFlow( int x) { this (x, 778 ); } public SlowFlow( int x, int y) { this .x = x; this .y = y; } } 예제 3 ) class SlowFlow { private int x, y; public SlowFlow() { x = 777 ; y = 778 ; } public SlowFlow( int x) { this .x = x; y = 778 ; } public SlowFlow( int x, int y) { this .x = x; this .y = y; } } |
10. "extends" VS "implements"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 |
예제 1 ) extends import java.awt.event.*; import java.awt.*; public class MyWindowAdapter extends WindowAdapter { public void windowClosing(WindowEvent we) { Container source = (Container) we.getSource(); source.setVisible( false ); } } 예제 2 ) implements import java.awt.event.*; import java.awt.*; public class MyWindowListener implements WindowListener{ public void windowClosing(WindowEvent we){ Container sourve=(Container)we.getSource(); source.setVisible( false ); } } public void windowClosed(WindowEvent we){} public void windowActivated(WindowEvent we){} public void windowDeactivated(WindowEvent we){} public void windowIconified(WindowEvent we){} public void windowDeiconified(WindowEvent we){} public void windowOpened(WindowEvent we){} |
초래하는 반면, "extends"의 경우에는 슈퍼 클래스에 정의된 메소드들 중 필요한 메소드만 Overriding 하면 된다.
이 부분은 설계 시 추상 클래스를 사용할 것인지 인터페이스를 사용할 것인지 고려할 것.
11. 클래스 집약
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 |
예제 1 ) public class Person { private Name name; private Address address; } class Name { private String firstName; private String lastName; private String[] otherNames; } class Address { private int houseNumber; private String houseName; private String streetName; private String town; private String area; private String greaterArea; private String country; private String postCode; } |
클래스를 설계할 때 가능한 클래스의 수를 줄여서 동적으로 생성되는 Object의 수를 줄일 수도 있다.
예제1)에서 정의된 세 개의 클래스는 하나의 클래스로 집약될 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14 |
예제 2 ) public class Person { private String firstName; private String lastName; private String[] otherNames; private int houseNumber; private String houseName; private String streetName; private String town; private String area; private String greaterArea; private String country; private String postCode; } |
12. I/O 퍼포먼스 개선
자바에서는 자료를 읽거나 쓰기 위해 Stream을 사용한다. 자바는 두 가지 형태의 Stream을 지원한다.
Reader/Writer와 Input/OutputStream이 그것이다. Reader/Writer는 High-Level의 I/O(예. String)을 지원하고
Input/OutputStream은 Low-Level의 I/O(byte)를 지원한다. 속도 향상을 위해서는 BufferedStream을 사용한다.
BufferedStream을 사용할 경우 버퍼의 기본값은 2k이다. 이 값은 조정될 수 있으나 자료의 용량이 클 경우 메모리가 많이
필요하기 때문에 BufferedStream을 사용할 경우 여러가지 사항을 고려해야 한다.
BufferedStream을 사용하지 않고 I/O를 했을 경우의 예제이다. 370K의 JPEG 파일을 복사하는데 10800ms.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 |
예제 1 ) Simple File Copy public static void copy(String from, String to) throws IOException { InputStream in = null ; OutputStream out = null ; try { in = new FileInputStream(from); out = new FileOutputStream(to); while ( true ) { int data = in.read(); if (data == - 1 ) break ; out.write(data); } in.close(); out.close(); } finally { if (in != null ) in.close(); if (out != null ) out.close(); } } |
BufferedStream을 사용해서 퍼포먼스를 개선한 예제이다. 예제1)과 같은 파일을 복사하는데 130ms.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 |
예제 2 ) Faster File Copy public static void copy(String from, String to) throws IOException { InputStream in = null ; OutputStream out = null ; try { in = new BufferedInputStream( new FileInputStream(from)); out = new BufferedOutputStream( new FileOutputStream(to)); while ( true ) { int data = in.read(); if (data == - 1 ) break ; out.write(data); } } finally { if (in != null ) in.close(); if (out != null ) out.close(); } } |
while 루프를 사용하지 않고 배열을 사용함으로써 퍼포먼스를 개선한 예제. 예제1)과 같은 파일을 복사하는데 33ms.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 |
예제 3 ) Custom Buffred Copy public static void copy(String from, String to) throws IOException { InputStream in = null ; OutputStream out = null ; try { in = new FileInputStream(from); out = new FileOutputStream(to); int length = in.available(); byte [] bytes = new byte [length]; in.read(bytes); out.write(bytes); } finally { if (in != null ) in.close(); if (out != null ) out.close(); } } |
하지만 예제3)은 byte 배열로 선언되는 메모리 버퍼의 크기가 실제 파일의 크기와 동일해야 한다. 이에 따라 두 가지 문제점이
발생할 수 있다. 첫 번째는 파일의 용량이 클 경우 상당한 메모리 낭비를 초래한다는 점이다. 두 번째 문제점은 Copy() 메소드가
수행될 때마다 new byte[]에 의해 버퍼가 새로 만들어진다는 점이다. 만일 파일의 용량이 클 경우 버퍼가 만들어지고
Garbage Collector에 의해 수집될 때 상당한 OverHead를 초래할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 |
예제 4 ) Improved Custom Buffered Copy static final int BUFF_SIZE= 100000 ; static final byte [] buffer= new byte [BUFF_SIZE]; public static void copy(String from, String to) throws IOException { InputStream in= null ; OutputStream out= null ; try { in= new FileInputStream(from); out= new FileOutputStream(to); while ( true ) { synchronized (buffer) { int amountRead = in.read(buffer); if (amountRead == - 1 ) break ; out.write(buffer, 0 , amountRead); } } } finally { if (in!= null ) in.close(); if (out!= null ) out.close(); } } |
예제1)과 같은 파일을 복사하는데 22ms. static buffere의 사용으로 I/O 작업을 수행할 경우 발생할 수 있는 문제점을
해결하기 위해 synchronized block을 사용했다. 비록 synchronization을 사용함에 따라 성능 저하를 초래하지만,
실제 while 루프에 머무는 시간이 극히 짧기 때문에 퍼포먼스에 문제는 없다. 테스트에 의하면 synchronized 버전과
unsynchronized 버전 모두 같은 시간에 수행을 완료했다.
13. Web 환경에서 Caching을 이용한 자바 퍼포먼스 개선
웹 환경에서 Object Caching 기법은 주로 DB나 파일에서 동일한 내용을 가져오는 루틴에서 사용된다.
DBMS에 SQL문을 던지고 받아오는 부분의 내용이 거의 변동이 없을 경우 요청시마다 매번 SQL문을 실행시켜서
결과를 받아오는 것이 아니라 최초 실행된 값을 그대로 반환하는 기법이다. 그리고 시간 Check 기법을 이용해서 특정 시간
경과 후 요청이 들어오면 이전 요청에 의해 수행되어진 값을 갱신해서 반환한다. 결과에 의한 반환값이 메모리에 부담이 되지
않을 정도로 크지 않은 경우, 실시간으로 변경된 정보를 반환값으로 사용하는 루틴이 아닐 경우 유용하게 사용할 수 있다.
14. Perfomance CheckList
- 임시로 사용하기 위해 Object를 생성하는 것을 피해라. 특히 Loop에서...
- 빈번하게 호출되는 메소드에서 Object 생성하는 것을 피하라.
- 가능한 Object를 재사용하라.
- 임시 Object의 생성을 줄이기 위해 데이터 타입 컨버전 메소드를 재정의 하는 방법을 고려하라.
- 메소드 설계 시 데이터를 유지하고 있는 Object를 반환하는 메소드보다 데이터로 채워진 재사용 가능한
Object에 접근하는 메소드를 정의하라.
- String이나 Object를 Integer로 대치하라. Object 비교를 위해 equals() 메소드를 호출하지 말고 기본 데이터
타입의 == 연산자를 사용하라.
- 인스턴스 변수로 기본 데이터 타입을 사용하라.
- 단지 메소드 호출을 위해 Object를 생성하는 것을 피하라.
- String 연속 연산자 (+)를 사용하는 것보다 StringBuffer 클래스를 사용하라.
- 복사본을 생성하는 메소드보다 Object를 직접 수정하는 메소드를 사용하라.
- 생성자는 간단하게... 상속 계층은 얕게 ...
- 인스턴스 변수를 초기화 하는 것은 한 번 이상 하지 말 것.
- 생성자 호출을 피하기 위해 clone() 메소드를 사용할 것.
- 간단한 배열일 경우에는 초기화를... 복합한 배열일 경우에는 clone() 메소드 호출.
- 프로그램 내부에서 Object 생성 시기를 조절해서 Object 생성에 따른 bottlenecks를 없앤다.
- 어플리케이션 내부에서 여분의 시간이 허용된다면 가능한 Object를 빨리 생성하라. 생성된 Object를 내부적으로
유지하고 있다가 요청이 발생하면 할당하라.
- 사용 가능성이 희박하거나, 분산처리에 의해 Object를 생성할 경우 Object는 생성 시기를 늦춰라.