프로젝트 룸(Project Loom)이란 무엇인가?

프로젝트 룸 (project loom)은 JVM 가상머신 기반의 스케줄링 메커니즘을 새로 구현하는 프로젝트이다. 얼랭(erlang)과 마찬가지로 동시성 패턴의 최적화를 목표로 한다

이는 os의 관리하에 있었던 커널스레드 모델에서 가상머신이 관리하는 경량스레드 모델로의 전환으로 이루어진다.

경량스레드라는 용어는 혼란을 줄 수 있는 용어인데 엄밀히 말하면 스레드가 아니기 때문이다. 스레드는 그저 OS가 관리하는 실행 흐름의 단위를 뜻한다. 하나의 어플리케이션에서 여러개의 실행흐름이 동시에 관리되어야 하는 상황이 있다. 네트워크와 통신하거나 유저의 입력을 기다리거나 UI를 새로 렌더링 하는 등 이런 여러개의 실행흐름을 동시에 관리하려는 목적으로 OS레벨에서 관리하는 실행흐름을 스레드 또는 커널스레드라고 한다

그러면 경량스레드는 무엇인가 ? 이 또한 실행흐름의 단위이다. 스레드와 개념적으로는 거의 같다. 즉 여러개의 실행흐름이 동시에 관리되어야 하는 상황에서 사용한다. 다만 이러한 실행흐름이 OS단위에서 관리되지 않고 어플리케이션 레벨에서 관리된다. 자바의 예를 들면 JVM이라는 가상머신이 이러한 실행흐름의 관리자가 된다. 이 가상머신은 OS와 비슷한 개념으로 작동하는데 즉 자체 스케줄러를 별도로 가지게 되며 이 스케줄러에서 관리하는 자체의 실행흐름을 생성 및 관리한다.

이것은 제네레이터라는 개념을 알고 있다면 이해가 쉽다. 가상머신 내부의 스케줄러가 제네레이터를 하나의 실행흐름 단위로 생성하여 생성, 호출, 중지, 재개하기 등의 작업을 종합적으로 컨트롤한다. 이렇게 하면 마치 OS에서 스레드를 사용하여 동시성을 관리하는 것과 거의 유사한 효과를 준다

이러한 경량스레드라는 용어가 OS 스레드와 혼선을 줄 수 있음에도 경량스레드로 불리는 이유는 스레드와 기능적으로 거의 유사하기 때문이다. 이러한 혼선을 피하기 위한 목적으로 화이버(fiber)라는 용어가 고안되었다. 화이버는 경량스레드의 또다른 이름이다. 프로젝트 룸에서 화이버 모델은 컨티뉴에이션이라는 형태로 구현된다.

사실 이전에도 자바 진영에서 화이버 모델을 구현하려는 시도는 있었다. 그린 스레드라는 이름으로도 유명한데 이 모델은 커널 스레드와 경량 스레드를 1:N 관계로 맵핑하여 경량 스레드를 스케줄링한다. 대략 아래와 같다

img

이 모델은 각 커널 스레드별로 별도의 스케줄러를 생성하여 경량스레드를 작업단위로 관리한다

이 모델은 훌륭하지만 한계가 있다. 커널 스레드 단위로만 스케줄링이 가능하다는 점이다. 전체 가상머신 단위의 잡 스케줄링이 불가능하다. 프로젝트 룸은 이것을 실현하기 위한 프로젝트이다.

즉 프로젝트 룸 기반의 경량스레드 모델은 커널 스레드와 경량 스레드를 N:N 관계로 스케줄링한다. 이 스케줄링의 주체는 가상머신이다. 그림으로 표현하면 아래와 같다

project_loom_2

이 모델은 특정한 경량스레드가 실행되어야 할 때 가장 최소의 컨텍스트 스위칭 비용이 발생하는 커널 스레드에게 작업을 위임할 수 있다. 이러한 스레드간의 N:N 모델은 그린스레드의 1:N 모델에 비하여 커널 스레드라는 자원을 훨씬 효율적으로 사용할 수 있다

그리고 이 모델은 기존의 그린스레드가 가졌던 메모리 절감 효과도 그대로 가져온다. 하나의 루틴을 새로 생성한다고 가정해 보자. 이 때 고전적인 스레드 기반의 루틴을 새로 생성한다면 이들 스레드는 OS가 제공하는 커널 스레드이고 이 커널 스레드는 상당한 메모리 공간을 차지한다. 일단 이것은 메모리 효율성 측면에서 좋지않다.

반면 화이버 기반에서는 OS가 아닌 가상머신이 경량스레드를 생성하는데 이 경량스레드는 커널스레드 대비 메타데이터 크기가 약 1/7에서 1/10 수준에 그친다. 아래는 프로젝트 룸의 테크니컬 리드가 말하는 커널스레드와 가상 스레드의 비교 데이터이다.

img

그림의 상단은 스레드가 차지하는 메타데이터 크기를 비교해놓았다. 이와는 별개로 그림 하단에는 컨텍스트 스위칭 시간을 비교해놓았다.

컨텍스트 스위칭이란 활성 스레드가 기존의 작업을 중단하고 유휴 스레드에게 자원을 할당하여 새로운 작업을 시작하는 프로세스를 일컫는데 고전적인 모델에서는 이 컨텍스트 스위칭이 OS레벨에서 발생했다. 즉 컨텍스트 스위칭이 커널스레드 간에 스위칭하는 방식으로 구현되어 있다. 그런데 앞서 살펴봤듯이 커널스레드는 약 200kb 가량의 메타데이터를 가지며 추가로 1메가 가량의 스택 크기도 할당받는다. 이 메타데이터를 스위칭하고 새로운 작업을 준비하는 과정에서 약 1-10마이크로세컨드가 소모된다.

이 시간이 사소해 보일지 모르겠지만 작업이 빈번하게 여러번 전환되는 상황에서는 어느정도 처리속도의 지연이 있다. 반면 가상머신 기반의 컨텍스트 스위칭은 커널스레드에 기반하지 않는다. 이는 가상머신의 경량스레드에 기반하며 따라서 스케줄링되는 작업의 단위는 경량스레드, 즉 화이버이다. 화이버 단위의 컨텍스트 스위칭이 가능한 이유는 OS가 수행했던 스케줄링 작업을 가상머신이 대신 수행하기 때문이다

당연하지만 컨텍스트 스위칭에 소모되는 시간은 스레드가 가진 메타데이터의 크기에 비례한다. 그런 관계로 훨씬 적은 메타데이터를 가진 경량스레드는 기존 컨텍스트 스위칭 대비 약 5~50배 빠른 전환 속도를 보여준다. 기존의 동시성 패턴대비 더나은 퍼포먼스와 더 적은 메모리 소모를 경험할 수 있다

스케줄링의 권한이 OS가 아닌 응용프로그램에 위임될 때는 또다른 장점이 있다. 이것을 코틀린과 비교하여 설명해 보자. 코틀린의 컨티뉴에이션 루틴은 작업이 오래 지속될 시에 개발자가 원하는 타이밍에 작업을 취소되거나 작업순위을 뒤로 미룰 수 없다. 만일 특정한 작업이 자원을 선점하고 있어서 다른 화이버가 자원을 사용할 수 없는 기아상태가 지속된다면 그것이 OS 스케줄러 수준에서 관리된다.

즉 OS스케줄러가 작업을 중단시키거나 작업순위를 뒤로 미루게 된다. 이것은 두가지 문제가 있다. 첫째로 얼마나 시간이 흐른 뒤에 작업을 중단시킬지 응용프로그램 수준에서 관리할 수 없다. 즉 스케줄링이 실행중인 OS에 전적으로 의존해야 하므로 당신이 OS커널 내부의 스케줄러를 수정하지 않는 이상은 실행흐름 관련 커스터마이징이 불가능하다. 리눅스 커널의 스케줄러를 개조하여 사용하는 경우가 있기는 하지만 이것은 상당한 전문지식과 수고로움을 필요로 한다. 두번째 문제는 내가 작업을 도중에 중단하고 싶지 않은 경우에도 OS스케줄러에 의해 강제적으로 재스케줄링이 될 수도 있다는 점이다. 종합하면 내가 원하는 스케줄링 정책을 적용할 수가 없다.

그러나 가상머신 기반의 컨티뉴에이션 루틴은 스케줄링 정책을 개발자가 직접 설정할 수 있다. 만일 더 지연된 시간에 작업을 리스케줄링 하고 싶다면 그렇게 할 수 있고 중도에 리스케줄링을 원하지 않는다면 그렇게 할 수도 있다. 이것은 실행흐름을 실행할 때 데드라인을 설정하여 가능해진다. 가령 withDeadline(30초)라고 설정하여 실행흐름을 시작한다면 이 실행흐름의 최대 실행시간은 30초로 제한된다. 즉 응용프로그램 레벨에서 미세한 스케줄링 정책을 컨트롤할 수 있게 되었다

그 외에 롬은 기존의 비동기함수 처리방식을 훨씬 간단하게 만든다. 이것을 설명하기 앞서 전염성이라는 개념을 이해할 필요가 있다.

예를들어 어떤 메소드가 퓨처(Future)를 반환하면 이를 호출한 다른 메소드도 퓨처를 반환해야 하는데 이러한 방식은 퓨처가 포함된 콜스택을 통틀어 비동기 패러다임을 강제하는 문제를 야기한다. 이것을 함수의 색 문제라고 하는데 동기 함수와 비동기 함수 사이에 거대한 벽이 있어 상호 연동이 쉽지 않은 문제를 말한다. 실제로 개발 시 동기 함수를 작성하고 있는 건지 비동기 함수를 작성하고 있는 건지 계속해서 의식하게 해야 하는 불편함이 발생한다.

함수의 색 문제를 자세히 알고 싶다면 밥 니스트롬(Bob Nystrom)의 당신 함수의 색은 무엇입니까?(What Color is Your Function?) 문서를 참고해 보자.

프로젝트 룸의 테크니컬 리드인 론 프레슬러는 이런 전염성이 기존 스레드 모델의 가장 큰 문제라고 말한다. 만일 가상머신 기반의 경량스레드 모델을 사용한다면 이런 함수의 색 문제로부터 자유롭다. 즉 함수를 작성할 때 이전에 호출했던 함수가 동기인지 비동기인지를 체크할 필요가 없어진다. 그저 비동기 처리용 컨티뉴에이션을 생성하고 그즉시 yield를 수행하여 작업을 잠정 중단한 뒤에 비동기 처리용 컨티뉴에이션에서 비동기 작업이 종료되면 이전에 작업하던 컨티뉴에이션을 다시 호출하여 작업을 재개하면 되기 떄문이다. 따라서 프로젝트 룸이 프로덕션 레벨에 적용된다면 비동기 처리관련 코딩 스타일이 급변하게 되는것은 필연이다. 그리고 이것은 개발자를 더욱 편하게 만들어줄 것이다

그 외에 프로젝트 룸은 꼬리재귀 최적화를 지원한다. 따라서 재귀함수가 꼬리재귀 기반으로 구현되었다면 더 빠른 처리 속도와 적은 메모리 사용이라는 이익을 볼 수 있다. 이로서 퍼포먼스의 눈치를 보지 않고도 꼬리재귀를 구현할 수 있는 환경이 마련되었다


참고자료

론 프레슬러의 프로젝트 룸 소개 동영상 (론 프레슬러는 프로젝트 룸의 테크니컬 리드이다)
https://www.youtube.com/watch?v=fOEPEXTpbJA

자바 공식 문서 (Project Loom: JVM을 위한 화이버와 컨티뉴에이션)
https://cr.openjdk.java.net/~rpressler/loom/Loom-Proposal.html

홈으로