Item 67 - 최적화는 신중히 하라
최적화 격언 👨💻
(맹목적인 어리석음을 포함해) 그 어떤 핑계보다 효율성이라는 이름 아래 행해진 컴퓨터 죄악이 더 많다(심지어 효율을 높이지도 못하면서).
윌리엄 울프
(전체의 97% 정도인) 자그마한 효율성은 모두 잊자. 섣부른 최적화가 만악의 근원이다.
도널드 크루스
최적화를 할 때는 다음 두 규칙을 따르라. 첫 번째, 하지 마라. 두 번째, (전문가 한정) 아직 하지 마라. 다시 말해, 완전히 명백하고 최적화되지 않은 해법을 찾을 때까지는 하지 마라.
M. A. 잭슨
프로그램 설계 시, 고려해야 할 요소 🧐
(1) 빠른 프로그램보다는 좋은 프로그램을 작성하라. 🐢
- 성능 때문에 견고한 구조를 해치는 최적화는 하지말자.
- 좋은 프로그램은 개별 구성 요소의 내부를 독립적으로 설계할 수 있다. - 정보 은닉 원칙
- 시스템의 나머지에 변화를 주지 않고도 내가 원하는 부분에 변화를 줄 수 있다.
(2) 성능을 제한하는 설계를 피하라.
- 설계적인 요소는 완성 후 변경하기가 가장 어렵다.
- API, 네트워크 프로토콜, 영구 저장용 데이터 포맷 등이 대표적이다.
- 완성 후에는 변경이 어렵고, 시스템 성능도 크게 제한할 수 있다.
(3) API를 설계할 때 성능에 주는 영향을 고려하라.
- public 타입을 가변으로 만들면, 불필요한 방어적 복사를 유발할 수 있다.
- 메모리 사용이 증가하고, 프로그램 성능이 저하될 수 있다.
- 가능한 불변 객체를 사용해서 이런 문제를 방지하고, 필요한 경우에만 가변 객체를 사용하자.
- 컴포지션으로 작성할 수 있는 부분을 상속으로 만들면, 성능제약까지 물려받을 수 있다.
- 상속은 코드의 재사용성을 높이지만, 상위 클래스의 특성을 물려받아 변경이 어렵다.
- 반면, 컴포지션을 이용하게 되면 필요한 부분만 선택해서 클래스를 구성할 수 있기 때문에 유연성이 높아지고 성능 최적화에 유리하다.
- 인터페이스가 아닌 구현 타입을 사용하면, 차후 개선된 구현체를 사용하기 어려워진다.
ex) java.awt.Component
클래스의 getSize
메서드
1
2
3
4
5
6
7
8
9
10
11
12
public abstract class Component implements ImageObserver, MenuContainer,
Serializable
{
public Dimension getSize() {
return size();
}
@Deprecated
public Dimension size() {
return new Dimension(width, height); // 방어적 복사 수행
}
}
getSize
메서드는Dimension
인스턴스를 반환한다.
1
2
3
4
5
6
7
8
9
10
11
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;
}
}
Dimension
클래스는 가변으로 설계되었다.getSize
메서드를 호출할 때마다 새로운Dimension
인스턴스가 생성된다. → 성능 저하 유발
1
2
3
4
5
6
7
8
9
public abstract class Component implements ImageObserver, MenuContainer, Serializable {
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
}
- 자바 2에서
getWidth
와getHeigth
메소드를 추가하여 성능 문제를 개선하려 하였다. - 이 두 메소드는 각각 너비와 높이의 기본 타입을 반환하므로,
getSize
를 호출할 때마다 새로운Dimension
객체를 생성하는 것보다 훨씬 효율적이다.
하지만, 기존 클라이언트 코드는 여전히
getSize
메서드를 호출하며, 원래 내린 API 설계 폐해를 감내한다.
(4) 성능을 위해 API를 왜곡하는 건 매우 안 좋은 생각이다.
- 잘 설계된 API는 성능도 좋은 게 보통이다.
- API를 왜곡하도록 만든 성능 문제는 플랫폼이나 아랫단 소프트웨어 개선으로 사라질 수 있다.
(5) 최적화 시도 전후로 성능을 측정하라.
- 시도한 최적화 기법이 성능을 드라마틱하게 높이지 못하거나 심지어 악화시키는 경우가 많다.
- 주 원인은 프로그램에서 시간을 잡아먹는 부분을 추즉하기가 어렵기 때문이다.
- 프로파일링 도구를 이용하여 최적화 할만한 부분을 열심히 탐색해보자.
- 보통 10%의 코드에서 90%의 성능을 사용한다.
자바는 구현 시스템, 릴리즈, 프로세서, 플랫폼 등에 따라 성능 차이가 심하니 잘 측정하면서 최적화 하자.
💡 핵심 정리
- 좋은 프로그램을 작성하면 보통 성능은 따라온다.
- 시스템 설계 시에는 성능을 염두에 두자.
- API, 네트워크 프로토콜, 영구 저장용 데이터 포맷 등이 있다.
- 충분히 빠르면 그걸로 끝이며, 그렇지 않다면 프로파일링 도구를 이용해 원인을 찾자.
- 알고리즘을 잘 살펴보고, 알고리즘을 변경하는 것을 고려하자.
- 다른 작은 요소들은 최적화 해봐야 보통 미미한 효과만 갖는다.
This post is licensed under CC BY 4.0 by the author.