최적화는 섣불리 진행하면 좋은 결과보다 해로운 결과로 이어지기 쉽다.
따라서, 프로그램이 우선 좋은 아키텍처를 가질 수 있도록 한 이후에나 최적화를 고려해야 한다.
구현상의 문제가 아닌 아키텍처 결함이 성능을 제한하는 상황이라면, 완성된 기본 틀을 변경해야 하기 때문에 유지보수나 개선하기 어려운 시스템이 만들어진다. 따라서, 성능을 염두해두고 설계하도록 하자.
public
타입을 가변으로 만들어 내부 데이터를 변경할 수 있게 만드는 것은, 불필요한 방어적 복사를 유발한다.
컴포지션으로 해결할 수 있음에도 상속 방식으로 설계한 public
클래스는 상위 클래스에 영원히 종속되며 그 성능 제약까지도 물려받게 된다.
인터페이스가 있는데 구현 타입을 사용하는 것은 특정 구현체에 종속되게 하여, 나중에 더 빠른 구현체가 나오더라도 이용하지 못하게 된다.
1번의 대표적인 예시로 java.awt.Component
클래스의 getSize
메서드가 있다.
public abstract class Component implements ImageObserver, MenuContainer,
Serializable
{
public Dimension getSize() {
return size();
}
@Deprecated
public Dimension size() {
return new Dimension(width, height); // 방어적 복사 수행
}
}
아래와 같이 Dimension
을 가변으로 설계했기 때문에 getSize
를 호출하는 모든 곳에서 Dimension
인스턴스를 새로 생성하고 있다. 요즘의 VM
이라면 작은 객체를 몇 개 생성하는게 큰 부담은 아니지만, 수백만 개를 생성해야 한다면 성능에 영향을 줄 수도 있기 때문이다.
public class Dimension extends Dimension2D
implements java.io.Serializable { // 불변이 아니다
public int width;
public int height;
public void setSize(int width, int height) {
this.width = width;
this.height = height;
}
}
jmh
같은 프로파일링 도구는 최적화 노력을 어디에 집중해야 할지 찾는데 도움을 준다.
가장 먼저 살펴볼것은, 개별 메서드의 소비 시간과 호출 횟수 같은 런타임 정보의 제공을 이용해서 알고리즘을 알맞게 골랐는지 확인하는 것이다. 알고리즘을 잘못 골랐다면 다른 저수준 최적화는 아무런 소용이 없어진다.
참고로 자바는 C/C++보다 성능 모델이 덜 정교하다. 프로그래머가 작성하는 코드와 CPU
에서 수행하는 명령 사이의 '추상화 격차'가 크기 때문에 최적화로 인한 성능 변화를 일정하게 예측하기가 어렵기 때문이다.
📚 핵심 정리
좋은 프로그램을 작성하다 보면 성능은 따라오므로 빠른 프로그램을 작성하려 안달하지 않아도 된다. 특히 API, 네트워크 프로토콜, 영구 저장용 데이터 포맷을 설계할 때는 성능을 염두해 두어야 한다. 시스템 구현을 다했다면 성능을 측정해보고 충분히 빠르지 않다면 프로파일러를 사용해 문제의 원인이 되는 지점을 찾아 최적화를 수행하자. 만족할 때까지 반복하고, 모든 변경 후에는 성능을 측정하라.