Derinlemesine yazılım eğitimleri için kanalımı takip edebilirsiniz...

.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.
.NET 8 - Minimal API AOT Compilation Template

Ş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
  • Başlatma Hızı
    AOT derlemesi neticesinde kod zaten hali hazırda derlenmiş olacağı için uygulamanın başlatma hızını artırabilir. JIT derlemesi ise kodun başlatılma süresini uzatabilir, çünkü kodun bir kısmı çalışma anında derlenmektedir.
  • Daha Düşük Bellek Kullanımı
    AOT derlemesi, önceden derlenmiş kod nedeniyle daha düşük bellek kullanımı sağlayabilmektedir. JIT ise derleme sırasında ve runtime’da bellek kullanımını bazen istemsiz artırabilmektedir.
  • Güvenlik
    AOT derlemesi sayesinde kaynak ve ara kodun açıkça sunulması gerekmeyeceğinden uygulamanın kod seviyesinde daha iyi korunması söz konusu olabilmektedir.
  • Büyüme
    AOT derlemesi, platforma özgü kod ürettiği için uygulamanın boyutunu artırabilir. Özellikle bu durum, mobil cihazlar ve sınırlı depolama alanına sahip cihazlar için dezavantaj olabilir.
  • Güncelleme Zorluğu
    AOT derlemesi, uygulama güncellemelerini daha zor ve karmaşık hale getirebilir, çünkü yazılım sürecinde sürekli olarak kodun yeniden derlenmesi ve uygulamanın yeniden deploy edilmesi işin doğasında vardır. JIT derlemesi bu açıdan daha esnek olabilmektedir.
  • Bağımlılıklar
    AOT derlemesinde, uygulamanın çalışabilmesi için tüm bağımlılıkların önceden derlenmiş olması gerekir. Bu durum, bağımlılıkların güncellenmesinin daha karmaşık hale gelebileceği anlamına gelebilir.
JIT Compilation
  • Kod Boyutu
    JIT derlemesi, genellikle AOT’ye nazaran daha küçük uygulama boyutlarına yol açmaktadır. Bu da maliyet açısından daha avantajlı bir durum olarak değerlendirilebilir.
  • Dinamik Optimizasyon
    JIT derlemesi, çalışma sırasında platform özelliklerine ve kullanım desenlerine göre kodu AOT’ye nazaran daha iyi optimize edebilir.
  • Güncelleme Kolaylığı
    JIT derlemesi, uygulamada sadece değişen kısımların yeniden derlenmesini sağlayarak süreçteki güncellemeleri daha esnek hale getirebilir.
  • Başlatma Hızı
    JIT derlemesi, derleme süresini çalışma anında gerçekleştireceği için uygulamanın başlatılma süresini uzatabilir.
  • Bellek Kullanımı
    JIT derlemesinde hem kaynak hem de derlenmiş kod bellekte tutulacağı için AOT’ye nazaran, runtime’da daha fazla bellek kullanım durumu söz konusu olabilir..

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..NET 8 - Minimal API Ahead of Time(AOT) Compilation TemplateYa da Visual Studio şablonlarından da aşağıdaki gibi ‘native AOT’ olan template’i seçip, proje oluşturabilirsiniz..NET 8 - Minimal API Ahead of Time(AOT) Compilation Template.NET 8 - Minimal API Ahead of Time(AOT) Compilation TemplateŞ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..NET 8 - Minimal API Ahead of Time(AOT) Compilation Template

.NET 8 - Minimal API Ahead of Time(AOT) Compilation TemplateŞ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…

Bunlar da hoşunuza gidebilir...

2 Cevaplar

  1. 13 Eylül 2023

    […] 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 […]

  2. 03 Ekim 2023

    […] ö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 […]

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir