.NET 8 – Minimal API Ahead of Time(AOT) Compilation Template
Merhaba,
Bu içeriğimizde .NET 8’de, Asp.NET ekibi tarafından üzerinde çalışılmakta olan ana özelliklerden biri olan Ahead of Time(AOT) compilation üzerine odaklanıyor olacak ve bu kavramın ne olduğunu anlayabilmek için öncelikle .NET’te işlerin genel olarak nasıl gittiğine ve AOT ile bu sürecin nasıl farklılaştığına göz atıyor olacağız.
Şimdi her şeyden önce yazı sürecinde kullanacağımız bazı terminolojilere hakimiyet kazanabilmek için aşağıdaki açıklama satırlarına göz gezdirerek mevzuya giriş yapalım.
IL, .NET platformunda kullanılan ve ara dil(intermediate language) olarak nitelendirilen bir konsepttir ve C#, Visual Basic, F# vs. gibi çeşitli dillerde yazılan programların çalıştırılabilmesini sağlamaktadır. Programcılar tarafından bu dillerde yazılan kodlar derlendikten sonra insanlar tarafından okunması oldukça zor olan, düşük seviyeli bir dil olan CIL(Common Intermediate Language) adı verilen ara dile çevrilirler. CIL dilindeki kodlar ise daha sonraki süreçte Just-In-Time(JIT) derleyicisi tarafından hedef platformun makine koduna çevrilmektedirler. Bu sayede .NET programları farklı platformlarda çalıştırılabilmektedirler.
Şimdi bu bilgiler eşliğinde ilk olarak aşağıdaki kodu ele alarak başlayalım;
class Program { public static void Main(string[] args) => Console.WriteLine("Naaptın müdür?"); }
Bu kod, derleyici tarafından işlendikten sonra aşağıdakine benzer bir IL çıktısı üretecektir.
.class private auto ansi beforefieldinit Program extends [System.Runtime]System.Object { // Methods .method public hidebysig static void Main ( string[] args ) cil managed { .custom instance void System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = ( 01 00 01 00 00 ) // Method begins at RVA 0x209d // Code size 11 (0xb) .maxstack 8 IL_0000: ldstr "Naaptın müdür?" IL_0005: call void [System.Console]System.Console::WriteLine(string) IL_000a: ret } // end of method Program::Main .method public hidebysig specialname rtspecialname instance void .ctor () cil managed { // Method begins at RVA 0x20a9 // Code size 7 (0x7) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [System.Runtime]System.Object::.ctor() IL_0006: ret } // end of method Program::.ctor } // end of class Program
Burada görülen ayrıntıların birçoğu önemli değildir. Bizlerin esas odaklanacağı nokta IL’in hala nispeten yüksek seviyede çıktı üretmesidir. Bu talimatları alıp, doğrudan CPU üzerinde çalıştıramazsınız. Ee haliyle bu durumda IL çıktısını assembly koduna dönüştürebilmek için başka bir derleme aşamasına ihtiyacımız vardır. Bunun için de .NET’te JIT compiler varlık göstermektedir.
JIT ile kodu derlediğimizde ortaya aşağıdaki talimatlara benzer sonuçlar çıkmaktadır;
; Core CLR 7.0.823.31807 on x86 Program..ctor() L0000: ret Program.Main(System.String[]) L0000: mov ecx, [0x8fcbf80] L0006: call dword ptr [0x10d377e0] L000c: ret Microsoft.CodeAnalysis.EmbeddedAttribute..ctor() L0000: ret System.Runtime.CompilerServices.NullableAttribute..ctor(Byte) L0000: push esi L0001: push ebx L0002: mov esi, ecx L0004: mov ebx, edx L0006: mov ecx, 0xa13cabc L000b: mov edx, 1 L0010: call 0x063b319c L0015: mov [eax+8], bl L0018: lea edx, [esi+4] L001b: call 0x06380008 L0020: pop ebx L0021: pop esi L0022: ret System.Runtime.CompilerServices.NullableAttribute..ctor(Byte[]) L0000: mov eax, edx L0002: lea edx, [ecx+4] L0005: call 0x06380008 L000a: ret System.Runtime.CompilerServices.NullableContextAttribute..ctor(Byte) L0000: mov [ecx+4], dl L0003: ret System.Runtime.CompilerServices.RefSafetyRulesAttribute..ctor(Int32) L0000: mov [ecx+4], edx L0003: ret
İşte bunlar CPU tarafından gerçekten çalıştırılabilir talimatlardır.
Şimdi düşünürsek eğer, madem ki nihai olarak kodu bizim JIT çıktısına dönüştürmemiz gerekmektedir, o halde neden önce IL çıktısına dönüştürüp, zaman kaybediyoruz ki? Direkt JIT çıktısını hedeflesek derleme açısından daha az maliyetli bir süreç söz konusu olmayacak mı? Olur gibi değil mi…
İşte AOT derlemesi sayesinde ara IL derleme adımını tamamen atlayabilir ve doğrudan kodu CPU’da çalışabilecek assembly koduna dönüştürebiliriz. Tabi öncelikle bunun nasıl yapıldığından öte, derleme sürecindeki bu doğal akımda neden AOT’ye nazaran JIT’in tercih edildiğini ve AOT ile JIT arasındaki avantaj ve dezavantaj durumlarını değerlendirerek konumuza devam etmekte fayda görmekteyim.
AOT Compilation – Avantaj ve Dezavantajları
Ahead of Time(AOT); .NET platformunda, runtime’da uygulama kodunun JIT tarafından derlenmesi yerine bu işlemin önceden yapılarak uygulama performansını artırmak için kullanılan bir tekniktir. JIT derlemesi, uygulamanın başlatılma ve çalıştırma sürelerinde ister istemez gecikmelere sebebiyet verebilmektedir. İşte bizler bu gecikmelere karşın AOT derleme ile kodun önceden derlenmesini sağlayarak gecikmeleri minimize edebilmekte ve uygulamanın daha hızlı başlatılmasını sağlayabilmekteyiz.
Evet, aklınıza gelebileceği üzere yalnızca bir kez başlatılan ve bunun üzerine saatlerce ve hatta günlerce çalışan geleneksel sunucu uygulamalarında başlatma süresinin uzun olması pek önem teşkil etmeyecektir. Ancak AWS Lambda veya Azure Function gibi cloud teknolojilerden istifade eden uygulamalarda ise başlatma süresi oldukça önem arz etmektedir.
Bu platformlardaki uygulamalar her daim ayakta tutulmamaktadırlar, bilakis ilk gelen isteğe karşın function’ın kullanılabilirliğini hazır duruma getirmek için gerekli ön yüklemeler yürütülmekte ve akabinde function başlatılarak, işlevlerini gerçekleştirmeye odaklanılmaktadır. Sonrasında ise belirli bir müddet istek gelmediği taktirde function tekrardan soğuyacak ve bir sonraki gelen ilk istekte bu süreç baştan tekrar ediyor olacaktır. Ee doğal olarak bu tarz uygulamalarda başlatma süresi oldukça önemli bir parametre olmaktadırlar.
İşte bu tarz senaryolara karşın AOT derlemesi biçilmiş kaftandır diyebiliriz. Cloud teknolojilerinin yaygınlaşması ve yapısal olarak yukarıda bahsedilen ortamlarda başlangıç zamanlarının kritik bir parametre olarak karşımıza çıkması, .NET 8’de AOT derlemesinin odak noktası olmasına sebebiyet doğurmuştur diye düşünebiliriz. Ancak buradaki güzellemeler neticesinde AOT’nin her daim JIT derlemesinden daha iyi olduğu kanaatine varmamak gerekmektedir. Yerinde kullanılmadığı taktirde pek çok dezavantaj durumu da söz konusu olabilmektedir.
AOT’nin ana sorunlarından biri, makine kodunun IL’den üretilmiş olana nazaran önemli ölçüde daha büyük olmasıdır. Haliyle bir Asp.NET Core uygulamasını AOT ile derlerseniz, üretilecek dosyaların ortaya çıkacak boyutu kesinlikle IL’dekine oranla çok daha büyük olacaktır.
Bir diğer sorun ise AOT’nin direkt olarak Widows x64 veya Linux arm64 gibi belirli bir platform için assembly kodu oluşturmasıdır. Bu durumda da, IL’in platformlar arası davranışına karşın yalnızca belirtilen platformda çalışma sınırlılığı söz konusu olacaktır.
Tüm bunların dışında, JIT derleyicisinin, kodun üzerinde çalıştığı makine hakkında AOT derleyicisinden daha fazla bilgi sahibi olabilmesi de, potansiyel olarak JIT’in, AOT derleyicisinden daha fazla optimize edilmiş kod üretebileceği anlamına gelecektir ve işte bu da, kimi zamanlarda JIT’in performansının AOT’den daha iyi olabileceğini göstermektedir.
Olayı teorik olarak bu şekilde yorumladıktan sonra kısaca özetlememiz gerekirse eğer;
Avantajlar | Dezavantajlar | |
---|---|---|
AOT Compilation |
|
|
JIT Compilation |
|
|
Muhtemelen tüm bu anlatılanların .NET 8’de gelebilecek olan hangi yenilikle ne gibi bir ilişkisi olabileceğini merak ediyorsunuz. Evet, hak veriyorum. O halde fazla sabrınızı sınamadan .NET 8’deki sadede gelelim.
.NET 8’de AOT Template’i
.NET 8’de, AOT ile hazır derlenme temelinde sunulan Minimal bir API projesi template’i gelmektedir. Bu template, birazdan da göreceğimiz üzere basit bir endpoint konfigürasyon içeriğine sahiptir.
AOT template’inde bir uygulama oluşturmak istiyorsanız dotnet cli üzerinden
dotnet new webapiaot --name <project name>
şeklinde talimatınızı verebilirsiniz.Ya da Visual Studio şablonlarından da aşağıdaki gibi ‘native AOT’ olan template’i seçip, proje oluşturabilirsiniz.Şimdi bu template’te oluşturulan bir uygulamaya göz attığımızda soldaki gibi bir dosya yapılanmasına sahip olacaktır.
Direkt ‘Program.cs’ dosyasını incelersek eğer aşağıdaki gibi bir içerikle bizleri karşılayacaktır;
using System.Text.Json.Serialization; var builder = WebApplication.CreateSlimBuilder(args); builder.Services.ConfigureHttpJsonOptions(options => { options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default); }); var app = builder.Build(); var sampleTodos = new Todo[] { new(1, "Walk the dog"), new(2, "Do the dishes", DateOnly.FromDateTime(DateTime.Now)), new(3, "Do the laundry", DateOnly.FromDateTime(DateTime.Now.AddDays(1))), new(4, "Clean the bathroom"), new(5, "Clean the car", DateOnly.FromDateTime(DateTime.Now.AddDays(2))) }; var todosApi = app.MapGroup("/todos"); todosApi.MapGet("/", () => sampleTodos); todosApi.MapGet("/{id}", (int id) => sampleTodos.FirstOrDefault(a => a.Id == id) is { } todo ? Results.Ok(todo) : Results.NotFound()); app.Run(); public record Todo(int Id, string? Title, DateOnly? DueBy = null, bool IsComplete = false); [JsonSerializable(typeof(Todo[]))] internal partial class AppJsonSerializerContext : JsonSerializerContext { }
Şimdi buradaki kod bloğunu incelersek eğer birkaç ilginç noktayla karşılaşmaktayız.
Bunlardan ilki, 3. satırda bir sonraki içeriğimizde derinlemesine incelemede bulunacağımız CreateBuilder
fonksiyonu yerine gelen CreateSlimBuilder
fonksiyonunun kullanılmasıdır.
Bir diğer nokta ise AOT için gerekli olan 31 ile 35. satır aralığında tanımlanmış olan ‘JsonSerializerContext’ sınıfının 5 ile 8. satır aralığında yapılandırılmasıdır.
Ve son olarak da 12 ile 18. satır aralığında örnek data olması amacıyla abartılı bir dizi tanımında bulunulmasıdır.
Ayrıca uygulamanın .csproj
dosyasına göz atarsak eğer;
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> <RootNamespace>Company.WebApplication1</RootNamespace> <InvariantGlobalization>true</InvariantGlobalization> <PublishAot>true</PublishAot> </PropertyGroup> </Project>
şeklinde olduğunu gözlemliyoruz. Burada dikkat edilmesi gereken esas hususun 10. satırdaki <PublishAot>true</PublishAot>
yapılandırması olduğuna dikkatinizi çekerim.
Tüm bunların dışında uygulamanın endpoint’lerini hızlıca test edebilmemiz için HTTP request’leriyle yapılandırılmış bir .http
dosyası olan ‘example-aot-project.http’ dosyasını da incelersek eğer;
@example_aot_project_HostAddress = http://localhost:5177 GET {{example_aot_project_HostAddress}}/todos/ Accept: application/json ### GET {{example_aot_project_HostAddress}}/todos/1 Accept: application/json ###
şeklinde bir içerikle karşımıza çıkmaktadır. Bu dosyanın kullanımı aşağıdaki görseldeki gibi netice vermektedir.
Şimdi tüm bu hazır konfigürasyonlarla uygulamayı test etmek için derleyip, çalıştırırsak eğer direkt tarayıcı üzerinden response’un soldaki ekran görüntüsünde olduğu gibi geldiğini görebilmekteyiz.
Sizler JIT ile derlenmiş olan bu uygulamanın muadili bir içeriğe sahip başka bir Asp.NET Core uygulamasını, buradaki açılış hızıyla kıyaslayabilir ve gerekli ölçümleri yapabilirsiniz. İnternette yaptığım araştırmalar sonucu bu karşılaştırmayı yapanlarda nereden baksanız başlangıç süresinde 1/8 oranına yakın artış gözlemlendiği söylenmektedir.
İşte yerinde kullanıldığında AOT’nin gücü bu şekilde tezahür edecektir 🙂
Nihai olarak;
İçerik boyunca AOT ile JIT derlemeleri arasındaki mukayeseyi gerçekleştirerek, .NET 8 ile Microsoft’un geleceğe yine performans odaklı bir misyon beslediğini görmüş bulunuyoruz. Tabi ki de bir çiçekle bahar olmayacağı gibi salt AOT ile yapılan derlemelerinde mutlak performans getirmeyeceğini, uygun olmayan durumlarda ise ekstradan maliyetlere sebebiyet verebileceğini de değerlendirmiş bulunuyoruz. Özellikle gelen bu yeniliğin, AWS Lambda ve Azure Function gibi çağdaş cloud çözümlere odaklandığımız çalışmalar da AOT derlemesinin finansal açısından bizlere ne kadar avantaj sağlayabileceğine dair de bir vizyon temeli sağlayabileceğini düşünüyoruz.
O halde hayırlı olsun diyelim 🙂
İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…
2 Cevaplar
[…] da AOT derlemesini gerçekleştirmek istiyorsanız yakın zamanda klavyeye almış olduğumuz Minimal API Ahead of Time(AOT) Compilation Template başlıklı makalemizden de hatırlayacağınız üzere AOT template’i üzerinden […]
[…] önceki .NET 8 – Minimal API Ahead of Time(AOT) Compilation Template başlıklı makalemizde incelediğimiz AOT için olmasa da esasında AOT merkezli düşünce […]