GraalVM에 대해서 간단히 소개하고자 한다.
Teawan.Kim님이 블로그에서 자세히 설명해 주셨는데, 그중에 자바 서버 개발자인 본인에게 큰 변화로 느켜지는 몇가지를 나름 정리해보고자한다. Teawan.Kim님 블로그에 관련 자료가 많으니 자세한 내용을 알고 싶은분은 방문하시면 더 많은 도움이 될것 같다.

어려운 부분은 다 빼고 간단히 정리하면 아래와 같다.

GraalVM?


어럽게 생각하면 복잡하고 어렵지만 쉽게 생각하면 그냥 새로 나온 JDK라고 생각하면 된다. 기존 Oracle JDK의 컴파일러 부분이 C++로 작성되어 있다. 이는 유지보수나 기타 개선사항에 한계점까지 와서 컴파일러를 자바로 작성하는 서브 프로젝트로 시작했다고 한다.
현재 나와있는 최신 버전은 OpenJDK 8을 기본으로 만들어진 것으로 JDK 8 소스 컴파일 및 실행까지 잘 동작한다.
그리고 요즘 자바가 트렌드를 따라가지 못한다는 이야기가 많이 나오고 있다. 그 와중에 타 언어들은 최신 트렌드를 수용하면서 입지를 넓히고 있는 중이다. 이런 부분을 감안했는지 최신 트렌드를 수용하는 부분도 추가로 들어갔다.

그럼 무엇이 다른지 큰 꼭지만 알아보자.

이후 JVM은 HotSpotVM을 지칭한다.

(JVM - C2) + Graal = GraalVM

기존 JVM에는 JIT 컴파일러를 가지고 있다. JIT 컴파일러는 Bytecode를 실시간으로 Machine Code로 컴파일하게 된다. 이때 코드를 있는 그대로 컴파일하는 것이 아니라 실행하면서 계속 코드를 최적화하면서 Machine Code를 만들어 낸다. 이런 JIT 컴파일러가 JVM에는 두 개가 존재한다. C1/C2가 그것이다. 코드 최적화보다 빨리 프로그램 로딩에 중점을 두고 있는 C1, 보통 한번 로딩 후 짧은 시간 동작 후 종료하는 클라이언트 프로그램에 적합하다. C2는 로딩 시간보다는 코드 최적화에 좀 더 투자한다(C++ 코드보다 빠른 코드를 만들어 낼 경우도 있단다.). 처음 로딩 시 많은 최적화 작업을 진행함으로 인해서 성능적으로 손실이 있으나, 프로그램이 계속 실행되면서 최적화된 컴파일 코드가 실행됨에 따라 성능이 개선된다. C2는 장시간 동작하는 서버 데몬에 적합하다.

추가로 JIT 컴파일러에 대해서 더 자세한 내용은 아래 링크 참조.
http://www.ittc.ku.edu/~kulkarni/teaching/EECS768/19-Spring/Idhaya_Elango_JIT.pdf

기존에 자바 프로그램을 실행하는 스크립트 코드를 보다 보면 JVM 옵션중에서 아래 코드를 본적이 있을 것이다.

1
2
java -client agent.jar
java -server daemon.jar

이것은 C1/C2 컴파일러를 선택하는 옵션이다. 하지만 최신 JVM은 기본적으로 혼합 모드로 설정되어 있다. 프로그램 초기 로딩시에는 C1으로 빠르게 로딩하고, 이후 C2로 변경하여 최적화된 컴파일 코드로 성능을 개선하게 된다.
먼가 복잡해 보이지만 목표는 하나다. 성능이다. 하지만 JVM의 성능의 개선은 점점 한계점에 도달했다. 더 나은 성능개선을 위해서 GraalVM과 Graal 컴파일러가 만들어졌다.
컨셉은 간단하다. 복잡도가 너무 높아진 C++로 만들어진 JIT 컴파일러로 이 이상의 성능 개선의 의지가 없어 보이자 Java로 다시 만들어서 성능을 개선하면 어떨까이다. 정적 컴파일로(AOT:ahead-of-time compile)만들어진 머신코드로도 성능 개선이 안되는데 JIT로 만들어진 머신코드가 성능이 날까 의심스러울 것이다. 어찌됐던 진행되었고 결과물이 나왔다.
GraalVM은 C2 컴파일러를 Graal이라는 Java로 만든 컴파일러로 교체한다. 그리고 Java 컴파일러를 사용함으로 있어 추가적으로 Runtime과 컴파일러 인터페이스를 JVMCI라는 Java 인터페이스를 추가했다. 이것은 만약 원하다면 Java 컴파일러를 다른 커스텀 자바 컴파일러로 쉽게 바꿀 수 있도록 도와준다.
이렇게 만들어진 GrallVM은 기존 JVM C2보다 20% 빠른 성능을 보여준다고 한다.
과연 그런지 한번 테스트 해보았다. 테스트 프로그램은 학부때 과자로 나왔는 고전 알고리즘 문제인 TSP로 정했다. 새로 만들면 좋겠지만 성능 테스트가 목적인 만큼 그냥 Github 저장소:Travelling Salesman Problem solution. by Sinclert의 코드를 사용하겠다. 입력 데이터는 6X6 매트릭스를 사용했다.

  • OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.222-b10, mixed mode)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$> time java HK_Optimal
Please, introduce the size of the matrix
Path: 0 - 2 - 3 - 4 - 1 - 0. Distance = 83java HK_Optimal 0.07s user 0.04s system 96% cpu 0.113 total
$> time java HK_Optimal
Please, introduce the size of the matrix
Path: 0 - 2 - 3 - 4 - 1 - 0. Distance = 83java HK_Optimal 0.07s user 0.04s system 97% cpu 0.113 total
$> time java HK_Optimal
Please, introduce the size of the matrix
Path: 0 - 2 - 3 - 4 - 1 - 0. Distance = 83java HK_Optimal 0.07s user 0.04s system 96% cpu 0.113 total
$> time java HK_Optimal
Please, introduce the size of the matrix
Path: 0 - 2 - 3 - 4 - 1 - 0. Distance = 83java HK_Optimal 0.07s user 0.04s system 97% cpu 0.113 total
$> time java HK_Optimal
Please, introduce the size of the matrix
Path: 0 - 2 - 3 - 4 - 1 - 0. Distance = 83java HK_Optimal 0.07s user 0.04s system 97% cpu 0.113 total
$> time java HK_Optimal
Please, introduce the size of the matrix
Path: 0 - 2 - 3 - 4 - 1 - 0. Distance = 83java HK_Optimal 0.07s user 0.04s system 96% cpu 0.113 total
  • OpenJDK 64-Bit GraalVM CE 19.1.1 (build 25.222-b08-jvmci-19.1-b01, mixed mode)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$> time java HK_Optimal
Please, introduce the size of the matrix
Path: 0 - 2 - 5 - 4 - 1 - 3 - 0. Distance = 81java HK_Optimal 0.06s user 0.02s system 112% cpu 0.067 total
$> time java HK_Optimal
Please, introduce the size of the matrix
Path: 0 - 2 - 5 - 4 - 1 - 3 - 0. Distance = 81java HK_Optimal 0.06s user 0.02s system 114% cpu 0.065 total
$> time java HK_Optimal
Please, introduce the size of the matrix
Path: 0 - 2 - 5 - 4 - 1 - 3 - 0. Distance = 81java HK_Optimal 0.06s user 0.02s system 113% cpu 0.066 total
$> time java HK_Optimal
Please, introduce the size of the matrix
Path: 0 - 2 - 5 - 4 - 1 - 3 - 0. Distance = 81java HK_Optimal 0.06s user 0.02s system 114% cpu 0.067 total
$> time java HK_Optimal
Please, introduce the size of the matrix
Path: 0 - 2 - 5 - 4 - 1 - 3 - 0. Distance = 81java HK_Optimal 0.06s user 0.02s system 114% cpu 0.065 total
$> time java HK_Optimal
Please, introduce the size of the matrix
Path: 0 - 2 - 5 - 4 - 1 - 3 - 0. Distance = 81java HK_Optimal 0.07s user 0.02s system 98% cpu 0.094 total

결과를 보면 최악의 경우와 평균치 모두 GraalVM이 더 나은 성능을 보인다.

Graal + SubstrateVM = Native App

요즘 인프라 파트에서는 클라우드가 대세다. 그리고 AWS에 람다는 그중 가장 핫한 트렌드 중 하나이다. 이런 환경은 자원과 실행시간이 비용이다. 이런 환경은 적은 비용으로 높은 효율을 내기 위해서는 프로그램이 적은 리소스로 빠른 실행 및 성능을 가져야 한다. 이런 부분을 대응하기 위해서 GraalVM에서는 제시한 기술이 SubstrateVM(SVM)이다.
SVM은 JVM 애플리케이션을 컴파일해서 Native 실행 파일을 만들어 낸다. 가상 머신이 필요 없어지는 것이다.
이렇게 설명했지만 GraalVM에서 실행하는 것보다 느리다. 단순 시작만 빠르다. 그리고 Java은 모든 기술을 지원하지 못한다. 현재로는 가상 머신 없이 빠른 실행이 필요하고 앱을 패킹해서 간단히 제공해야 할 경우 사용하면 좋을 것 같다.

간단하게 NativeApp을 빌드해보면 해보자.

  • Source Code : HelloWorld.java
1
2
3
4
5
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, Graal World!");
}
}
  • 네이티브 이미지 제작툴 다운로드

GraalVM에 포함되어 있는 gu라는 프로그램으로 네이티브 변환 프로그램을 다운받는다.

1
2
3
4
5
6
7
8
9
10
11
12
$>  pwd
/Library/Java/JavaVirtualMachines/graalvm/Contents/Home/bin
➜ ./gu install native-image
Downloading: Component catalog from www.graalvm.org
Processing component archive: Native Image
Downloading: Component native-image: Native Image from github.com
Installing new component: Native Image (org.graalvm.native-image, version 19.1.1)

$> ls -ltr
...
lrwxr-xr-x 1 walker staff 23 9 24 13:41 native-image -> ../jre/bin/native-image
...
  • 컴파일 -> 바이너리 실행파일 생성 -> 실행
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$>  ~ /Library/Java/JavaVirtualMachines/graalvm/Contents/Home/bin/native-image HelloWorld
Build on Server(pid: 84661, port: 64677)

[helloworld:84661] classlist: 20,916.92 ms
[helloworld:84661] (cap): 1,207.04 ms
[helloworld:84661] setup: 1,508.77 ms
[helloworld:84661] (typeflow): 1,328.72 ms
[helloworld:84661] (objects): 1,455.38 ms
[helloworld:84661] (features): 82.32 ms
[helloworld:84661] analysis: 2,897.46 ms
[helloworld:84661] (clinit): 44.36 ms
[helloworld:84661] universe: 109.29 ms
[helloworld:84661] (parse): 107.79 ms
[helloworld:84661] (inline): 532.84 ms
[helloworld:84661] (compile): 1,059.69 ms
[helloworld:84661] compile: 1,811.15 ms
[helloworld:84661] image: 163.12 ms
[helloworld:84661] write: 93.29 ms
[helloworld:84661] [total]: 27,552.46 ms
$>
$> ./helloworld
Hello, Graal World!

Truffle(JS, R, Ruby, Python, LLVM(C, C++, Rust)) = JVM App

요즘 언어 트렌드 중에 중요한 키워드가 Polyglot이다. 쉽게 풀어쓰면 다양한 언어로 프로그램 작성이라고 할 수 있지 않을까 쉽다.
예를 들어 고성능 알고리즘 부분은 C Or C++로 작성하고 나머지 인터페이스 부분은 ReactJS로 작성한다던지가 아닐까!
이런 트렌드를 지원하기 위해서 GraalVM에서는 Truffle 프레임워크를 제공한다. Truffle 프레임워크는 JVM위에서 작동하는 새로운 언어를 쉽게 만들 수 있는 방법을 제공한다.
프로그램 언어를 만들기 위해서는 다양한 기반 기술을 모두 제공한다. 그외에도 실제 환경에서 사용하기위해서는 디버거나 기타 등등 툴들이 필요하다. JVM은 이미 해당 툴들을 보유하고 있고, Truffle이 프로그램 언어를 만들기위한 기반 기술을 제공한다.
이런 기능들을 이용해서 JS, R, Ruby, Python 등등 다양 언어를 기존 만들어져 있는 언어도 GraalVM에서 동작시킬 수 있다. 코드로도 실행할 수 있지만, 별도의 Truffle로 만들어진 인터프리터가 제공되기에 바로 앱자체를 실행할 수도 있다. 더 나아가 Ruby 같은 경우 성능도 기존 네이티브 루비보다 좋다고 한다.

말이 나왔으니 Ruby를 실행 환경 구성하고 코드를 실행해보자.

  • 우선 TruffleRuby를 설치.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$>  pwd
/Library/Java/JavaVirtualMachines/graalvm/Contents/Home/bin
➜ ./gu install ruby
Downloading: Component catalog from www.graalvm.org
Processing component archive: TruffleRuby
Downloading: Component ruby: TruffleRuby from github.com
Installing new component: TruffleRuby (org.graalvm.ruby, version 19.1.1)
...
You may need to install "native-image" component which provide the rebuild tools.

$> ls
...
lrwxr-xr-x 1 walker staff 15 9 24 12:42 ruby -> ../jre/bin/ruby
...
  • 코드 실행.
1
2
3
$>  echo "puts 'Hello World!'" > ~/hello-world.rb
$> ./ruby ~/hello-world.rb
Hello World!

결론

지금까지 간단하게 GraalVM이 이제까지의 JDK과 JVM과 무엇이 다른지 알아보았다. 설명한 내용 이외에도 내용이 더 있지만 크게 느껴지는 부분은 위 3가지를 정리해보았다.
그중 가장 매력적인 부분은 Graal 컴파일러인 것 같다. 요즘 데이터 분석에서 spark+scala를 사용하기 위해서 준비 중이다.
GraalVM에서 Scala코드가 35% 정도 성능 개선이 된다고 한다.

아래 블로그 글을 참조하면 드라마틱한 내용도 있다.

Compiling Scala Faster with GraalVM
https://medium.com/graalvm/compiling-scala-faster-with-graalvm-86c5c0857fa3

본인과 같은 허접한 개발자에게 35%의 성능 개선은 엄청난: 메리트인 것 같다. 서비스 구축 시 적용 고려 대상이다.

이런 GraalVM을 사용하려면 공식사이트에서 CE 배포판을 다운로드하여서 바로 사용할 수 있다. 무료이다. 현재 19.xx 버전 정식 번들을 다운로드 할 수있다.

번들을 설치가 번거로울 경우에 현재 JDK9/10/11 쓰고 있다면 Graal 컴파일러만 사용해 볼 수 있다. 실행 시 아래와 같은 옵션을 주면 C2 컴파일러가 Graal 컴파일러로 변경된다.

1
java -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler

참고사이트