티스토리 뷰

🧑🏻‍💻
그 어떤 핑계보다 효율성이라는 이름 아래 행해진 컴퓨팅 죄악이 더 많다.
자그마한 효율성은 모두 잊자. 섣부른 최적화가 만악의 근원이다.
최적화를 할 때는 다음 두 규칙을 따르라.
첫 번째, 하지마라.
두 번째, (전문가 한정) 아직 하지 마라.
다시 말해, 완전히 명백하고 최적화되지 않은 해법을 찾을 때까지는 하지마라.

 

위 격언들은 자바가 탄생하기 20년 전에 나온 것으로, 최적화의 어두운 진실을 이야기해줍니다.

최적화는 좋은 결과보다는 해로운 결과로 이어지기 쉽고, 섣불리 진행하면 특히 더 그렇습니다.

빠르지도 않고 제대로 동작하지도 않으면서 수정하기는 어려운 소프트웨어를 탄생시키는 것입니다.

성능 때문에 견고한 구조를 희생하지 맙시다.

빠른 프로그램보다는 좋은 프로그램을 작성합시다.

좋은 프로그램이지만 원하는 성능이 나오지 않는다면 그 아키텍처 자체가 최적화할 수 있는 길을 안내해줄 것입니다.

좋은 프로그램은 정보 은닉 원칙을 따르므로 개별 구성요소의 내부를 독립적으로 설계할 수 있습니다.

따라서 시스템의 나머지에 영향을 주지 않고도 각 요소를 다시 설계할 수 있습니다. (아이템 15)

프로그램을 완성할 때까지 성능 문제를 무시하라는 뜻이 아닙니다.

구현상의 문제는 나중에 최적화해 해결할 수 있지만, 아키텍처의 결함이 성능을 제한하는 상황이라면 시스템 전체를 다시 작성하지 않고는 해결하기 불가능할 수 있습니다.

완성된 설계의 기본 틀을 변경하려다 보면 유지보수하거나 개선하기 어려운 꼬인 구조의 시스템이 만들어지기 쉽기 때문입니다.

따라서 설계 단계에서 성능을 반드시 염두에 두어야 합니다.

1. 성능을 제한하는 설계를 피합시다.

완성 후 변경하기가 가장 어려운 설계 요소는 바로 컴포넌트끼리, 혹은 외부 시스템과의 소통 방식입니다.

API, 네트워크 프로토콜, 영구 저장용 데이터 포맷 등이 대표적입니다.

이런 설계 요소들은 완성 후에는 변경하기 어렵거나 불가능할 수 있으며, 동시에 시스템 성능을 심각하게 제한할 수 있습니다.

2. API를 설계할 때 성능에 주는 영향을 고려합시다.

public 타입을 가변으로 만들면, 즉 내부 데이터를 변경할 수 있게 만들면 불필요한 방어적 복사를 수없이 유발할 수 있습니다. (아이템 50)

비슷하게, 컴포지션으로 해결할 수 있음에도 상속 방식으로 설계한 public 클래스는 상위 클래스에 영원히 종속되며 그 성능 제약까지도 물려받게 됩니다. (아이템 18)

인터페이스도 있는데 굳이 구현 타입을 사용하는 것 역시 좋지 않습니다.

특정 구현체에 종속되게 하여, 나중에 더 빠른 구현체가 나오더라도 이용하지 못하게 됩니다. (아이템 64)

API 설계가 성능에 주는 영향은 현실적인 문제입니다.

java.awt.Componet 클래스의 getSize 메서드를 생각해봅시다.

이 API 설계자는 이 메서드가 Dimension 인스턴스를 반환하도록 결정했습니다.

여기에 더해 Dimension은 가변으로 설계했으니 getSize를 호출하는 모든 곳에서 Dimension 인스턴스를 (방어적으로 복사하느라) 새로 생성해야만 합니다.

요즘 VM이라면 이런 작은 객체를 몇 개 생성하는 게 큰 부담은 아니지만, 수백만 개를 생성해야 한다면 이야기가 달라집니다.

이 API를 다르게 설계했을 수도 있습니다.

Dimension을 불변(아이템 17)으로 만드는 게 가장 이상적이지만, getSize를 getWidth와 getHeight로 나누는 방법도 있습니다.

즉, Dimension 객체의 기본 타입 값들을 따로따로 반환하는 방식입니다.

실제로도 자바 2에서는 성능 문제를 해결하고자 Component 클래스에 이 메서드들을 추가했습니다.

하지만 기존 클라이언트 코드는 여전히 getSize 메서드를 호출하며 원래 내렸던 API 설계 결정의 폐해를 감내하고 있습니다.

다행히 잘 설계된 API는 성능도 좋은 게 보통입니다.

그러니 성능을 위해 API를 왜곡하는 건 매우 안 좋은 생각입니다.

API를 왜곡하도록 만든 그 성능 문제는 해당 플랫폼이나 아랫단 소프트웨어의 다음 버전에서 사라질 수도 있지만, 왜곡된 API와 이를 지원하는 데 따르는 고통은 영원히 계속될 것입니다.

신중하게 설계하여 깨끗하고 명확하고 멋진 구조를 갖춘 프로그램을 완성한 다음에야 최적화를 고려해볼 차례가 됩니다.

물론 성능에 만족하지 못할 경우에 한정됩니다.

위에서 소개한 최적화 규칙 두 가지를 상기해봅시다.

여기에 하나를 더 추가한다면 "각각의 최적화 시도 전후로 성능을 측정하라" 입니다.

아마도 측정 결과에 놀랄 때가 많을 것입니다.

시도한 최적화 기법이 성능을 눈에 띄게 높이지 못하는 경우가 많고, 심지어 더 나빠지게 할 때도 있습니다.

주요 원인은 여러분 프로그램에서 시간을 잡아먹는 부분을 추측하기가 어렵기 때문입니다.

느릴 거라고 짐작한 부분이 사실은 성능에 별다른 영향을 주지 않는 곳이라면 여러분의 시간만 허비한 꼴이 됩니다.

일반적으로 90%의 시간을 단 10%의 코드에서 사용한다는 사실을 기억해둡시다.

프로파일링 도구는 최적화 노력을 어디에 집중해야 할지 찾는 데 도움을 줍니다.

이런 도구는 개별 메서드의 소비 시간과 호출 횟수 같은 런타임 정보를 제공하여, 집중할 곳으 물론 알고리즘을 변경해야 한다는 사실을 알려주기도 합니다.

프로그램에 시간이 거듭제곱으로 증가하는 알고리즘이 숨어 있다면 더 효율적인 것으로 교체해야 합니다.

그러면 다른 튜닝을 하지 않아도 문제가 사라질 것입니다.

시스템 규모가 커질수록 프로파일러가 더 중요해집니다.

건초더미에서 바늘찾기와 비슷합니다.

건초더미가 거대해질수록 금속탐지기가 절실해지는 것처럼 말입니다.

그 외에 jmh도 언급해둘 만한 도구입니다.

프로파일링 도구는 아니지만 자바 코드의 상세한 성능을 알기 쉽게 보여주는 마이크로 벤치마킹 프레임워크입니다.

최적화 시도 전후의 성능 측정은 C와 C++ 같은 전통적인 언어에서도 중요하지만, 성능 모델이 덜 정교한 자바에서는 중요성이 더욱 큽니다.

자바는 다양한 기본 연산에 드는 상대적인 비용을 덜 명확하게 정의하고 있습니다.

다시 말해, 프로그래머가 작성하는 코드와 CPU에서 수행하는 명령 사이의 '추상화 격차'가 커서 최적화로 인한 성능 변화를 일정하게 예측하기가 그만큼 더 어렵습니다.

그래서인지 최적화와 관련해 일부만 맞거나 터무니없는 미신들이 떠돌아다닙니다.

자바의 성능 모델은 정교하지 않을뿐더러 구현 시스템, 릴리스, 프로세서마다 차이가 존재합니다.

여러분의 프로그램을 여러 가지 자바 플랫폼이나 여러 하드웨어 플랫폼에서 구동한다면 최적화의 효과를 그 각각에서 측정해야 합니다.

그러다 보면 다른 구현 혹은 하드웨어 플랫폼 사이에서 성능을 타협해야 하는 상황도 마주할 것입니다.

정리

  • 빠른 프로그램을 작성하려 안달하지 말자.
  • 좋은 프로그램을 작성하다 보면 성능은 따라오게 마련이다.
  • 하지만 시스템을 설계할 때, 특히 API, 네트워크 프로토콜, 영구 저장용 데이터 포멧을 설계할 때는 성능에 염두해 둬야 한다.
  • 시스템 구현을 완료했다면 이제 성능을 측정해보라.
  • 충분히 빠르면 그것으로 끝이다.
  • 그렇지 않다면 프로파일러를 사용해 문제의 원인이 되는 지점을 찾아 최적화를 수행하라.
  • 가장 먼저 어떤 알고리즘을 사용했는지를 살펴보자.
  • 알고리즘을 잘못 골랐다면 다른 저수준 최적화는 아무리 해봐야 소용이 없다.
  • 만족할 때까지 이 과정을 반복하고 모든 변경 후에는 성능을 측정하라.
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
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
글 보관함