Published on

Optimizing Java Chap1

Authors
  • avatar
    Name
    ywj9811
    Twitter

자바 성능: 잘못된 방법

우선 자바 성능 튜닝에 대해서 알아보려고 검색을 할 때는 주의해야 한다.

요즘의 JVM과 다른 과거의 특징에 맞춰 작성된 내용들이 많이 나올지도 모른다.

예를 들어 메소드를 나누지 말고 하나의 큰 덩치로 작성하는게 좋다는 말이 나오기까지 하니까 말이다.

우선 기본적으로 가지고 들어가야 하는 원칙이 있다.

  1. JVM을 더 빨리 작동시키는 마법은 없다.
  2. 자바를 더 빨리 실행하게 만드는 ‘팁, 트릭’ 은 없다.
  3. ‘비밀 알고리즘’ 이런것도 없다.

그러니 차근차근 앞부터 공부하면서 다지도록 하자.

자바 성능 개요

자바는 실용적인 용도로 개발된 언어이다.

그러다 보니 용량과 같은 저수준으로 제어하는 부분을 관리해주어 개발자가 하나하나 관리하지 않아도 되도록 해준다.

이정도는 포기해도 실용적인 것이 우선인 언어였다.

그러다보니 JVM 전반에 걸쳐 관리되는 서브시스템은 그 존재 자체로 JVM 어플리케이션의 런타임 동작에 복잡도를 유발한다.

서브시스템이란? VM(Java Virtual Machine) 내부의 특정 컴포넌트를 나타낼 수 있습니다. 예를 들어, 가비지 컬렉션(GC), JIT(Just-In-Time) 컴파일러, 쓰레드 관리 등

그리고 이러한 JVM 어플리케이션의 성능은 특징이 있다.

성능 측정값이 정규 분포를 따르지 않는 경우가 대부분이라 기초 통계 기법을 사용할 수 없는 것이다.

예를 들어 JVM 어플리케이션에서 특이점은 매우 중요한 의미를 가지고 있을 수 있지만, 측정값을 샘플링을 하게 되면 특이점을 일으킨 중요한 이벤트가 묻혀버릴 수 있다는 것이다. (일부에서만 나타나는 부분이다 보니..)

그리고 성능 측정을 하면서 주의해야할 부분은 최적화를 위해 측정을 하지만, 이러한 측정하는 행위 자체도 오버헤드를 유발하며 너무 잦은 샘플링 혹은 기록은 성능 결과에 영향을 끼칠 수 있다는 것이다.

성능은 실험과학이다

JVM 성능 튜닝은 기술, 방법론, 정량적 측정값, 툴을 망라한 개념으로 아래와 같은 활동을 하면서 원하는 결과를 얻어내는 것이다.

  1. 원하는 결과를 정의한다.
  2. 기존의 시스템을 측정한다.
  3. 요건을 충족시키려면 무엇을 해야한지 정한다.
  4. 개선 활동을 추진한다.
  5. 다시 테스트한다.
  6. 목표가 달성됐는지 판단한다.

3 ~ 6을 반복한다.

이 과정을 통해서 정량적인 일련의 목표가 수립되고 목표를 기록하며 결과물과 제품 일부를 형성하게 된다.

즉, 성능 분석은 통계치에 근거해 적절히 결과를 처리하는 활동이다.

성능 분류 (성능 지표)

성능 지표는 성능 분석의 어휘집이자, 튜닝 프로젝트의 목표를 정량적인 단위로 표현하는 기준이다.

즉, 성능 목표를 정의한 비기능 요건이다.

아래는 그러한 성능 지표이다.

  • 처리율

    처리율은 시스템이 수행 가능한 작업 비율을 나타내는 지표로 일정 시간 동안 완료한 작업 단위 수로 표시한다. (ex 초당 처리 가능한 트랜잭션 수)

    의미있는 값을 얻기 위해서는 다음과 같은 내용을 지켜야 한다.

    1. 수치를 얻은 기준 플랫폼에 대한 내용을 기술해야 한다. (하드웨어 스팩, OS, 소프트웨어 스택 등등…)
    1. 트랜잭션(혹은 작업 단위)는 테스트할 때 마다 동일해야 한다.
    2. 워크로드(작업 할당량) 일정하게 유지해야 한다.
  • 지연

    하나의 트랜잭션을 처리하고 그 결과를 받을때까지 소요된 시간이다.

    이를 종단 시간이라고도 한다.

    이는 보통 워크로드에 비례하는 함수로 그래프가 그려진다.

  • 용량

    시스템이 보유한 작업 병렬성의 총량으로 시스템이 동시 처리 가능한 작업 단위(트랜잭션) 개수이다.

    이는 처리율과 밀접한 연관이 있는데 당연하게도 동시 부하가 증가하면 처리율(그리고 지연)도 영향을 받는다.

    따라서 용량은 어떤 처리율 또는 지연 값을 전제로 가능한 처리량으로 표시한다.

  • 사용률

    시스템 리소스를 활용하는 것에 대해 얼마나 사용하는지를 말하는 것으로 리소스를 놀리지 말고 실제 작업 단위를 처리하는데 사용해야 온당하다.

    사용률은 워크로드에 따라서리소스별로 들쑥날쑥할 수 있다.

    (그래픽 처리, 암호화와 같은 경우에는 CPU 사용률이 100%지만 메모리는 많이 남을 수 있다.)

  • 효율

    처리율을 리소스 사용률로 나눈 것이다.

    즉, 같은 처리율에 더 많은 리소스를 사용하게 되면 효율이 낮은 것이다.

    참고로 대형 시스템에서는 원가 회계 형태로 효율을 측정하는 방법도 있다. (유지하기 위한 직/간접 비용을 사용)

  • 확장성

    리소스를 투입한 만큼 처리율이 얼마나 비례해서 증가하냐에 대한 것으로 리소스 추가에 따른 처리율 변화를 통해 확장성을 알 수 있다.

    만약 서버 클러스터 기반으로 구축된 시스템에서 클러스터를 2배 확장했을 때 처리량도 2배로 늘어난다면 이 시스템은 완벽한 선형 확장 이라고 한다.

  • 저하

    요청의 개수가 증가 or 요청 접수 속도 증가 등등 어떤 방식으로든 시스템에 부하가 증가하게 되면 지연 처리율 측정값에 변화가 생기게 된다.

    이때 시스템이 풀 가동된 상태에서 처리율이 더는 늘어나지 않고 지연이 증가하는 양상을 띄게 되면 이를 부하 증가에 따른 저하라고 한다.

이렇게 7가지의 측정값들은 서로 어떤 방식으로든 연결이 되어있다.

하지만 어떻게 상호 관계가 이루어지는가 이것은 시스템의 상태에 따라서 다르다.

예를 들어 일반적으로 시스템이 풀가동 되어있을 때 부하가 증가하면 사용률은 감소할 것이고 시스템이 여유있을 때 부하가 증가하면 사용률이 크게 변하지 않을 것이다.

다른 예시를 들면 확장성과 저하는 부하가 증가할 때 당연히 반영이 되는 지표로 부하가 증가했는데 확장성이 떨어진다면 저하가 생길 것이다.

하지만 예외적인 경우도 있는데 오히려 부하가 증가할 때 일부 리소스를 집중적으로 사용하게 되면 지연이 감소하게 되는 경우도 있다.

이는 JIT 컴파일러가 좋은 예가 될 수 있는데,

JIT 컴파일러란 프로그램이 실행되는 동안 기계어로 컴파일하는 방식으로 먼저 코드의 일부분을 인터프리티드하면서 이 코드가 자주 사용된다면 기계어로 컴파일해두어 이후에도 그대로 사용할 수 있도록 하여 성능을 높이는 것이다.

인터프리티드란 해석 모드로 간단히 소개되어 있는데, 이는 소스코드를 한줄씩 읽어서 이를 바로 실행하는 방식으로 실행 시점에서 실시간으로 기계어로 번역하여 실행하는 방식이다.

JIT 컴파일을 사용할 때 부하가 적으면 충분히 많은 요청이 오지 않아서 핵심 메소드가 기계어로 번역되어 이후에도 사용하는 방식이 아닌 매번 인터프리티드 방식으로 실행되게 되어

오히려 부하가 많아서 핵심 메소드가 기계어로 번역되어 이후에도 사용하는 방식보다 더 비효율적일 수 있다.

이 외에도 워크로드에 따라서도 천차만별로 다를 수 있다.

성능 그래프 읽기

몇가지 자주 등장하는 패턴에 대해서 알아보도록 하자.

  1. 성능 엘보

    이는 부하가 증가하면서 예기치 않게 저하(지연)가 발생하는 그래프이다.

    Alt text
  2. 준-선형적(near-linear) 확장

    클러스터에 장비를 추가함에 따라 거의 선형적으로 처리율이 확장되는 케이스

    이는 운이 아주 좋은 케이스로 환경이 극단적으로 순조로운 경우 나타난다.

  3. 암달의 법칙

    진 암달이 발표한 법칙으로 암달에 따르면 근본적으로 확장성에는 제약이 따른다는 것이다.

    Untitled

    이는 확장성에는 근본적으로 제약이 따른다는 것이다.

    워크로드에 순차 실행되어야 하는 것이 하나라도 있다면 선형 확장은 처음부터 불가능하다.

이외에도 몇가지 예시를 살펴보면 JVM의 가비지 컬렉터 시스템의 메모리 사용 패턴은 건강한 상태에서도(부하가 별로 없는) 증가하다가 내렸다가 반복하는 톱니 모양을 나타낸다.