2012년 1월 26일 목요일

JAVA의 기본적인 최적화 팁


1. 불필요한 캐스팅을 피하자.

신입 개발자의 경우 참조 데이터형을 변환하는 경우 캐스팅을 한다.
대부분의 캐스팅은 몇몇 기본 클래스 타입의 자식클래스로 모든 객체의 타입을 제네릭 형태의 프로그래밍을 할수있다.

그러나 신입 개발자들의 경우 항상 그들이 잘못 캐스팅했다는것을 모르고 VM으로 하여금 불필요한 부하를 만든다.

예를 들어 보자:

1
class MyCastingClass {
2
    public Object myMethod() {
3
        String myString = "Some data";
4
        Object myObj = (Object) myString;
5
        return myObj;
6
    }
7
}

좋다. Object myObj = (Object) myString;  구문을 보면 불필요한 캐스팅하고 있는것을알수있다. string 을 Object 형으로 변환하는것인데, String은 Object의 자식형으로 부모의 성향을 다 가지고 있는데 굳이 캐스팅을 할 필요는 없는것이다.
그리고 캐스팅은 자바 컴파일러의 안전검사를 무시하고 오류를 일으킬수도있다.

개선해 보자:

1
class MyCastingClass {
2
    public Object myMethod() {
3
        return "Some data";
4
    }
5
}

자 이제 깔끔하게 수정이 되었다. 무엇보다 코드가 줄어서 가독성도 향상되었고 컴파일 규칙으로부터 안정적이고 메모리의 오버헤드 발생또한 없앴다. 아주 좋다.

2. 일반적인 하위 표현식의 제거

이것은 일반적인 실수로 같은 하위표현식을 중복해서 사용하는경우가 있다. 

예를 들어보자 :

1
int someNumber = (someValue * number1 / number2) + otherValue;
2
int someNumber2 = (someValue * number1 / number2) + otherValue2;

이 경우 계산을 각각 객체마다 실행하기 때문에 부하가 두배로 들게된다. 그리고 이 경우는 그나마 두번 정의했기때문에 부하가 적을지 몰라도 만약에 점점 사용이 늘어 나게 된다면 더욱 더 부하 발생의 심각도 는 심해질것이다 그렇다면 이러한 것을 수정해 보자.

1
final int constantCalculation = (someValue * number1 / number2);
2
int someNumber = (constantCalculation) + otherValue;
3
int someNumber2 = (constantCalculation) + otherValue2;

아주 좋다. 중복되는 부분을 하나의 변수로 따로 선언하고 final을 선언하여 상수화 했다. 이로써 심플한 최적화가 완료되었다.

3. 상수가 아닌 string 대신에 StringBuffer나 StringBuilder를 이용하자.

String 변수는 immutable 변수이기 때문에 변화가 일어날시 오버헤드를 만들게 된다. 당신이 새로운 행동을 정의할때마다 컴파일러는 인터프리트에게 명령을 내려 변경을 해야하기 때문이다.
String 변수가 변경될때 변수 공간에 새로운 것을 만들어야한다. 그후에도 이러한 경우에도 마찬가지이다.
이 부하는 String 대신에 StringBuffer나 StringBuilder를 사용하여 해결할수 있다.
그럼 예를 들어보자:

1
class MyStringClass {
2
    public void myMethod() {
3
        String myString = "Some data";
4

5
        for (int i = 0; i < 20; i++) {
6
            myString += getMoreChars();
7
        }
8
    }
9
}  

이것은 StringBuffer 클래스를 존재하여야 하는 이유이다.
StringBuffer는 가변적인 객체로써 잦은 길이의 변경이나 추가 들에 용의한 객체를 제공해주는 클래스이다.
StringBuffer와 StringBuilder의 차이는 StringBuffer는 동기화시에 항상 안전성을 보장합니다. 그에 반해 StringBuilder는 동기화서 안정성이 보장되지 않습니다. 
다만 장점으로 보자면 로컬 변수로 사용시 더 좋은 성능을 보여줍니다.
위의 예를 변경시켜보자:

01
class MyCastingClass {
02
    public Object myMethod() {
03
        String myString = "Some data";
04
        StringBuffer myBuffer = new StringBuffer();
05

06
        for (int i = 0; i < 20; i++) {
07
            myBuffer.append(getMoreChars());
08
        }
09
        myString = myBuffer.toString();
10
    }
11
}

StringBuffer 사용하여  컴파일러로 하여금 변동시마다 부하를 걸리게하는것을 해소 할수있게 되었다. 그후, toString()을 이용하여 String으로 변동하여 myString에 삽입함으로써 원래 소스코드의 목적을 훼손하지 않고 처리할수 있게 되었다.

4. 단순한 Boolean 연산자를 사용하자.

만약에 하나의 연산에 두가지 조건을 정의했을경우 단락회로 검사시 첫번째 조건이 참일시 컴파일러는 두번째 조건은 확인하지 않기 때문에 평가하는 시간을 줄일수가 있다.
예를 들어 ||에 의한 or를 사용시에는 첫번째 조건이 참이면 두번째 조건은 연산하지 않기 때문에 속도가 빨라집니다. 그러나 | 를 사용한 or시에는 두가지 조건을 다 연산하기 때문에 || 경우에 반해 속도가 느려집니다. 그와 같이 && 경우도 앞의 문장이 false 인 경우 두번째 조건을 연산하지 않게 됩니다.

그럼 소스코드를 통해서 확인 해 봅시다.

1
class MyShortCircuitClass {
2
    public void myMethod() {
3
        if (someValue.equals("true") | someValue.equals("false")) {
4
            System.out.println("valid boolean");
5
        }
6
    }
7
}

단순하게 참과 거짓을 통해 결과를 출력하는 것이지만, 이 경우에는 두가지 조건을 다 연산을 하게된다. 그렇다면 이것을 수정해보자.

1
class MyCastingClass {
2
    public void myMethod() {
3
        if ("true".equals(someValue) ||"false".equals(someValue)) {
4
            System.out.println("valid boolean");
5
        }
6
    }
7
}

| 를 || 로 변환함으로써 우리가 목표한 바를 달성했다.

의미 
Short circuit?
&&
and
yes
&
and
no
||
or
yes
|
or
no
표를 통해서 앞서의 경우와 유사한 경우를 판단해서 부하를 줄여보자.

5. 단순한 empty확인시에는 equals() 대신에 length()를 사용하자.

몇몇 개발자들은 String의 문자열의 유무를 equals()를 통해서 확인하곤 한다.

이와같이 :
1
class MyStringClass {
2
    public boolean myTestMethod() {
3
        return myString.equals("");
4
    }
5
}
이것의 문제는 equals()를 사용시 높은 부하가 발생하게 된다. 
이 이슈는 equals()를 구현시 기본적으로 같은 객체 클래스를 두개의 객체가 참조하여 테스트를 하게 디자인 되있기 때문에 생기는 것이다.
결론적으로 length()또는 isEmpty()의 경우 단순히 같은 객체 타입으로 참조포인트를 놓고 테스트를 하게된다. 
이것은 단순히 빈값 테스트를 하기에는 효율적이다. 
자 위의 내용을 바꿔보자.

1
class MyCastingClass {
2
    public void myMethod() {
3
        public boolean myTestMethod() {
4
            
// which really does myString.length() == 0 behind the schenes
5
            return myString.isEmpty();
6
        }
7
    }
8
}

length()를 사용하여 컴파일러에게 객체 참조 부하를 주지 않고 string의 빈값을 테스트 할수 있는 효율적인 메서드를 제공하게 되었다.

기억해야한다. 코드의 최적화는 조잡한 트릭과 읽기 힘든 코드가 필요없는데서 발생한다.
종종 단순한 변화가 프로세싱 시간을 줄여주고 코드를 좋게 만들어준다.
위의 경우를 사용하여 코드의 최적화를 해보자.

댓글 없음:

댓글 쓰기