티스토리 뷰

반응형

Real-time Java, Part 1: 실시간 시스템에 자바 사용하기

Real-time Java™ 시리즈 첫 번째 기술자료에서는, 자바를 사용하여 실시간 성능 요구 사항들에 부합하는 시스템을 개발하는 주요 문제점들을 다룹니다. 실시간 애플리케이션 개발이 의미하는 바가 무엇인지, 실시간 애플리케이션의 요구 사항을 맞추기 위해 런타임 시스템들이 어떻게 다루어져야 하는지를 설명합니다. 필자는 표준 기반 기술들을 결합하여 실시간 자바의 문제점들을 해결하는 방법을 여러분에게 소개합니다.

실시간 시스템에서 자바는 여러 가지 이유로 인해 보급되지 못했다. 여기에는 동적 클래스 로딩 같은 본질적인 자바 언어의 디자인에 내재한 성능 문제, 가비지 컬렉터와 네이티브 코드 컴파일 같은 Java Runtime Environment (JRE) 자체의 문제들도 포함된다. Real-time Specification for Java (RTSJ)는 오픈 스팩으로서, 실시간 시스템을 구현하여 자바 언어를 확장시켰다. (참고자료) RTSJ를 구현하기 위해서는 OS, JRE, Java Class Library (JCL)의 지원이 필요하다. 이 글에서는 자바 언어를 사용하여 실시간 시스템을 구현할 때의 문제점을 조명하고 그러한 문제들을 해결할 수 있는 개발 킷과 런타임 환경을 소개한다. 본 시리즈의 후속 기술자료에서는 개념과 기술을 보다 상세히 다루도록 하겠다.

실시간 요구 사항

실시간(Real-time | RT)은 실시간 요구 사항을 갖고 있는 애플리케이션들을 묘사하는데 사용되는 용어이다. 예를 들어, 느린 사용자 인터페이스는 일반 사용자의 기본적인 RT 요구 사항들을 충족시키지 못한다. 이러한 유형의 애플리케이션을 종종 soft RT 애플리케이션이라고 한다. 이 같은 요구 사항은 "애플리케이션은 마우스 클릭에 반응하는데 0.1초 이상 걸리지 않아야 한다."라는 문구로 보다 구체적으로 표현할 수 있겠다. 요구 사항이 충족되지 못하면 이것은 가벼운(soft) 오류이다. 애플리케이션은 계속 실행되고, 사용자는 비록 그다지 행복하지는 않지만 사용은 계속할 수 있다. 반대로, 실시간 요구 사항들을 엄격히 준수해야 하는 애플리케이션들을 hard RT 애플리케이션이라고 한다. 예를 들어, 비행기의 방향타를 제어하는 애플리케이션은 그 어떤 이유로든 지연되어서는 안된다. 그 결과는 엄청나기 때문이다. 타이밍 요구 사항을 지키지 못했을 때의 오류에 대해 애플리케이션이 얼마나 견딜 수 있는가가 RT 애플리케이션에서는 중요하다.

RT 요구 사항의 또 다른 측면은 응답 시간이다. 하드(hard) RT 애플리케이션 또는 소프트(soft) RT 애플리케이션을 작성하는 프로그래머들이 응답 시간 제약을 이해하는 것이 중요하다. 하드 1-마이크로 초 응답성을 필요로 하는 기술은 하드 100-밀리초 응답성에 필요한 기술과는 엄연히 다르다. 실제로, 수십 마이크로 초 이하로 응답 시간을 맞추기 위해서는 커스텀 하드웨어와 소프트웨어의 조합이 필요하다. OS 레이어의 도움도 약간 필요하다.

마지막으로, 강력한 RT 애플리케이션의 디자이너들은 응답 시간 요구 사항들에 부합하는 애플리케이션을 설계하기 위해 중요한 성능 특성을 정량화 할 수 있어야 한다. 예견 할 수 없는 성능은 시스템 기능에 큰 영향을 끼쳐서 애플리케이션의 응답 시간 요구 사항을 맞출 수 없게 하고 애플리케이션을 올바르게 설계하기도 불가능 하다. 대부분의 RT 실행 환경 디자이너들은 광범위한 RT 애플리케이션들의 응답 시간 필요를 맞추기 위해 불가피한 성능 결과를 줄이기 위해 상당한 노력을 기울여야 한다.




위로


RT 자바 애플리케이션의 문제

범용 OS 기반 범용 JVM에서 실행되는 표준 자바 애플리케이션들은 수백 밀리초 레벨로 소프트 RT 요구 사항을 맞출 수 있을 뿐이다. 이 언어의 여러 가지 근본적인 측면 들 즉, 쓰레드 관리, 클래스 로딩, Just-in-time (JIT) 컴파일러 액티비티, 가비지 컬렉션(GC) 때문이다. 이러한 문제들 중 일부는 애플리케이션 디자이너들이 해결할 수 있지만 상당한 작업이 필요하다.

쓰레드 관리

표준 자바는 쓰레드 스케줄링이나 쓰레드 우선 순위를 보장하지 않는다. 정해진 시간으로 이벤트에 응답해야 하는 애플리케이션의 경우 낮은 우선 순위의 쓰레드가 높은 우선 순위의 쓰레드에 앞서 스케줄링 되지 않도록 하는 방법이 없다. 이를 위해서, 프로그래머는 OS가 다른 우선 순위로 실행할 수 있도록 애플리케이션 세트로 분할해야 한다. 이러한 파티셔닝은 이벤트의 오버헤드를 높이고 이벤트들간 통신을 더욱 어렵게 만든다.

클래스 로딩

자바 순응 JVM은 프로그램에 의해 첫 번째로 참조될 때까지 클래스 로딩을 지연시켜야 한다. 클래스 로딩은 클래스가 로딩되는 미디어(디스크 등)의 속도, 클래스 사이즈, 클래스 로더들의 오버헤드에 따라 그 속도가 다르다. 클래스 로딩의 지연은 일반적으로 10 밀리초 정도가 될 수 있다. 수십 또는 수백 개의 클래스들이 로딩되어야 한다면 로딩 시간 자체가 엄청난, 예측할 수 없는 지연 사태를 일으킬 수 있다. 신중한 애플리케이션 디자인을 통해 애플리케이션 시작 시 모든 클래스들을 로딩해야 하지만, 자바 언어 스팩은 JVM이 이 단계를 조기에 수행하지 못하게 하기 때문에 직접 수행되어야 한다.

Stopping the world

역사적으로, 가비지 컬렉션은 일명Stop-the-world (STW)라는 프로세스라고 하는 애플리케이션 프로그램 중지 기간 동안 수행된다. 컬렉션 동안, 활성 객체들은 "루트(root)" 객체들부터 트레이스 되고(정적 필드에 의해 지목되는, 현재 쓰레드 스택에 살아있는 객체들) 사용되지 않는 메모리는 나중에 할당 요청에 사용되도록 여유 리스트(free list)로 스위핑(sweep)된다.

STW 가비지 컬렉터를 사용하면, 애플리케이션 프로그램은 GC를 프로그램 연산의 중지로서 경험한다. 이러한 STW 중지는 시간 제한이 없고 수백 밀리초부터 수초에 이르기까지 매우 다양하다. 중지 시간은 힙 사이즈, 힙에 살아있는 데이터의 양, 컬렉터가 여유 메모리를 요구하는 정도에 따라 다르다.

많은 현대적 컬렉터들은 동시 알고리즘(concurrent algorithm)과 점증적 알고리즘(incremental algorithm) 같은 기술을 사용하여 중지 시간을 줄인다. 이러한 기술들에도 불구하고, GC 중지는 여전히 한정되지 않은 기간 동안 무작위로 발생할 수 있다.

가비지 컬렉션

포인터 안정성, 누수 방지, 커스텀 메모리 관리 툴링 작성의 필요성 제거 등 애플리케이션 개발에 있어서 GC의 장점에 대해서는 여러 문서에서 증명하고 있다. 하지만, GC는 자바 언어를 사용하는 하드 RT 프로그래머들에게 또 다른 좌절의 원천이다. 가비지 컬렉션은 자바 힙이 애플리케이션 요청이 충족될 수 없는 지점에까지 다다를 때 자동으로 발생한다. 애플리케이션도 스스로 컬렉션을 실행할 수 있다.

한편, GC는 자바 프로그래머에게는 대단한 물건이다. C 와 C++ 같은 언어로 메모리를 관리할 필요성에서 생기는 에러들은 진단하기가 가장 어렵다. 애플리케이션이 전개될 때 이 같은 에러들이 없음을 증명하는 것은 큰 도전이 된다. 자바 프로그래밍 모델의 주요 강점들 중 하나는 애플리케이션이 아닌 JVM이 메모리 관리를 수행하고, 이것이 애플리케이션 프로그래머의 짐을 줄여준다는 점이다.

한편, 전통적인 가비지 컬렉터들은 애플리케이션 프로그래머가 예견할 수 없을 정도의 오랜 지연을 가져올 수 있다는 점이다. 수백 밀리초의 지연은 보통이다. 애플리케이션 레벨에서 이러한 문제를 해결하는 유일한 방법은 재사용 되는 객체들을 만들고 자바 힙 메모리가 결코 소진되지 않도록 함으로써 GC를 방지하는 것이다. 다시 말해서, 메모리를 명확하게 관리함으로써 관리형 메모리의 혜택을 포기함으로써 이 문제를 해결할 수 있다. 실제로, 프로그래머들은 JDK나 다른 클래스 벤더들이 제공하는 클래스 라이브러리를 사용할 수 없기 때문에 결국 힙을 가득 채우는 많은 임시 객체들을 만들게 되므로, 이러한 접근 방식은 일반적으로 실패한다.

컴파일

자바 코드를 네이티브 코드로 컴파일 하려면 클래스 로딩에 비슷한 문제가 생긴다. 대부분의 현대적인 JVM은 처음에는 자바 메소드를 인터프리팅 하고, 자주 실행되는 메소드들의 경우 나중에 네이티브 코드로 컴파일 한다. 지연된 컴파일링의 결과로 시작은 빠르고 애플리케이션 실행 동안 수행된 컴파일의 양은 줄어든다. 하지만 인터프리팅 된 코드를 가진 태스크를 수행하는 것과 컴파일 된 코드로 태스크를 수행하는 것은 시간에서 큰 차이가 난다. 하드 RT 애플리케이션의 경우, 컴파일이 발생할 시기를 예견할 수 없기 때문에 애플리케이션의 액티비티를 효율적으로 플래닝 할 수 없다. 클래스 로딩의 경우, Compiler 클래스를 사용하여 애플리케이션 시작 시 프로그래밍 방식으로 메소드를 컴파일 함으로써 이러한 문제를 완화시켰지만, 이 같은 메소드 리스트를 관리하는 일은 지루하고 에러도 많이 만들게 된다.




위로


Real-time Specification for Java

RTSJ는 RT 실행 환경에서 자바 언어의 한계를 해결하기 위해 만들어졌다. RTSJ는 스케줄링, 메모리 관리, 쓰레딩, 동기화, 시간, 클럭, 비동기식 이벤트 핸들링 같은 여러 문제 영역들을 다룬다.

스케줄링

RT 시스템들은 쓰레드가 스케줄링 되는 방식을 엄격히 제어해야 하고 이들을 예견 가능하도록 스케줄링 되는 것을 보증해야 한다. 다시 말해서, 이러한 쓰레드들은 같은 조건으로 스케줄링 되어야 한다. JCL이 쓰레드 우선 순위의 개념을 정의했지만, 전통적인 JVM에서는 우선 순위를 실행하지 않아도 된다. 또한, 비 RT 자바 구현은 round-robin 선점 스케줄링 방식을 예견 불가능한 스케줄링 순서로 사용한다. RTSJ를 사용하여, 진정한 우선 순위와 우선 순위 상속 지원이 되는 고정된 우선 순위 선점형 스케줄러가 RT 쓰레드에 필요하다. 이러한 스케줄링 방식은 높은 우선 순위를 가진 액티브 쓰레드가 늘 실행되고 이것이 자발적으로 CPU를 릴리스 하거나 더 높은 우선 순위 쓰레드에 의해 선점될 때까지 계속 실행한다. 우선 순위 상속은 높은 우선 순위를 가진 쓰레드가 낮은 우선 순위 쓰레드가 보유한 리소스를 필요로 할 때 우선 순위 역전(priority inversion)을 피할 수 있도록 한다. 우선 순위 역전은 RT 시스템에서는 중요한 문제이다. (RT Linux® )

메모리 관리

일부 RT 시스템들은 가비지 컬렉터에서 기인한 지연을 견딜 수 있지만, 많은 경우 지연은 허용되지 않는다. GC 인터럽션을 견딜 수 없는 태스크를 지원하기 위해, RTSJ는 immortalscoped 메모리 영역을 정의하여 표준 자바 힙을 보충한다. 이러한 영역들은 가비지 컬렉터가 힙에 메모리를 비워야 한다면 차단할 필요 없이 태스크가 메모리를 사용할 수 있도록 한다. Immortal 메모리 영역에 할당된 객체들은 모든 쓰레드에 액세스 가능하고 절대로 수집되지 않는다. 절대로 수집되지 않기 때문에 Immortal 메모리는 신중히 사용되어야 하는 유한 리소스이다. Scoped 메모리 영역은 프로그래머 컨트롤 하에 생성 및 파괴될 수 있다. 각 Scoped 메모리 영역은 최대 크기로 할당되고 객체 할당에 사용될 수 있다. 객체들 간 참조 무결성을 보장하기 위해 RTSJ는 하나의 메모리 영역(힙, Immortal, Scoped)에 있는 객체들이 다른 메모리 영역에 있는 객체들을 참조하는 방법을 관리하는 규칙을 정의한다. 더 많은 규칙들이 Scoped 메모리에 있는 객체들이 종료될 때와 메모리 영역이 재사용 될 때를 정의하고 있다. 이러한 복잡성 때문에 Immortal 메모리와 Scoped 메모리 사용은 GC 중지를 견딜 수 없는 컴포넌트로 제한된다.

쓰레드

RTSJ는 RT 작동으로 태스크를 실행의 토대를 제공하는 두 개의 새로운 쓰레드 클래스(RealtimeThreadNoHeapRealtimeThread (NHRT))를 지원한다. 이러한 클래스들은 우선 순위, 주기적 작동, 데드라인이 초과될 때 실행될 수 있는 핸들러를 가진 데드라인, 힙 외의 다른 메모리 영역의 사용을 지원한다. NHRT는 힙에 액세스 할 수 없기 때문에, 다른 유형의 쓰레드와는 달리, NHRT는 GC에 의해 인터럽트나 선점되지 않는다. RT 시스템들은 일반적으로 엄격한 레이턴시 요구 사항을 가진 태스크에 높은 우선 순위로 NHRT를 사용하고, 가비지 컬렉터에서 감당이 될 수 있는 레이턴시 요구 사항을 가진 태스크에는 RealtimeThread를 사용하고, 기타에는 일반 자바 쓰레드를 사용한다. NHRT는 힙에 액세스 할 수 없기 때문에 이러한 쓰레드를 사용할 때에는 주의를 기울여야 한다. 예를 들어, 표준 JCL에서 컨테이너 클래스의 사용도 신중하게 관리되어야 하므로 컨테이너 클래스는 힙에서 임시 객체 또는 내부 객체들을 아무런 이유 없이 만들지 않도록 해야 한다.

동기화

동기화는 RT 시스템 내에서 신중하게 관리되어 높은 우선 순위를 가진 쓰레드가 낮은 우선 순위의 쓰레드를 기다리는 일이 없도록 해야 한다. RTSJ에는 우선 순위 상속 지원이 포함되어 동기화를 관리하고, 쓰레드가 wait-free 읽기 및 쓰기 큐를 통해 동기화 없이 통신할 수 있도록 하는 기능을 제공한다.

시간과 클럭

RT 시스템들은 표준 자바 코드에서 제공되는 Higher-Resolution 클럭을 필요로 한다. 새로운 HighResolutionTimeClock 클래스가 이러한 시간 서비스들을 캡슐화 한다.

비동기식 이벤트 핸들링

RT 시스템들은 비동기식 이벤트를 관리하고 이에 반응한다. RTSJ는 타이머, OS 시그널, 경과된 데드라인, 기타 애플리케이션 이벤트 같은 많은 소스들에서 발생된 비동기식 이벤트를 핸들한다.




위로


IBM WebSphere Real Time

WebSphere Application Server와는 달리, WebSphere Real Time에는 Java Enterprise Edition 애플리케이션 서버가 포함되지 않는다.

RTSJ 구현에는 기반 OS와 JRE 컴포넌트의 지원이 필요하다. 2006년 8월에 출시된 IBM® WebSphere® Real Time(참고자료)에는 RTSJ 호환성과 RT 시스템의 런타임 작동을 향상시키고 애플리케이션 디자이너들이 RT 시스템을 만들기 위해 사용해야 하는 새로운 기술들이 포함되었다. 그림 1은 WebSphere Real Time의 컴포넌트를 묘사한 것이다.


그림 1. WebSphere Real Time 개요도
WebSphere Real Time 개요도

RTSJ 구현이라는 (작은) 세계

리눅스에서 실행되는 두 개의 다른 RTSJ로 TimeSys RTSJ Reference Implementation과 Apogee Aphelion이 있다. Sun의 Java SE Real-time (Java RTS)은 Sparc/Solaris에서 구동된다. (참고자료)

WebSphere Real Time은 IBM의 크로스 플랫폼 J9 기술에 기반하고 있다. 리눅스에 적용된 오픈 소스 RT 패치들이 RT 작동을 지원하는데 필요한 기본적인 RT 서비스들을 제공한다. 매우 향상된 GC 기술은 1-밀리초 중지 시간을 지원한다. JIT 컴파일 역시 높은 우선 순위 작업이 수행될 필요가 없을 때 컴파일이 발생하는 소프트 RT 시나리오에 사용된다. 새로운 Ahead-of-time (AOT) 컴파일 기술(그림 1) 역시 도입되어 JIT 컴파일이 부적절한 시스템에서 높은 RT 성능을 제공한다. 다음 섹션에서는 이러한 각 기술들을 설명하겠다. 본 시리즈의 후속 기술자료에서는 각 기술들이 어떻게 사용되는지를 보다 자세히 설명하도록 하겠다.




위로


RT 리눅스

WebSphere Real Time은 커스터마이징 된, 완전한 오픈 소스 버전의 리눅스에서 실행된다. 여러 가지 변경 사항들이 적용되어 RT 자바용 환경을 만들었다. 완전히 선점 가능한 커널, 쓰레디드 인터럽트 핸들러, high-resolution 타이머, 우선 순위 상속, 강력한 mutex 등을 제공한다.

완전한 선점 가능 커널

RT 자바 쓰레드들은 first-in-first-out 스케줄링 정책과 정적 우선 순위 스케줄링(static priority scheduling)으로 알려진 고정된 우선 순위 스케줄링(fixed priority scheduling)으로 실행된다. 표준 리눅스 커널은 소프트 RT 작동을 제공하고, 높은 우선 순위를 가진 쓰레드가 낮은 우선 순위 쓰레드를 선점하기 위해 얼마나 오래 기다려야 하는지에 대한 상한선은 없지만 시간은 대략 수십 마이크로초로 잡고 있다. RT 리눅스에서, 거의 모든 커널 액티비티는 선점 가능하기 때문에, 낮은 우선 순위 쓰레드가 선점되는데 필요한 시간을 줄이고, 높은 우선 순위를 가진 쓰레드가 실행될 수 있도록 한다. 선점될 수 없는 나머지 중요한 섹션들은 짧고 예견 범위 내에서 수행된다. RT 스케줄링 레이턴시도 향상되었고 수십 마이크로초로 측정된다.

레이턴시를 줄이기 위한 쓰레디드 인터럽트 핸들러

거의 모든 인터럽트 핸들러는 프로세스 정황에서 실행되는 커널 쓰레드로 변환된다. 레이턴시는 더 낮아지고 더욱 예견 가능하다. 핸들러는 사용자가 설정할 수 있고 스케줄도 가능하다. 다른 프로세스와 마찬가지로 선점 및 우선 순위가 정해질 수 있다.

High-resolution 타이머

High-resolution 시간과 타이머는 높은 해상도와 정확성을 제공한다. RT 자바는 High-resolution 슬립과 시간이 정해진 대기 시간 동안 이 기능을 사용한다. 리눅스 High-resolution 타이머는 높은 정밀도의 64-bit 타입으로 구현된다. 시간과 타이머가 Low-resolution 시스템 틱에 의존하는 전통적인 리눅스와는 달리, RT 리눅스는 독립적으로 프로그래밍 가능한 High-resolution 타이머 이벤트를 사용한다. 이는 마이크로초 안에 종료될 수 있다.

우선 순위 상속

우선 순위 상속은 전통적인 우선 순위 역전 문제를 방지하는 기술이다. 그림 2의 위쪽 다이어그램에는 세 개의 쓰레드가 포함된다. high (H), medium (M), low (L) 우선 순위 쓰레드가 하나씩 있다. H와 M이 이벤트가 실행되기를 기다리고 L이 활성이고 잠금을 보유하고 있다. H가 깨어나서 이벤트를 핸들하면 이것은 L을 선점하고 실행을 시작할 것이다. 만약 H가 L이 보유한 잠금을 차단할 경우 어떤 일이 발생할까? H는 L이 잠금을 풀 때까지 진행할 수 없기 때문에, H는 차단되고 L은 실행을 다시 시작한다. M이 이벤트에 의해 실행되면, M은 L을 선점하고 실행될 것이다. 이러한 상황을 우선 순위 역전이라고 한다. H가 M보다 높은 우선 순위를 갖고 있더라도 H를 동결시킬 수 있기 때문이다.


그림 2. 우선 순위 역전과 우선 순위 상속 예제
우선 순위 역전과 우선 순위 상속 예제

RT 리눅스는 우선 순위 상속(우선 순위 렌딩(lending))이라고 하는 정책을 통해 우선 순위 역전을 방지한다. (그림 2의 아래쪽 다이어그램) H가 L이 보유한 잠금을 차단하면, H는 L에게 우선 순위를 주고, 이는 H보다 낮은 우선 순위의 태스크들이 H에 의해 잠금이 해제되기 전까지 L을 선점할 수 없도록 한다. 잠금 해제되면, L의 우선 순위는 원래의 값으로 돌아가서 H가 더 이상 L을 기다리지 않고도 진행할 수 잇도록 한다. 애플리케이션 디자이너는 높은 우선 순위 쓰레드가 낮은 우선 순위 쓰레드가 보유한 리소스를 요구하는 상황을 만들지 않도록 해야 한다. 하지만 이러한 우선 순위 상속 메커니즘으로 우선 순위 역전이 방지된다.

Robust mutex와 rt-mutex

리눅스 pthread mutex는 fast user-space mutex(Futex)에 의해 지원을 받는다. Futex는 커널에 의존하지 않고 무경쟁의 잠금을 획득하는 시간을 최적화 한다. 커널 개입은 경쟁 잠금에만 필요하다. Robust mutex는 잠금을 보유한 애플리케이션이 충돌한 후에 잠금을 적절히 해제하는 문제를 해결한다. 또한, rt-mutex는 우선 순위 상속 프로토콜을 Robust mutex로 확장하여, RT JVM이 pthread 라이브러리를 통해 우선 순위 상속 작동에 의존할 수 있도록 한다.




위로


결정적 가비지 컬렉션(Deterministic garbage collection)

RT 작동을 위한 토대를 제공하는 RT 리눅스 같은 RT OS에서 JVM의 기타 주요 조각들도 구현되어 RT 작동을 나타낼 수 있다. GC는 JVM의 비결정적(nondeterministic) 작동의 큰 소스들 중 하나이지만, 이러한 비결정성(nondeterminism)은 신중한 디자인과 RT 리눅스의 기능을 의존함으로써 완화될 수 있다.

비결정적인 GC 중지 때문에 특정 데드라인 하에 태스크를 완료하는 RT 애플리케이션의 기능에 손상을 입는다. (가비지 컬렉션 참조) 대부분의 GC 구현은 큰 스케일의 느슨한 타이밍 요구 사항을 가진 태스크들만 GC 기술에 의존할 수 있는 상황에 대한 RT 애플리케이션의 레이턴시 목표와 충돌을 빚는다. 이러한 문제에 대한 RTSJ 솔루션은 Immortal 메모리 영역과 Scoped 메모리 영역과 NHRT를 통한 프로그래머 관리형 메모리 할당의 도입이지만, 이러한 솔루션은 자바 애플리케이션 디자이너에게는 큰 고통이 될 수 있다.

WebSphere Real Time에서는 프로그래머들이 원한다면 RTSJ 메모리 영역에 의존할 수 있지만, 이러한 접근 방식은 매우 엄격한 레이턴시 요구 사항들을 가진 태스크에만 권장된다. 1 밀리초로 GC 중지 시간을 견딜 수 있는 태스크를 위해 IBM은 자동 메모리 관리 예견 가능한 성능으로 태스크를 관리할 수 있는 결정적(deterministic) GC 기술을 만들었다.

IBM의 결정적 GC 기술은 두 가지 전제에 기반하고 있다.

  • 어떤 GC 중지도 최대 상한선을 초과하지 않는다.
  • GC는 중지의 수를 제어함으로써 주어진 시간 일정 비율만 소비한다.

이러한 두 가지 전제 조건으로 GC 액티비티를 관리하면 애플리케이션은 RT 목표를 달성할 수 있을 것이다.

Metronome GC

WebSphere Real Time은 Metronome GC를 사용하여 JVM에서 결정적 low-pause-time GC 작동을 수행한다. (참고자료) Metronome GC는 스케줄링에 시간 기반 방식을 사용하는데, 컬렉터와 애플리케이션을 고정된 스케줄에 끼워 넣는다. (가비지 컬렉터의 관점에서 볼 때 애플리케이션은 활성 객체들의 그래프를 시간이 흐르면서 변경하기 때문에 mutator로 알려져 있다.)

할당 비율 대신 시간에 따라 스케줄링을 하는 이유는 애플리케이션 실행 동안 할당이 평등하지 않기 때문이다. 할당에 대한 세금처럼 GC 작업을 완료함으로써, GC 중지의 불평등한 분배가 이루어 질 수 있고 GC 작동에서 결정성 레벨이 줄어든다. 시간 기반 스케줄링을 사용함으로써, Metronome GC는 일관성 있고, 결정적이며 제한된 중지 시간을 이룩할 수 있다. 더욱이, 기존 코드에 대해 어떤 언어 확장이나 수정도 필요 없기 때문에 일반 자바 애플리케이션도 Metronome 코드를 사용할 수 있고 결정성의 효과도 볼 수 있다.

메트로놈은 시간을 뚜렷한 분량으로 나눈다. 약 500마이크로초이지만 1밀리초가 넘지 않는다. GC 작동이나 애플리케이션 작동에 쓰인다. 시간은 매우 짧지만, 여러 분량들이 GC 작동에 쓰인다면 애플리케이션은 RT 데드라인을 위험에 빠트릴 수 있는 긴 중지 시간을 경험할 수도 있다. RT 데드라인을 더욱 잘 지원하기 위해 메트로놈은 GC 작업에 쓰이는 분량을 분해하여 애플리케이션이 최소한의 시간 비율을 받을 수 있도록 한다. 이러한 비율을 사용자가 공급하는 매개변수인 utilization이라고 한다. 시간 간격이 지나면, 애플리케이션에 사용되는 분량은 지정된 Utilization 만큼 되어야 한다. 기본적으로, Utilization은 70%이다. 10-밀리초 타임 윈도우에서, 최소 7 밀리초가 전적으로 애플리케이션에 할당되어야 한다.

사용자는 프로그램 시작 시 Utilization을 설정할 수 있다. 그림 3은 더 긴 시간 동안 애플리케이션 Utilization 예제를 보여주고 있다. 가비지 컬렉터가 실행되는 곳에서 시간 분량에 따라 주기적인 급강하가 보인다. 그림 3의 전체적인 시간을 보면, 애플리케이션 Utilization은 지정된 70% (0.7)를 웃돌고 있다.


그림 3. Utilization 그래프
Utilization 그래프 예제

그림 4는 결정적 GC 중지 시간들이 Metronome 기술과 어떤 관련이 있는지를 보여준다. 작은 부분만 500 마이크로초를 초과하고 있다.


그림 4. GC 중지 시간 히스토그램
GC 중지 시간 히스토그램

개별 GC 중지를 짧게 유지하기 위해, Metronome은 힙과 제휴 메타 구조 내에 write barriers를 사용하여 활성 객체와 죽은 객체들을 트래킹 한다. 활성 객체를 트레이싱 하여 어떤 객체들이 살아있고 어떤 객체들이 재생되어야 하는지를 결정하기 위해서 GC가 필요하다. 이러한 트레이싱 작업은 프로그램 실행 중간에 들어가기 때문에 애플리케이션이 로드나 저장을 실행하는 동안 숨길 수 있는 특정 객체들을 GC가 트래킹 할 수 없다.

활성 객체들을 숨기는 것이 악의적인 애플리케이션 코드의 결과인 것만은 아니다. 애플리케이션은 가비지 컬렉터의 액티비티를 인식하지 못하기 때문에 이러한 일은 일반적이다. 컬렉터가 어떤 객체들도 놓치지 않게 하려면, GC와 VM은 객체들간 링크를 트래킹 해야 한다. 애플리케이션이 저장을 수행하기 전에 실행되는 write barrier가 이 트래킹을 수행한다. write barrier의 목표는 저장으로 인해 활성 객체들이 숨겨질 경우 객체들이 서로 연결된 방식에 대한 변경 사항을 기록하는 것이다. 이러한 write barrier는 성능 및 메모리 풋프린트 오버헤드를 나타낸다.

큰 객체들의 할당은 많은 GC 전략에 있어 문제가 많다. 많은 경우, 힙이 너무 단편화 되어 어레이 같은 하나의 큰 객체를 수용할 수 없다. 결국, 힙을 모으기 위해 긴 중지가 발생하게 되고 많은 작은 여유 메모리 영역들이 큰 여유 메모리 영역으로 합병되어 큰 할당 요청을 충족시키게 된다. Metronome은 arraylets이라고 하는 2-레벨 객체 모델을 사용한다. Arraylet은 큰 어레이들을 더 작은 조각들로 쪼개서 힙을 모으지 않고도 큰 어레이 할당을 쉽게 수행할 수 있도록 한다. Arraylet 객체의 첫 번째 레벨인 spine에는 Leaf라고 하는 어레이의 작은 조각들에 대한 포인터 리스트가 포함되어 있다. 각 Leaf는 같은 크기이고, 이는 어레이의 특정 객체를 찾는 계산을 단순화 하고 컬렉터가 각 Leaf를 할당 할 알맞은 여유 공간을 쉽게 찾을 수 있도록 한다. 어레이를 더 작인 조각들로 나누면 압축할 필요 없이 더 작은 많은 여유 영역들 내에 할당될 수 있다.

가비지 컬렉션의 시작과 중지를 나타내기 위해 GC 사이클 개념을 사용하는 전통적인 STW 가비지 컬렉터와는 달리, Metronome은 GC를 애플리케이션의 수명 기간 동안 연속적인 프로세스로서 수행한다. 애플리케이션 수명 기간 동안 보장되고 많은 GC 작업이 필요하지 않은 상황 보다 조금 높은 Utilization이 보장된다. 여유 메모리는 컬렉터가 애플리케이션에 리턴 할 여유 메모리를 찾을 때 위아래로 변한다.




위로


RT용 네이티브 코드 컴파일

대부분의 현대적인 JVM은 인터프리테이션과 컴파일 된 코드 실행의 결합을 사용한다. 인터프리테이션의 고성능 비용을 줄이기 위해 JIT 컴파일러는 자주 실행되는 코드를 선택하여 CPU의 네이티브 명령어로 직접 트랜슬레이트 되도록 한다. 자바 언어의 동적인 성격으로 인해 이러한 컴파일러는 프로그램이 실행되기 전에 발생되는 단계 보다는 프로그램이 실행하는 것처럼 실행된다. (C++ 또는 Fortran의 경우도 마찬가지다.) JIT 컴파일러는 어떤 코드를 컴파일 할 것인지를 선택하여 컴파일에 걸리는 시간이 코드 성능의 향상에 기여하도록 한다. 이러한 동적인 컴파일 작동의 백미는, 전통적인 JIT 컴파일러가 특정 프로그램의 실행 동안 한 지점에서는 잘 맞는 실행 프로그램의 동적인 특성을 활용하는 다양한 최적화를 사용하지만 실행 동안에는 잘 맞지 않을 수도 있다. 이 같은 최적화는 이러한 특성이 나중에 잘 맞지 않는다고 생각되면 "실행 취소"가 될 수 있다.

전통적인 비 RT 환경에서, 프로그램이 실행하는 동안의 코드 컴파일은 잘 작동된다. 컴파일러의 액션이 애플리케이션 성능에 대부분 투명하기 때문이다. 하지만 RT 환경에서는, JIT 컴파일러는 예견 불가능한 런타임 작동을 가져온다. 하지만 컴파일 된 코드가 가져다 주는 성능 이점은 이러한 환경에서는 여전히 중요하다. 보다 복잡한 태스크들이 짧은 시간 동안 완료되기 때문이다.

WebSphere Real Time은 이러한 두 개의 요구 사항들을 맞추기 위해 두 개의 솔루션들을 도입했다. 첫 번째 솔루션은 JIT 컴파일러를 사용하는 것이다. 낮은 비 RT 우선 순위로 실행하면서 위험한 최적화를 수행하는 것으로 변해간다. 비RT 우선 순위 연산은 컴파일러가 RT 태스크의 실행으로 방해 받지 않도록 OS가 보장한다. 그럼에도 불구하고, 코드 성능이 시간이 흐르면서 변한다는 사실 때문에 하드 RT 환경 보다는 소프트 RT 환경에 더 알맞다.

하드 RT 환경을 위해, WebSphere Real Time은 애플리케이션 프로그램에 AOT 컴파일을 도입했다. JAR 파일로 저장된 자바 클래스 파일들은 간단한 명령행을 통해 Java eXEcutable (JXE) 파일로 사전 컴파일 될 수 있다. 원래 JAR 파일 보다는 이러한 JXE 파일들을 지정함으로써, 애플리케이션 classpath에서 애플리케이션이 호출되어 바이트코드가 인터프리팅 되거나 JIT 컴파일러에 의해 네이티브 코드가 컴파일 되는 것이 아닌, AOT로 컴파일 된 코드가 실행된다. 최초의 WebSphere Real Time 릴리스에서는, AOT 코드를 사용한다는 것의 의미는 JIT 컴파일러가 없다는 의미였고, 이는 두 가지의 주요한 장점을 갖고 있다. 낮은 메모리 소비와 JIT 컴파일 쓰레드나 샘플링 쓰레드에서 오는 동적인 성능 영향이 없다는 것이다.

그림 5는 AOT 코드가 사용될 때 WebSphere Real Time에서 자바 코드가 실행되는 방법을 나타낸다.


그림 5. AOT 코드가 사용되는 방법
AOT 코드가 사용되는 방법

그림 5의 왼쪽 상단부터 시작해 보겠다. 개발자가 자바 개발 프로젝트에서 자바 소스 코드를 클래스 파일로 컴파일 한다. 클래스 파일들은 JAR 파일로 번들 되고, 이는 jxeinajar 툴을 사용하여 AOT-컴파일이다. 이 툴은 JAR 파일의 모든 클래스에 있는 모든 메소드를 컴파일 하거나 가장 중요한 메소드를 나타내는 샘플 JIT 기반 프로그램에 의해 생성된 아웃풋에 기반하여 일부 메소드만 선택적으로 컴파일 할 수 있다. jxeinajar 툴은 JAR 파일에 있는 메소드를 컴파일 하고 원래의 JAR 파일과 AOT 컴파일러에서 생성된 네이티브 코드를 포함하고 있는 JXE 파일을 만든다. JXE 파일은 프로그램이 실행될 때 JAR 파일로 직접 대체된다. JVM이 -Xnojit 옵션으로 호출되면 classpath 상의 JXE 파일에 있는 AOT-컴파일 코드가 로딩된다. (자바 언어의 규칙에 의거함.) 프로그램 실행 동안 JAR 파일에서 로딩된 메소드나 JXE 파일에서 로딩된 컴파일 되지 않은 메소드들이 인터프리팅 된다. JXE에서 로딩된 컴파일 된 메소드는 네이티브 코드로서 실행된다. 그림 5에서, -Xrealtime 명령행 옵션 역시 RT VM이 호출되도록 지정하는데 필요하다. 이러한 명령행 옵션은 WebSphere Real Time에서만 사용할 수 있다.

AOT 코드의 단점

AOT 코드가 보다 결정적인 성능을 보이더라도 단점도 있다. AOT 코드를 저장하는데 사용되는 JXE는 클래스 파일을 저장하고 있는 JAR 파일 보다 훨씬 크다. 네이티브 코드는 클래스 파일에 저장된 바이트코드 보다 덜 응축되기 때문이다. 네이티브 코드 실행에는 코드가 JVM에 바인딩 되는 방식과 예외를 던지는 방법을 기술하는 다양한 보충 데이터를 필요로 한다. 두 번째 단점은 AOT-컴파일 코드가 인터프리팅 된 코드 보다 빠르지만 JIT 컴파일 코드 보다는 훨씬 느리다는 점이다. 마지막으로, 인터프리팅 된 메소드와 컴파일 된 메소드 간 트랜지션 시간이 다른 인터프리팅된 메소드에서 인터프리팅된 메소드를 호출하는데 걸리는 시간이나 컴파일 된 메소드에서 컴파일 된 메소드를 호출하는데 걸리는 시간 보다 오래 걸린다는 점이다. 활성 JIT 컴파일러가 있는 JVM에서 이러한 비용은 트랜지션의 수가 너무 작아서 성능에 영향을 주지 않을 때까지 컴파일 된 코드를 컴파일 함으로써 줄일 수 있다. JIT 컴파일러가 없고 AOT 컴파일 된 코드가 있는 JVM에서, 트랜지션의 수는 JXE로 컴파일 되었던 메소드에 의해 결정된다. 이 같은 이유로, 전체 애플리케이션을 컴파일 하는 AOT와 애플리케이션이 의존하는 자바 라이브러리 클래스를 권장한다. 컴파일 된 메소드의 수를 늘리면 풋프린트를 남기게 된다.

AOT 코드가 JIT 코드 보다 느린 이유는 자바 언어의 특성 때문이다. 자바는 실행 프로그램이 이들을 최초로 참조할 때 컴파일 된다. 프로그램이 실행하기 전에 컴파일 함으로써 AOT 컴파일러는 이것이 컴파일 하는 코드에 의해 참조되는 클래스, 필드, 메소드에 대해 신중하게 된다. AOT-컴파일 코드는 JIT-컴파일 코드 보다 느리다. JIT는 실행 애플리케이션이 이러한 많은 참조들을 컴파일 한 후에 컴파일을 수행한다는 이점을 갖고 있다. 하지만, JIT 컴파일러는 프로그램을 컴파일 하는 시간 균형을 조심스럽게 맞춰야 한다. 이 같은 이유로, JIT 컴파일러는 동급의 최적화로 모든 코드를 컴파일 할 수 없다. AOT 컴파일러는 이러한 한계가 없기 때문에 더욱 적극적인 컴파일 기술을 적용하여 JIT 컴파일러 보다 더 나은 성능을 내기도 한다. 더욱이, AOT는 JIT 컴파일러 보다 더 많은 메소드를 컴파일 할 수 있기 때문에 성능도 더 낫다. 하지만, 일반적으로 AOT-컴파일 코드는 JIT-컴파일 코드 보다 느리다.

비 결정적 성능 효과를 피하기 위해, WebSphere Real Time에서 제공하는 JIT 컴파일러나 AOT 컴파일러는 현대적인 JIT 컴파일러에서 일반적으로 사용되는 공격적인 최적화를 적용하지 않는다. 이러한 최적화는 일반적으로는 성능을 높일 수 있지만 RT 시스템에는 적합하지 않다. 더욱이 RTSJ와 메트로놈 가비지 컬렉터를 지원하기 때문에 전통적인 컴파일러가 수행할 필요가 없는 컴파일 된 코드로의 오버헤드가 생긴다. 이 같은 이유로, RT 환경을 위해 컴파일 된 코드는 비 RT 환경을 위해 컴파일 된 코드 보다 느리다.




위로


앞으로의 방향

RT 자바 환경을 더욱 빠르게 하기 위해 많은 작업들이 진행 중이다. 자바 언어가 RT 애플리케이션 환경에서 성공을 거두기 위해서는,

  • 전통적인 OS에서 실행할 때 더 나은 예견 가능성을 원하는 사용자들에게 RT 기술을 제공한다.
  • 이러한 기술을 보다 쉽게 사용할 수 있도록 한다.

소프트 RT

WebSphere Real Time의 많은 기능들이 전통적인 OS를 기반으로 하는 프로그래머들에게 유용하다. 점증적 GC와 우선 순위 기반 쓰레드는 많은 애플리케이션에서 유용하다. 비록 하드 RT 보다는 소프트 RT에 알맞다. 예측 불가능한 GC 지연 없이 예견 가능한 성능을 제공하는 애플리케이션 서버는 많은 개발자들에게는 매력적인 요인이다. 합리적인 스케줄링 목표를 갖고 높은 우선 순위의 자바 건전성 모니터링 쓰레드를 실행한다면 자바 서버 개발은 더욱 간단해 질 것이다.

RT를 더욱 쉽게

소셜 북마크

mar.gar.in mar.gar.in
digg Digg
del.icio.us del.icio.us
Slashdot Slashdot

자바 언어의 장점을 RT 시스템을 구현하는데 도입하는 것만으로도 개발자들에게는 상당한 이득이 된다. 하지만, 향상의 여지는 많이 남아있고, RT 프로그래밍을 단순화 할 수 있는 새로운 기능들을 지속적으로 평가해야 한다. IBM alphaWorks 사이트에서 실시간 쓰레드 연구 기술을 참조하기 바란다. (참고자료) 코드를 사전 로딩, 사전 초기화, 사전 컴파일 하여 이벤트를 핸들하고, RTSJ의 NHRT 보다 적은 제약으로 가비지 컬렉터와는 독립적으로 코드를 실행함으로써 매우 결정적인 작동을 이룩할 수 있다. 또한 TuningFork이라는 툴링을 통해 JVM에서의 OS 경로를 추적할 수 있고 상세한 성능 분석도 쉽게 얻을 수 있다.



 

참고자료

교육


제품 및 기술 얻기


토론



 

필자소개

Mark Stoodley

Mark Stoodley는 2001년 토론토대학교(University of Toronto)에서 컴퓨터 엔지니어링 박사 학위를 받았고, 2002년에 IBM Toronto Lab에 합류하여 Java JIT 컴파일 기술 분야에서 일했다. 2005년 초반부터 IBM WebSphere Real Time용 JIT 기술에 대해 작업했고, 기존 JIT 컴파일러를 실시간 환경에 적용하는 것과 관련한 작업을 했다. 현재 자바 컴파일 컨트롤 팀의 리더이며, 실행 환경의 코드 컴파일의 효과를 증진하기 위해 노력하고 있다.


Mike Fulton

Mike Fulton은 1989년 사이먼프레이저대학교(캐나다, 브리티시컬럼비아주)를 졸업했다. 전공은 컴퓨터 공학이다. IBM Toronto Lab에 18년 동안 있으면서 테스팅, 코드 개발, 문서화, 서비스, 아키텍처, 성능 분석 관련 작업을 했다. C, C++, 자바, 파서 기술, 디버거, 프로파일러 제품 관련 작업을 했으며, JIT 자바 컴파일의 최적화 분야에서 일했다. 2005년에, 실시간 자바 솔루션을 개발하는 분야로 전향하였지만, IBM zSeries 기술에도 계속 개입하고 있다.


Michael Dawson

Michael Dawson은 1989년 워털루대학교에서 컴퓨터 엔지니어링 학사 학위를, 1991년에는 Queens대학교에서 전기 엔지니어링 석사 학위를 받았다. 보안 컨설팅을 수행하고 EDI 보안 제품을 개발했다. 다양한 플랫폼에서 보안 제품들을 제공하는 개발 팀 리더도 역임했다. 전자 상거래 애플리케이션 개발과 EDI 통신 서비스, 신용 카드 프로세싱, 온라인 경매, 전자 발주서 같은 서비스를 제공하였다. C/C++에서 자바와 J2EE 플랫폼을 사용했다. 2006년에 Michael은 IBM에 입사하여 J9 JVM과 WebSphere Real Time 분야에서 일하고 있다.


Ryan Sciampacone

Ryan Sciampacone은 1997년 칼턴대학교에서 BCS를 받은 후에, VM 구현, JNI API 레이어, Ahead-of-time 컴파일을 포함한 가상 머신 개발에 참여했다. 2002년 이후, J9 JVM의 가비지 컬렉션의 기술 리더이자 수석 아키텍트를 역임했다. JSE 구현에서 사용할 수 있는 확장성 있는 컬렉터 수트의 책임을 맡고 있으며, 메트로놈 컬렉터와 ME 구성 컬렉터에 대한 책임도 맡고 있다.


John Kacur

John Kacur는 Acadia대학교에서 학사 학위를 받고 Brock대학교에서 컴퓨터 공학 학위를 받았다. 우크라이나에서 러시아어를 공부하고, 독일에서는 영어를 가르친 후에, IBM Toronto Lab에서 2000년부터 근무하기 시작했다. 분야는 Java JIT 컴파일러이다. 리눅스 시스템 광신도로 알려져 있다. JIT 외에도, 리눅스 프로파일러 관련 일을 하고 있고 2005년부터 실시간 자바 프로젝트에 참여하고 있다.



출처 http://www.ibm.com/developerworks/kr/library/j-rtj1/index.html
반응형
댓글