Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Bu makalede.NET 10 için .NET çalışma zamanındaki yeni özellikler ve performans geliştirmeleri açıklanmaktadır. Önizleme 5 için güncelleştirildi.
Dizi arabirimi yöntemi sanallaştırmadan çıkarma
.NET 10 için odak alanlarından biri, popüler dil özelliklerinin soyutlama ek yükünü azaltmaktır. Bu hedefin peşinde, JIT'nin yöntem çağrılarını devirtualize etme yeteneği, dizi arabirim yöntemlerini kapsayacak şekilde genişlemiştir.
Bir dizi üzerinde döngü gerçekleştirmenin tipik yaklaşımını göz önünde bulundurun:
static int Sum(int[] array)
{
int sum = 0;
for (int i = 0; i < array.Length; i++)
{
sum += array[i];
}
return sum;
}
Bu kod yapısı, JIT için kolayca optimize edilebilir çünkü değerlendirilmesi gereken sanal çağrılar yoktur. Bunun yerine, JIT dizi erişimindeki sınır denetimlerini kaldırmaya ve .NET 9 eklenendöngüsü iyileştirmelerini uygulamaya odaklanabilir. Aşağıdaki örnek bazı sanal çağrılar ekler:
static int Sum(int[] array)
{
int sum = 0;
IEnumerable<int> temp = array;
foreach (var num in temp)
{
sum += num;
}
return sum;
}
Temel koleksiyonun türü açıkça belirlenmiştir ve JIT'nin bu kod parçacığını önceki haline dönüştürmesi gerekir. Ancak, dizi arabirimleri "normal" arabirimlerden farklı şekilde uygulanır, böylece JIT bunları nasıl devirtualize ettiğini bilmez. Bu, foreach
döngüsündeki numaralandırıcı çağrılarının sanal kaldığı ve satır içi ve yığın ayırma gibi birden çok iyileştirmeyi engellediği anlamına gelir.
.NET 10'dan başlayarak JIT, satır içi dizi arabirim yöntemlerini devirtualize edebilir. Bu, .NET 10 soyutlama planlarında açıklandığı gibi, uygulamalar arasında performans eşliği elde etmek için birçok adımın ilkidir.
Dizi numaralandırmasının soyutlamadan arındırılması
Numaralandırıcılar aracılığıyla dizi yinelemesinin soyutlama ek yükünü azaltmaya yönelik çalışmalar JIT'nin satır içi, yığın ayırma ve döngü kopyalama yeteneklerini geliştirdi. Örneğin, IEnumerable
aracılığıyla dizileri listeleme yükü azalır ve koşullu kaçış analizi artık belirli senaryolarda numaralandırıcıların yığın ayırmasını sağlar.
Geliştirilmiş kod düzeni
.NET 10'daki JIT derleyicisi, daha iyi çalışma zamanı performansı için yöntem kodunu temel bloklar halinde düzenlemeye yönelik yeni bir yaklaşım sağlar. Daha önce JIT, ilk düzen olarak programın akış grafiğinin ters sıralı (RPO) geçişini ve ardından yinelemeli dönüşümleri kullanıyordu. Etkili olsa da, bu yaklaşımın dallanmayı azaltma ile sıcak kod yoğunluğunun artırılması arasındaki dengeleri modelleme konusunda sınırlamaları vardı.
.NET 10'da JIT, blok yeniden sıralama sorununu asimetrik Seyahat Eden Satıcı Sorununun bir indirgeme olarak modelleyerek optimuma yakın bir yolu bulmak için 3-opt buluşsal yöntemi uygular. Bu optimizasyon, sıcak yol yoğunluğunu artırır ve dal uzaklıklarını azaltarak çalışma süresi performansını artırır.
AVX10.2 desteği
.NET 10, x64 tabanlı işlemciler için Gelişmiş Vektör Uzantıları (AVX) 10.2 desteği sağlar. System.Runtime.Intrinsics.X86.Avx10v2 sınıfında bulunan yeni iç bileşenler, uygun donanım kullanılabilir olduğunda test edilebilir.
AVX10.2 özellikli donanım henüz kullanılamadığından, JIT'nin AVX10.2 desteği şu anda varsayılan olarak devre dışıdır.
Yığın ayırma
Yığın ayırma, GC'nin izlemesi gereken nesne sayısını azaltır ve ayrıca diğer iyileştirmelerin kilidini açar. Örneğin, bir nesne yığında tahsis edildikten sonra, JIT onu tamamen skaler değerleriyle değiştirmeyi düşünebilir. Bu nedenle yığın ayırma, başvuru türlerinin soyutlama cezasını azaltmanın anahtarıdır. .NET 10 , küçük değer türleri dizileriveküçük başvuru türleri dizileri için yığın ayırması ekler. Ayrıca yerel yapı alanları ve temsilciler için kaçış analizi içerir. (Kaçamayan nesneler yığın üzerinde ayrılabilir.)
Küçük değer türleri dizileri
JIT, GC işaretçileri içermeyen ve üst yöntemlerinden daha uzun ömürlü olmayacağı garanti edilen küçük, sabit boyutlu değer türü dizilerini artık yığına ayırır. Aşağıdaki örnekte, JIT derleme zamanında numbers
öğesinin yalnızca üç tamsayıdan oluşan ve Sum
çağrısından sonra yaşamayan bir dizi olduğunu bilir, bu nedenle onu yığına ayırır.
static void Sum()
{
int[] numbers = {1, 2, 3};
int sum = 0;
for (int i = 0; i < numbers.Length; i++)
{
sum += numbers[i];
}
Console.WriteLine(sum);
}
Referans türlerinin küçük dizileri
.NET 10 , .NET 9 yığın ayırma geliştirmelerini başvuru türlerinin küçük dizilerine genişletir. Daha önce, referans türü dizileri, yaşam süreleri yalnızca bir yönteme dayalı olduğunda bile yığında her zaman tahsis edilirdi. Artık JIT, oluştukları bağlamdan daha uzun süre yaşamayan bu tür dizileri yığında tahsis edebilir. Aşağıdaki örnekte dizi words
artık yığında ayrılmıştır.
static void Print()
{
string[] words = {"Hello", "World!"};
foreach (var str in words)
{
Console.WriteLine(str);
}
}
Kaçış analizi
Kaçış analizi bir nesnenin üst metodundan daha uzun süre yaşayıp yaşayamayacağını belirler. Yerel olmayan değişkenlere atandığında veya JIT tarafından satır içi hale getirilmeyen işlevlere geçirildiğinde nesneler "kaçar". Bir nesne kaçamıyorsa, yığına atanabilir. .NET 10, şunlar için kaçış analizi içerir:
Yerel yapı alanları
.NET 10'dan başlayarak, JIT daha fazla yığın ayırması sağlayan ve yığın ek yükünü azaltan yapı alanları tarafından başvurulan nesneleri dikkate alır. Aşağıdaki örneği göz önünde bulundurun:
public class Program
{
struct GCStruct
{
public int[] arr;
}
public static void Main()
{
int[] x = new int[10];
GCStruct y = new GCStruct() { arr = x };
return y.arr[0];
}
}
Normalde, JIT kaçış olmayan, küçük ve sabit boyutlu dizileri, örneğin x
, yığında ayırır.
y.arr
'e atanması, x
'in kaçmasına neden olmaz çünkü y
de kaçmaz. Ancak JIT'nin önceki kaçış analizi uygulaması yapı alanı başvurularını modellemedi. .NET 9'da, Main
için oluşturulan x64 derlemesi, yığın üzerinde CORINFO_HELP_NEWARR_1_VC
ayırmak için bir x
çağrısını içerir ve kaçacağını belirten şekilde işaretlenmiştir.
Program:Main():int (FullOpts):
push rax
mov rdi, 0x719E28028A98 ; int[]
mov esi, 10
call CORINFO_HELP_NEWARR_1_VC
mov eax, dword ptr [rax+0x10]
add rsp, 8
ret
.NET 10'da, söz konusu yapı escape etmiyorsa JIT artık yerel yapı alanları tarafından referans verilen nesneleri escape etmiş gibi işaretlemez. Montaj şimdi şöyle görünüyor (yığın belleği ayırma yardımcı işlevinin gittiğine dikkat edin):
Program:Main():int (FullOpts):
sub rsp, 56
vxorps xmm8, xmm8, xmm8
vmovdqu ymmword ptr [rsp], ymm8
vmovdqa xmmword ptr [rsp+0x20], xmm8
xor eax, eax
mov qword ptr [rsp+0x30], rax
mov rax, 0x7F9FC16F8CC8 ; int[]
mov qword ptr [rsp], rax
lea rax, [rsp]
mov dword ptr [rax+0x08], 10
lea rax, [rsp]
mov eax, dword ptr [rax+0x10]
add rsp, 56
ret
.NET 10'daki soyutlamayı kaldırma geliştirmeleri hakkında daha fazla bilgi için bkz. dotnet/runtime#108913.
Temsilciler
Kaynak kodu IL'ye derlendiğinde, her temsilci, tanımına uygun bir yöntem ve yakalanan değişkenlere karşılık gelen alanlarla bir kapanış sınıfına dönüştürülür. Çalışma zamanında, yakalanan değişkenleri örneklemek için bir kapatma nesnesi ve temsilciyi çağırmak için Func
nesnesi oluşturulur. Kaçış analizi nesnenin geçerli kapsamını aşmayacağını belirlerse, JIT onu yığına ayırır.
Aşağıdaki Main
yöntemi göz önünde bulundurun:
public static int Main()
{
int local = 1;
int[] arr = new int[100];
var func = (int x) => x + local;
int sum = 0;
foreach (int num in arr)
{
sum += func(num);
}
return sum;
}
Daha önce, JIT Main
için aşağıdaki kısaltılmış x64 derlemesini üretti. Döngüye girmeden önce, arr
, func
ve func
olarak adlandırılan, Program+<>c__DisplayClass0_0
için kapanış sınıfı, çağrılarında belirtildiği üzere yığında ayrılır.
; prolog omitted for brevity
mov rdi, 0x7DD0AE362E28 ; Program+<>c__DisplayClass0_0
call CORINFO_HELP_NEWSFAST
mov rbx, rax
mov dword ptr [rbx+0x08], 1
mov rdi, 0x7DD0AE268A98 ; int[]
mov esi, 100
call CORINFO_HELP_NEWARR_1_VC
mov r15, rax
mov rdi, 0x7DD0AE4A9C58 ; System.Func`2[int,int]
call CORINFO_HELP_NEWSFAST
mov r14, rax
lea rdi, bword ptr [r14+0x08]
mov rsi, rbx
call CORINFO_HELP_ASSIGN_REF
mov rsi, 0x7DD0AE461140 ; code for Program+<>c__DisplayClass0_0:<Main>b__0(int):int:this
mov qword ptr [r14+0x18], rsi
xor ebx, ebx
add r15, 16
mov r13d, 100
G_M24375_IG03: ;; offset=0x0075
mov esi, dword ptr [r15]
mov rdi, gword ptr [r14+0x08]
call [r14+0x18]System.Func`2[int,int]:Invoke(int):int:this
add ebx, eax
add r15, 4
dec r13d
jne SHORT G_M24375_IG03
; epilog omitted for brevity
Artık func
, Main
kapsamı dışında hiçbir zaman başvurulmadığı için yığında da ayrılır.
; prolog omitted for brevity
mov rdi, 0x7B52F7837958 ; Program+<>c__DisplayClass0_0
call CORINFO_HELP_NEWSFAST
mov rbx, rax
mov dword ptr [rbx+0x08], 1
mov rsi, 0x7B52F7718CC8 ; int[]
mov qword ptr [rbp-0x1C0], rsi
lea rsi, [rbp-0x1C0]
mov dword ptr [rsi+0x08], 100
lea r15, [rbp-0x1C0]
xor r14d, r14d
add r15, 16
mov r13d, 100
G_M24375_IG03: ;; offset=0x0099
mov esi, dword ptr [r15]
mov rdi, rbx
mov rax, 0x7B52F7901638 ; address of definition for "func"
call rax
add r14d, eax
add r15, 4
dec r13d
jne SHORT G_M24375_IG03
; epilog omitted for brevity
Dikkat edin, bir tane kalan CORINFO_HELP_NEW*
çağrı var, bu da kapanış için bellek yığını tahsisatıdır. Çalışma zamanı takımı, gelecekteki bir sürümde fonksiyon kapatmalarının yığındaki tahsisini desteklemek için kaçış analizi (escape analysis) metodunu genişletmeyi planlıyor.
Inlining geliştirmeleri
.NET 10'da çeşitli inlining geliştirmeleri yapılmıştır.
JIT, artık önceki satır içi işlemler nedeniyle devirtualizasyon için uygun hale gelen yöntemleri satır içine alabilir. Bu geliştirme, JIT'nin daha fazla iyileştirme fırsatı, örneğin daha fazla "inline" işlemi ve sanallaştırmayı ortadan kaldırma gibi, ortaya çıkarmasını sağlar.
Özellikle try-finally
bloklarına sahip olan bazı istisna işleme semantiği yöntemleri de satır içi yerleştirilebilir.
JIT'nin bazı dizileri yığınlama özelliğinden daha iyi yararlanmak için inliner'ın buluşsal yöntemleri, küçük, sabit boyutlu diziler döndürebilecek adayların kârlılığını artıracak şekilde ayarlanmıştır.
Dönüş türleri
Satır içi oluşturma sırasında JIT artık dönüş değerlerini tutan geçici değişkenlerin türünü güncelleştirir. Bir çağrıdaki tüm dönüş siteleri aynı türe sahipse, sonraki çağrıları devirtualize etmek için bu kesin tür bilgileri kullanılır. Bu iyileştirme, geç sanal kaldırma ve dizi sayım soyutlamasının iyileştirmelerini tamamlar.
Profil verileri
.NET 10, profil verilerinden daha iyi yararlanmak için JIT'nin iç ilkesini geliştirir. Çok sayıda buluşsal yöntem arasında JIT inliner, çağıranın yöntemini şişirmekten kaçınmak için belirli bir boyuttaki yöntemleri dikkate almaz. Çağıranın, bir inlining adayının sıklıkla yürütüldüğüne işaret eden profil verileri olduğunda, inliner adayın boyut toleransını artırır.
JIT'in profil verileri olmayan bazı çağrıları Callee
profil verileri olan bazı arayanların Caller
içine satır içi olarak yerleştirdiğini varsayalım. Bu tutarsızlık, çağrılanın izlemeye değmeyecek kadar küçük olması veya yeterli çağrı sayısına sahip olmak için çok sık satır içi hale getirilmesi durumunda ortaya çıkabilir. Kendi inlining adayları varsa Callee
, JIT daha önce Callee
profil verilerinin bulunmaması nedeniyle bunları varsayılan boyut sınırını dikkate alarak değerlendirmemişti. Şimdi JIT, Caller
profil verilerine sahip olduğunu fark edecek ve boyut kısıtlamasını gevşetecek (ancak, kesinlik kaybını hesaba katmak için, Callee
profil verilerine sahip olsa olduğu kadar değil).
Benzer şekilde, JIT bir çağrı konumunun iç içe yerleştirme için karlı olmadığını belirlediğinde, gelecekteki inlining girişimlerini göz önünde bulundurmaktan kurtarmak için yöntemi NoInlining
ile işaretler. Bununla birlikte, birçok iç içe geçirme sezgisi profil verilerine duyarlıdır. Örneğin, JIT profil verilerinin yokluğunda bir yöntemin satır içi genişlemeye değmeyecek kadar büyük olduğuna karar verebilir. Ancak arayan yeterince sıcak olduğunda, JIT boyut kısıtlamasını ve aramayı satır içi olarak gevşetmeye istekli olabilir. .NET 10'da, Anında Derleyici (JIT), profil verileriyle çağrı noktalarının performansını olumsuz etkilememek için artık verimsiz inline'ları NoInlining
ile işaretlemez.
NativeAOT türü ön yükleyici geliştirmeleri
NativeAOT'un tür ön başlatıcısı artık conv.*
ve neg
opcode'larının tüm değişkenlerini destekliyor. Bu iyileştirme, dönüştürme veya negasyon işlemlerini içeren yöntemlerin ön yüklemesini ve çalışma zamanı performansını daha da optimize etmeyi sağlar.
Arm64 yazma engeli geliştirmeleri
. NET'in çöp toplayıcısı (GC) nesilseldir, yani canlı nesneleri toplama performansını geliştirmek için yaşa göre ayırır. GC, uzun ömürlü nesnelerin herhangi bir zamanda başvurulmama (veya "ölü") olma olasılığının daha düşük olduğu varsayımı altında genç nesilleri daha sık toplar. Ancak, eski bir nesnenin genç bir nesneye başvurmaya başladığını varsayalım; GC'nin genç nesneyi toplayamayacağını bilmesi gerekir. Ancak genç bir nesneyi toplamak için eski nesneleri taramak gerektiğinde, bu durum nesilsel GC'nin performans kazançlarını ortadan kaldırır.
Bu sorunu çözmek için JIT, GC'yi bilgilendirmek için nesne başvurusu güncelleştirmelerinden önce yazma engelleri ekler. x64'te çalışma zamanı, GC yapılandırmasına bağlı olarak yazma hızlarını ve koleksiyon verimliliğini dengelemek için yazma engeli uygulamaları arasında dinamik olarak geçiş yapabilir. .NET 10'da bu işlev Arm64'te de kullanılabilir. Özellikle Arm64'teki yeni varsayılan yazma engeli uygulaması GC bölgelerini daha hassas bir şekilde işler ve bu da toplama performansını yazma engeli aktarım hızına düşük bir maliyetle artırır. Benchmark testleri, yeni GC varsayılanlarıyla GC duraklatma iyileştirmelerinin 8%'dan 20%'in üzerine çıktığını gösteriyor.