graalvm native build

4 minute read

uzun zamandir linux makinemde graalvm’i default jre/jdk olarak kullaniyorum, diger secenekleri yuklemeyi biraktim. graalvm performans katkisi, polyglot ozelligi 1 2 ve native image build secenekleriyle sadece jvm gelistiricileri icin degil ayni zamanda farkli env’lerde calisan arkadaslar icin de muthis bir secenek.

bu blog yazisinda diger ozelliklerini bir kenara birakip bir scala projesinin native build’ini almak uzerine biraz kafa yoracagiz.

dancing cat

baslamadan soyleyelim, graalvm ile native build aldiginiz zaman uygulamanin icine oyle kocaman bir JVM koymuyor, o dediginiz electron. daha once “ne yapiyor ki? icine java koyuyor iste, ihi” diyen vizyon arkadaslar olmustu, varliklarina uzulduk.

bir is gorusmesinde assignment olarak alip implemente ettigim, daha sonra aktif bir proje haline getirip kendime playground yaptigim kucuk bir link kisaltma servisim var: xurl. aktif olarak guncelliyor, yer yer README.md todo section’a eklemeler yapip cozmeye calisiyorum. kendisini canliya alma gibi bir planim bazi nedenlerden oturu henuz yok.

burdan yola cikarak yeni baslayan yazilimci arkadaslara network yapmak yerine proje yapmayi onermis olalim. bu jestim unutulmasin.

daha once graalvm kullanarak kucuk scala applerinin native buildini almistim, ayni zamanda scala, clojure ve spring-boot projeleri icin bir cok ornek mevcut, ancak ben biraz daha real world bir ihtiyac duydum ki istedigim gibi bir kaynak bulamadim, sonrasinda da bu konuyu xurl ile deneyimlemek istedim. xurl’i typelevel stack, http4s, skunk, redis4cats vs ile gelistirdim, bu anlamda benim gibi fp ile ilgilenen arkadaslar icin real world bir ornek olacagini dusunuyorum.

on bilgi

graalvm ve jdk surumleri: OpenJDK 64-Bit Server VM GraalVM CE 22.0.0.2 (build 11.0.14+9-jvmci-22.0-b05, ... scala surum: 2.13.8 sbt: 1.6.1 ortam: amd ryzen 5 3600x, archlinux xurl ile kullanilan lib’lerin surum bilgileri README.md’de mevcut.

ilk asamada 2.13.7 ile compile etmeyi deneyip basarisiz olmustum. scala’daki bir bug compile olmasina musade etmiyor, ilgili issue. o sirada fix icin PR merge edilmisti, usulca 2.13.8 surumunu bekledim.

reflection

graalvm native image bir app’i compile ederken yuklenen ve kullanilan class’larin ne oldugunu bilmesi gerekiyor, bu blog yazisi bu ihtiyaci guzelce ozetlemis, oneririm. bir reflection conf hazirlayip dinamik yuklenen class’lari, parametrelerini, kullanilan methodlarini burdaki gibi tanimlamamiz lazim. mumkun gibi durmuyor, denemeyin (: bu islemi graalvm ile birlikte gelen native image agent’a birakacagiz.

sbt assembly

projeye sbt-assembly pluginini kurup bir guzel über-JAR aliyoruz, bu über-JAR native image agent kullanirken hem classpath’lerle bizi ugrastirmayacak, hem de uygulama icerisindeki resource file’lari native build’e dahil edecek.

ben soyle kucuk bir configuration yapmisim sbt assembly icin.

uber-jar icin:

sbt assembly

native image agent

yapmamiz gereken is über-JAR appimizi calistirirken agent parametresi gecmek ve hazirlanacak conf icin bir path vermek:

java -agentlib:native-image-agent=config-output-dir=./ci/native-image-configs -jar target/scala-2.13/app-assembly.jar

app’i yurutup terminate ettikten sonra ilgili conf dosyalarinin olustugunu goreceksiniz. yalniz graalvm soyle bir not dusmus doc’ta:

It is advisable to manually review the generated configuration files. Because the agent observes only code that was executed, the resulting configurations can be missing elements that are used in other code paths. It could also make sense to simplify the generated configurations to make any future manual maintenance easier.

bu kismi atlattiktan sonra image build e gecebiliriz (:

sbt native packager

yine native build sirasinda classpath ve diger parametrelerle ugrasmak yerine sbt-native-packager pluginini kullanip bu adimlardan kurtulabiliriz.

native packager’i yukledikten sonra:

addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.7")

native-packager ile gelen GraalVMNativeImagePlugin pluginini projede enable ediyoruz:

.enablePlugins(GraalVMNativeImagePlugin)

sonrasinda project def’i uzerinde yapmamiz gereken conf:

graalVMNativeImageOptions ++= Seq(
  "--allow-incomplete-classpath",
  "--enable-http",
  "--enable-https",
  "-H:+AllowVMInspection",
  "-H:ResourceConfigurationFiles=../../ci/native-image-configs/resource-config.json",
  "-H:ReflectionConfigurationFiles=../../ci/native-image-configs/reflect-config.json",
  "-H:JNIConfigurationFiles=../../ci/native-image-configs/jni-config.json",
  "-H:DynamicProxyConfigurationFiles=../../ci/native-image-configs/proxy-config.json",
),

son dort satir gordugunuz gibi generate edilen reflection ve diger bilgileri iceren json conflarinin parametre olarak gecilmesi ile ilgili, bu kismi daha fazla detaylandirmaya gerek yok.

--allow-incomplete-classpath yer yer bazi type’lar sadece runtime aninda resolve olabiliyor (dinamik yuklenen classlar gibi dusunebilirsiniz), bu gibi durumlar native build sirasinda compiletime hatasi gibi gozukebiliyor, bu parametre ile bu problemi asiyoruz.

http ve https parametrelerini atliyorum.

xurl icin burdaki ozel durum -H:+AllowVMInspection parametresi. xurl’de redis icin redis4cats kutuphanesini kullaniyorum, bu arkadas cats-effect ve fs2 ile gelistirilmis, lettuce adindaki redis java client’i icin bir wrapper. lettuce, tracing ve profiling icin Java Flight Recorder ile calisiyor ve eventlar publish ediyor. bu parametreyi vermezseniz xurl compile oluyor ancak runtime da event handler not found gibi bir hata aliyorsunuz. ozetle bu parametreyle derleme sirasinda JFR destegini enable etmis oluyoruz.

sonuc

ben linux ortamda ubuntu:20.04 base bir dockerfile kullanarak 167mb’lik bir cikti aldim. native build almam 90sn kadar suruyor.

apple m1 icin ise durum maalesef soyle, graalvm’in m1 islemci destegi yok, tebrikler graalvm. ben docker ile build etmeye calistim ancak native-image process’i terminate oldu. UTM ile aarch64 bir ubuntu yukleyip uzerinde docker image’ini build ettim, 158mb’lik bir bir image olustu ve sure 300sn.

app boot suresi inanilmaz kisa, herhangi bir derlenen dil ile gelistirdiginiz app’lerden farki yok. genel gozlemim response time’lar 14-15ms’ten 5ms’lere kadar dustu ancak bunlar dogru kabul edilecebilecek rakamlar degil, bir load test yapip ortalama almak lazim.