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 makale, büyük .NET Framework uygulamalarının veya dosyalar veya veritabanları gibi büyük miktarda veriyi işleyen uygulamaların performansını geliştirmeye yönelik ipuçları sağlar. Bu ipuçları, yönetilen kodda C# ve Visual Basic derleyicilerinin yeniden yazılmasından gelir ve bu makale C# derleyicisinden birkaç gerçek örnek içerir.
.NET Framework, uygulama oluşturmak için son derece üretkendir. Güçlü ve güvenli diller ve zengin kitaplık koleksiyonu, uygulama oluşturmayı son derece verimli hale getirir. Ancak, büyük üretkenlik sorumluluk getirir. .NET Framework'ün tüm gücünü kullanmanız, ancak gerektiğinde kodunuzun performansını ayarlamaya hazır olmanız gerekir.
Yeni derleyici performansı neden uygulamanız için geçerlidir?
.NET Derleyici Platformu ("Roslyn") ekibi, yönetilen koddaki C# ve Visual Basic derleyicilerini yeniden yazarak kodu modellemeye ve çözümlemeye, araçlar oluşturmaya ve Visual Studio'da çok daha zengin, koda duyarlı deneyimlere olanak sağlamaya yönelik yeni API'ler sağlar. Derleyicileri yeniden yazmak ve yeni derleyiciler üzerinde Visual Studio deneyimleri oluşturmak, büyük bir .NET Framework uygulaması veya çok fazla veri işleyen herhangi bir uygulama için geçerli olan yararlı performans içgörülerini ortaya çıkardı. C# derleyicisinden elde ettiğiniz içgörülerden ve örneklerden yararlanmak için derleyiciler hakkında bilgi sahibi olmanız gerekmez.
Visual Studio, tanımlayıcıların ve anahtar sözcüklerin renklendirmesi, söz dizimi tamamlama listeleri, hatalar için dalgalı çizgiler, parametre ipuçları, kod sorunları ve kod eylemleri gibi kullanıcıların sevdiği tüm IntelliSense özelliklerini oluşturmak için derleyici API'lerini kullanır. Visual Studio, geliştiriciler kodlarını yazarken ve değiştirirken bu yardımı sağlar ve derleyici kod geliştiricilerinin düzenlemesini sürekli modellerken Visual Studio'nun yanıt vermeye devam etmesi gerekir.
Son kullanıcılarınız uygulamanızla etkileşime geçtiğinde yanıt vermenizi bekler. Yazma veya komut işleme hiçbir zaman engellenmemelidir. Kullanıcı yazmaya devam ederse yardım hızlı bir şekilde açılır veya vazgeçer. Uygulamanız, uygulamanın yavaş çalışıyormuş gibi hissettirmesine neden olan uzun hesaplamalarla kullanıcı arabirimi iş parçacığını engellememeye dikkat etmelidir.
Roslyn derleyicileri hakkında daha fazla bilgi için bkz . .NET Derleyici Platformu SDK'sı.
Sadece Gerçekler
Performansı ayarlarken ve duyarlı .NET Framework uygulamaları oluştururken bu olguları göz önünde bulundurun.
Gerçek 1: Erken iyileştirmeler her zaman zahmete değmez
Olması gerekenden daha karmaşık bir kod yazmak bakım, hata ayıklama ve parlatma maliyetlerine neden olur. Deneyimli programcılar kodlama sorunlarını çözme ve daha verimli kod yazma konusunda sezgisel bir kavramaya sahiptir. Ancak, bazen kodlarını erken iyileştirirler. Örneğin, basit bir dizi yeterli olabilecekken, karma tablo kullanırlar veya yalnızca değerleri yeniden hesaplamak yerine belleği sızdırabilecek karmaşık bir önbellekleme kullanırlar. Deneyim programcısı olsanız bile, sorun bulduğunuzda performansı test etmeli ve kodunuzu analiz etmelisiniz.
2. Olgu: Ölçümlemiyorsanız, tahmin ediyorsunuz demektir.
Profiller ve ölçümler yalan söylemez. Profiller, CPU'nun tamamen yüklenip yüklenmediğini veya disk G/Ç'sinde engellenip engellenmediğinizi gösterir. Profiller, ne tür ve ne kadar bellek ayırdığınızı ve CPU'nuzun çöp toplamada (GC) çok fazla zaman harcayıp harcamadığını bildirir.
Uygulamanızdaki önemli müşteri deneyimleri veya senaryoları için performans hedefleri belirlemeniz ve performansı ölçmek için testler yazmanız gerekir. Bilimsel yöntemi uygulayarak başarısız testleri araştırın: Size yol gösterecek profilleri kullanın, sorunun ne olabileceğini hipotezleyin ve bir deneme veya kod değişikliğiyle hipotezinizi test edin. Düzenli testlerle zaman içinde temel performans ölçümleri oluşturun, böylece performansta regresyonlara neden olan değişiklikleri yalıtabilirsiniz. Performans çalışmalarına sıkı bir şekilde yaklaşarak ihtiyacınız olmayan kod güncelleştirmeleriyle zaman kaybetmekten kaçınmış olursunuz.
Olgu 3: İyi araçlar tüm farkları yaratır
İyi araçlar, en büyük performans sorunlarını (CPU, bellek veya disk) hızla detaylandırmanıza ve bu performans sorunlarına neden olan kodu bulmanıza yardımcı olur. Microsoft, Visual Studio Profiler ve PerfView gibi çeşitli performans araçları sağlar.
PerfView, disk G/Ç, GC olayları ve bellek gibi derin sorunlara odaklanmanıza yardımcı olan güçlü bir araçtır. Windows için Performansla ilgili Olay İzleme (ETW) olaylarını yakalayabilir ve uygulama başına, işlem başına, yığın başına ve iş parçacığı bilgileri başına kolayca görüntüleyebilirsiniz. PerfView, uygulamanızın ne kadar bellek ayırdığı ve hangi işlevlerin veya çağrı yığınlarının bellek ayırmalarına ne kadar katkıda bulunduğu gösterilir. Ayrıntılar için, araçla birlikte sunulan zengin yardım konularına, tanıtımlara ve videolara bakın.
Olgu 4: Her şey ayırmalarla ilgili
Hızlı yanıt veren bir .NET Framework uygulaması oluşturmanın, kabarcık sıralama yerine hızlı sıralama kullanma gibi algoritmalarla ilgili olduğunu düşünebilirsiniz, ancak durum bu değildir. Duyarlı bir uygulama oluşturmanın en büyük faktörü, özellikle uygulamanız çok büyük olduğunda veya büyük miktarda veri işlediğinde bellek ayırmadır.
Yeni derleyici API'leriyle duyarlı IDE deneyimleri oluşturmaya yönelik neredeyse tüm çalışmalar, ayırmalardan kaçınmayı ve önbelleğe alma stratejilerini yönetmeyi içeriyor. PerfView izlemeleri, yeni C# ve Visual Basic derleyicilerinin performansının nadiren CPU'ya bağlı olduğunu gösterir. Derleyiciler yüz binlerce veya milyonlarca kod satırı okurken, meta verileri okurken veya oluşturulan kodu yayarken G/Ç sınırına bağlı olabilir. UI iş parçacığı gecikmelerinin neredeyse tamamı çöp toplamadan kaynaklanmaktadır. .NET Framework GC yüksek performans için ayarlanmıştır ve uygulama kodu yürütülürken çalışmalarının büyük bir kısmını eşzamanlı olarak yapar. Ancak tek bir ayırma, tüm iş parçacıklarını durdurarak pahalı bir 2 . nesil koleksiyonunu tetikleyebilir.
Yaygın ayırmalar ve örnekler
Bu bölümdeki örnek ifadelerin küçük görünen gizli ayırmaları vardır. Ancak, bir uygulama ifadeleri yeterli sıklıkta çalıştırırsa, yüzlerce megabayt, hatta gigabayt boyutunda bellek tahsislerine neden olabilir. Örneğin, bir geliştiricinin düzenleyicide yazdığının benzetimini yapılan bir dakikalık testler gigabayt bellek ayırdı ve performans ekibini yazma senaryolarına odaklanmaya yönlendirdi.
Kutulama
Normalde yığında veya veri yapılarında yaşayan değer türleri bir nesneye sarmalandığında kutulama gerçekleşir. Başka bir ifadeyle, verileri tutmak için bir nesne ayırır ve sonra nesneye bir işaretçi döndürürsiniz. .NET Framework, bazen bir yöntemin imzası veya depolama yeri türü nedeniyle değerleri kutulama işlemi yapar. Nesnedeki bir değer türünü sarmalama, bellek ayırmaya neden olur. Birçok kutulama işlemi, uygulamanıza megabaytlar veya gigabaytlar kadar ayırmaya katkıda bulunabilir ve bu da uygulamanızın daha fazla GC'ye neden olacağı anlamına gelir. .NET Framework ve dil derleyicileri mümkün olduğunda kutulama yapmaktan kaçınabilir, ancak bazen en az beklediğiniz durumlarda bu durum ortaya çıkar.
PerfView'da kutulama işlemini görmek için bir izleme açın ve uygulamanızın işlem adının altındaki GC Yığın Yığın Yığınları'na bakın (unutmayın, PerfView tüm işlemlerle ilgili raporlar). ve System.Int32 gibi System.Char türleri ayırmaların altında görüyorsanız, değer türlerini kutulu olarak ayarlarsınız. Bu türlerden birini seçmek, kutulandıkları yığınları ve işlevleri gösterir.
Örnek 1: string yöntemleri ve değer türü bağımsız değişkenleri
Bu örnek kod, potansiyel olarak gereksiz ve aşırı kutulama durumunu gösterir:
public class Logger
{
public static void WriteLine(string s) { /*...*/ }
}
public class BoxingExample
{
public void Log(int id, int size)
{
var s = string.Format("{0}:{1}", id, size);
Logger.WriteLine(s);
}
}
Bu kod, günlük kaydı işlevi sağlar, bu nedenle bir uygulama Log işlevini sık, belki de milyonlarca kez çağırabilir. Sorun, string.Format çağrısının Format(String, Object, Object) aşırı yüklemesine çözümlenmesidir.
Bu aşırı yükleme, .NET Framework'ün değer türlerini int değerlerini nesnelere dönüştürerek bu yöntem çağrısına iletmesini gerektirir. Kısmi bir düzeltme, id.ToString() ve size.ToString() çağrılarını yapmak ve tüm dizeleri (nesneler olan) string.Format çağrısına geçirmektir. Çağrı ToString() bir dize ayırır, ancak bu ayırma yine de string.Format içinde gerçekleşir.
Temel çağrı string.Format için yalnızca bir dize birleştirme olduğunu düşünebilirsiniz, bu nedenle bunun yerine şu kodu yazabilirsiniz:
var s = id.ToString() + ':' + size.ToString();
Ancak, bu kod satırı derlendiği için bir kutulama tahsisine neden olur. .NET Framework'ün Concat çağırmak için karakter değişmezini kutulaması gerekir
Örnek 1 için düzeltme
Tam düzeltme basittir. Karakter değişmez değerini, dizeler zaten nesne olduğundan kutulama gerektirmeyen bir dize değişmez değeriyle değiştirmeniz gerekir:
var s = id.ToString() + ":" + size.ToString();
Örnek 2: enum boxlama
Bu örnek, özellikle sözlük arama işlemlerinde numaralandırma türlerinin sık kullanılması nedeniyle yeni C# ve Visual Basic derleyicilerinde çok büyük miktarda ayırmadan sorumluydu.
public enum Color
{
Red, Green, Blue
}
public class BoxingExample
{
private string name;
private Color color;
public override int GetHashCode()
{
return name.GetHashCode() ^ color.GetHashCode();
}
}
Bu sorun çok nüanslı. Yöntem, uygulama nedenleriyle enum türünün temel anlatımını kutuladığı için PerfView bunu GetHashCode() kutulama olarak raporlar. PerfView’e yakından bakarsanız, her bir çağrı başına GetHashCode() için iki boxing tahsisini görebilirsiniz. Derleyici birini ekler ve .NET Framework diğerini ekler.
Örnek 2'nin düzeltilmesi
çağrısından GetHashCode()önce temel gösterime dönüştürerek her iki ayırmayı da kolayca önleyebilirsiniz:
((int)color).GetHashCode()
Numaralandırma türlerinde kutulamanın bir diğer yaygın kaynağı Enum.HasFlag(Enum) yöntemidir. Geçirilen HasFlag(Enum) bağımsız değişken kutulamalı. Çoğu durumda, Enum.HasFlag(Enum) çağrılarının bit tabanlı bir testle değiştirilmesi daha basit ve bellek ayırmadan yapılabilir.
İlk performans olgusunu aklınızda bulundurun (yani, erken iyileştirmeyin) ve tüm kodunuzu bu şekilde yeniden yazmaya başlamayın. Bu kutulama maliyetlerine dikkat edin, ancak kodunuzu yalnızca uygulamanızın profilini çıkardıktan ve sık erişim noktalarını bulduktan sonra değiştirin.
Dizeler
Dize manipülasyonları, allokasyonlar için en büyük suçlulardan bazılarıdır ve genellikle PerfView'da en büyük beş allokasyon arasında sıklıkla görülür. Programlar serileştirme, JSON ve REST API'leri için dizeler kullanır. Sabit listesi türlerini kullanamıyorsanız sistemlerle birlikte çalışabilmek için dizeleri programlı sabitler olarak kullanabilirsiniz. Profil oluşturma işleminiz dizelerin performansı önemli ölçüde etkilediğini gösterdiğinde, String, Format, Concat, Split, Join ve Substring gibi yöntemlere yapılan çağrılara bakın. StringBuilder Birçok parça birleştirilerek bir dize oluşturmanın maliyetini önlemek için kullanmak yardımcı olur, ancak nesneyi ayırmanız StringBuilder bile, yönetilmesi gereken bir darboğaz haline gelebilir.
Örnek 3: dize işlemleri
C# derleyicisinde biçimlendirilmiş XML belge açıklamasının metnini yazan şu kod vardı:
public void WriteFormattedDocComment(string text)
{
string[] lines = text.Split(new[] { "\r\n", "\r", "\n" },
StringSplitOptions.None);
int numLines = lines.Length;
bool skipSpace = true;
if (lines[0].TrimStart().StartsWith("///"))
{
for (int i = 0; i < numLines; i++)
{
string trimmed = lines[i].TrimStart();
if (trimmed.Length < 4 || !char.IsWhiteSpace(trimmed[3]))
{
skipSpace = false;
break;
}
}
int substringStart = skipSpace ? 4 : 3;
for (int i = 0; i < numLines; i++)
WriteLine(lines[i].TrimStart().Substring(substringStart));
}
else { /* ... */ }
Bu kodun çok fazla dize işlemesi yaptığını görebilirsiniz. Kod, satırları ayrı dizelere bölmek, boşluğu kırpmak, bağımsız değişkenin text BIR XML belge açıklaması olup olmadığını denetlemek ve satırlardan alt dizeleri ayıklamak için kitaplık yöntemlerini kullanır.
WriteFormattedDocComment içindeki ilk satırda, her çağrıldığında text.Split çağrısı bağımsız değişken olarak yeni bir üç elemanlı dizi oluşturur. Derleyicinin, bu diziyi ayırmak için her seferinde kod üretmesi gerekiyor. Derleyicinin, Split diziyi başka bir kod tarafından değiştirilebilecek bir yerde depolayıp depolamadığını bilmemesi nedeniyle, bu durum, daha sonra WriteFormattedDocComment çağrılarını etkileyebilir.
Split çağrısı, text içindeki her satır için bir dize ayırır ve işlemi gerçekleştirmek için ek bellek ayırır.
WriteFormattedDocComment yöntemine üç çağrıya TrimStart sahiptir. İkisi, görevleri ve kaynak ayırımlarını tekrarlayan iç döngülerdedir. Daha da kötüsü, TrimStart yöntemini bağımsız değişken olmadan çağırmak, dize sonucuna ek olarak params parametresi için boş bir dizi tahsis eder.
Son olarak, genellikle yeni bir dize ayıran Substring yöntemine bir çağrı vardır.
Örnek 3 için düzeltme
Önceki örneklerden farklı olarak, küçük düzenlemeler bu ayırmaları düzeltemez. Geri adım atıp soruna bakmanız ve farklı yaklaşmanız gerekir. Örneğin, WriteFormattedDocComment() bağımsız değişkeninin, yöntemin ihtiyaç duyduğu tüm bilgileri içeren bir dize olduğunu fark edeceksiniz; bu sayede, kod birçok kısmi dize ayırmak yerine daha fazla dizinleme gerçekleştirebilir.
Derleyicinin performans ekibi tüm bu ayırmaları aşağıdaki gibi bir kodla ele aldı:
private int IndexOfFirstNonWhiteSpaceChar(string text, int start) {
while (start < text.Length && char.IsWhiteSpace(text[start])) start++;
return start;
}
private bool TrimmedStringStartsWith(string text, int start, string prefix) {
start = IndexOfFirstNonWhiteSpaceChar(text, start);
int len = text.Length - start;
if (len < prefix.Length) return false;
for (int i = 0; i < len; i++)
{
if (prefix[i] != text[start + i]) return false;
}
return true;
}
// etc...
İlk sürümü WriteFormattedDocComment(), bir dizi, birkaç alt dize ve kırpılmış alt dize veya diziler ile birlikte boş bir params dizisi tahsis etti. "///" için de kontrol edildi. Düzeltilen kod yalnızca indeksleme yapar ve hiçbir kaynak ayırmaz. Boşluk olmayan ilk karakteri bulur ve ardından dizenin "///" ile başlayıp başlamadığını görmek için karakteri karaktere göre denetler. Yeni kod, belirtilen bir başlangıç dizininden sonra boşluk olmayan bir karakterin oluştuğu ilk dizini döndürmek için TrimStart yerine IndexOfFirstNonWhiteSpaceChar kullanır. Düzeltme tamamlanmadı, ancak eksiksiz bir çözüm için benzer düzeltmelerin nasıl uygulanacağını görebilirsiniz. Bu yaklaşımı kod genelinde uygulayarak içindeki tüm ayırmaları WriteFormattedDocComment()kaldırabilirsiniz.
Örnek 4: StringBuilder
Bu örnekte bir StringBuilder nesne kullanılır. Aşağıdaki işlev genel türler için tam tür adı oluşturur:
public class Example
{
// Constructs a name like "SomeType<T1, T2, T3>"
public string GenerateFullTypeName(string name, int arity)
{
StringBuilder sb = new StringBuilder();
sb.Append(name);
if (arity != 0)
{
sb.Append("<");
for (int i = 1; i < arity; i++)
{
sb.Append("T"); sb.Append(i.ToString()); sb.Append(", ");
}
sb.Append("T"); sb.Append(i.ToString()); sb.Append(">");
}
return sb.ToString();
}
}
Odak, yeni StringBuilder bir örnek oluşturan satırdadır. Kod, sb.ToString() için bir ayırmaya ve StringBuilder içindeki dahili ayırmalara neden olur ancak dize sonucunu istiyorsanız, bu ayırmaları kontrol edemezsiniz.
Örnek 4 için düzeltme
Nesne ayırmayı StringBuilder düzeltmek için nesneyi önbelleğe alın. Çöpe gidebilecek tek bir örneği önbelleğe almak bile performansı önemli ölçüde geliştirebilir. Bu, işlevin yeni uygulamasıdır ve yeni ilk ve son satırlar dışındaki tüm kodu atlar:
// Constructs a name like "MyType<T1, T2, T3>"
public string GenerateFullTypeName(string name, int arity)
{
StringBuilder sb = AcquireBuilder();
/* Use sb as before */
return GetStringAndReleaseBuilder(sb);
}
Temel parçalar yeni AcquireBuilder() ve GetStringAndReleaseBuilder() işlevlerdir:
[ThreadStatic]
private static StringBuilder cachedStringBuilder;
private static StringBuilder AcquireBuilder()
{
StringBuilder result = cachedStringBuilder;
if (result == null)
{
return new StringBuilder();
}
result.Clear();
cachedStringBuilder = null;
return result;
}
private static string GetStringAndReleaseBuilder(StringBuilder sb)
{
string result = sb.ToString();
cachedStringBuilder = sb;
return result;
}
Yeni derleyiciler iş parçacığı kullandığından, bu uygulamalar StringBuilder önbelleğe almak için (ThreadStaticAttribute öznitelik) bir iş parçacığı statik alanı kullanır ve muhtemelen ThreadStatic bildiriminden vazgeçebilirsiniz. İş parçacığı statik alanı, bu kodu yürüten her iş parçacığı için benzersiz bir değer tutar.
AcquireBuilder() varsa, önbelleğe alınmış StringBuilder örneğini döndürmeden önce önbelleği temizler ve alanı veya önbelleği null olarak ayarlar. Aksi takdirde, AcquireBuilder() yeni bir örnek oluşturur ve alanı veya önbelleği null olarak bırakarak bu örneği döndürür.
StringBuilder ile işiniz bittiğinde, dize sonucunu almak için GetStringAndReleaseBuilder() öğesini çağırır, ardından örneği alana veya önbelleğe kaydedersiniz ve sonunda sonucu döndürürsünüz. Yürütmenin bu koda yeniden girmesi ve birden çok StringBuilder nesne oluşturması mümkündür (bu nadiren meydana gelirse de). Kod, daha sonra kullanmak üzere yalnızca son yayımlanan StringBuilder örneği kaydeder. Bu basit önbelleğe alma stratejisi, yeni derleyicilerdeki ayırmaları önemli ölçüde azaltmıştır. .NET Framework ve MSBuild ("MSBuild") bölümleri, performansı geliştirmek için benzer bir teknik kullanır.
Bu basit önbelleğe alma stratejisi, boyut üst sınırına sahip olduğundan iyi bir önbellek tasarımına bağlıdır. Ancak, şu anda özgün koddan daha fazla kod var ve bu da daha fazla bakım maliyeti anlamına geliyor. Önbelleğe alma stratejisini yalnızca bir performans sorunu bulduğunuzda benimsemeniz gerekir ve PerfView ayırmaların önemli bir katkıda bulunan olduğunu StringBuilder göstermiştir.
LINQ ve lambdalar
DilLe Tümleşik Sorgu (LINQ), lambda ifadeleriyle birlikte üretkenlik özelliğine bir örnektir. Ancak, kullanımı zaman içindeki performansı önemli ölçüde etkileyebilir ve kodunuzu yeniden yazmanız gerektiğini fark edebilirsiniz.
Örnek 5: Lambdas, Liste<T> ve IEnumerable<T>
Bu örnekte, derleyicinin modelinde ad dizesi verilen bir simgeyi bulmak için LINQ ve işlevsel stil kodu kullanılmaktadır:
class Symbol {
public string Name { get; private set; }
/*...*/
}
class Compiler {
private List<Symbol> symbols;
public Symbol FindMatchingSymbol(string name)
{
return symbols.FirstOrDefault(s => s.Name == name);
}
}
Yeni derleyici ve üzerinde oluşturulan IDE deneyimleri FindMatchingSymbol() sıkça çağrılır ve bu işlevin tek bir kod satırında birkaç gizli bellek tahsisi vardır. Bu ayırmaları incelemek için önce işlevin tek kod satırını iki satıra bölün:
Func<Symbol, bool> predicate = s => s.Name == name;
return symbols.FirstOrDefault(predicate);
İlk satırda lambda ifadesis => s.Name == name, yerel değişkeni name kapsar. Bu,
// Compiler-generated class to hold environment state for lambda
private class Lambda1Environment
{
public string capturedName;
public bool Evaluate(Symbol s)
{
return s.Name == this.capturedName;
}
}
// Expanded Func<Symbol, bool> predicate = s => s.Name == name;
Lambda1Environment l = new Lambda1Environment() { capturedName = name };
var predicate = new Func<Symbol, bool>(l.Evaluate);
İki new ayrıma (biri ortam sınıfı için, diğeri temsilci için) şimdi açık.
Şimdi FirstOrDefault çağrısına bakın. Bu uzantı yöntemi System.Collections.Generic.IEnumerable<T> türünde de bir ayırmaya neden olur. İlk bağımsız değişken olarak bir FirstOrDefault nesnesi aldığından, çağrıyı şu şekildeki koda genişletebilirsiniz (tartışma için biraz basitleştirilmiş):
// Expanded return symbols.FirstOrDefault(predicate) ...
IEnumerable<Symbol> enumerable = symbols;
IEnumerator<Symbol> enumerator = enumerable.GetEnumerator();
while(enumerator.MoveNext())
{
if (predicate(enumerator.Current))
return enumerator.Current;
}
return default(Symbol);
symbols değişkeninin türü List<T>vardır.
List<T> Koleksiyon türü, IEnumerable<T> uygular ve IEnumerator<T> arabirimi numaralandırıcı olarak struct ile uygular ve ustaca tanımlar. Sınıf yerine bir yapı kullanmak, genellikle atık toplama performansını etkileyebilecek yığın ayırmalarından kaçınmanız anlamına gelir. Numaralandırıcılar genellikle çağrı yığınında döndürülürken numaralandırıcı yapısını kullanan dilin foreach döngüsüyle birlikte kullanılır. Bir nesneye yer açmak için çağrı yığını işaretçisinin artırılması, yığın ayırmanın yaptığı gibi GC'yi etkilemez.
Genişletilmiş FirstOrDefault çağrısı söz konusu olduğunda, kodun IEnumerable<T> üzerinde GetEnumerator() çağrısı yapması gerekir.
symbols olarak, enumerable değişkenine atama yapıldığında türü IEnumerable<Symbol> olan gerçek nesnenin bir List<T> olduğu bilgisi kaybolur. Bu, kod numaralandırıcıyı enumerable.GetEnumerator() ile getirip enumerator değişkenine ataması gerektiğinde .NET Framework'ün döndürülen yapıyı kutulamak zorunda olduğu anlamına gelir.
Örnek 5 için düzeltme
Düzeltme, tek kod satırını hala kısa, kolay okunabilen ve anlaşılması kolay ve bakımı kolay altı kod satırıyla değiştirerek aşağıdaki gibi yeniden yazmaktır FindMatchingSymbol :
public Symbol FindMatchingSymbol(string name)
{
foreach (Symbol s in symbols)
{
if (s.Name == name)
return s;
}
return null;
}
Bu kod LINQ uzantısı yöntemlerini, lambda'ları veya numaralandırıcıları kullanmaz ve ayırma yapmaz. Derleyici koleksiyonun symbols a List<T> olduğunu görebildiği ve kutulamadan kaçınmak için elde edilen numaralandırıcıyı (bir yapı) doğru türe sahip bir yerel değişkene bağlayabildiği için ayırma yoktur. Bu işlevin özgün sürümü, C# dilinin etkileyici gücünün ve .NET Framework'ün üretkenliğinin harika bir örneğiydi. Bu yeni ve daha verimli sürüm, bakım için herhangi bir karmaşık kod eklemeden bu özellikleri korur.
Zaman uyumsuz yöntem önbelleğe alma
Sonraki örnekte, zaman uyumsuz bir yöntemde önbelleğe alınmış sonuçları kullanmaya çalıştığınızda sık karşılaşılan bir sorun gösterilmektedir.
Örnek 6: Zaman uyumsuz yöntemlerde önbelleğe alma
Yeni C# ve Visual Basic derleyicileri üzerinde oluşturulan Visual Studio IDE özellikleri sık sık söz dizimi ağaçlarını getirir ve derleyiciler bunu yaparken zaman uyumsuz kullanarak Visual Studio'yu yanıt vermeye devam eder. Söz dizimi ağacını almak için yazabileceğiniz kodun ilk sürümü aşağıdadır:
class SyntaxTree { /*...*/ }
class Parser { /*...*/
public SyntaxTree Syntax { get; }
public Task ParseSourceCode() { /*...*/ }
}
class Compilation { /*...*/
public async Task<SyntaxTree> GetSyntaxTreeAsync()
{
var parser = new Parser(); // allocation
await parser.ParseSourceCode(); // expensive
return parser.Syntax;
}
}
GetSyntaxTreeAsync() çağrısının bir Parser örneğini oluşturduğunu, kodu ayrıştırdığını ve ardından bir Task nesnesi, Task<SyntaxTree>, döndürdüğünü görebilirsiniz. Pahalı kısmı Parser örneğini tahsis etmek ve kodu çözümlemektir. İşlev, çağıranların ayrıştırma işini bekleyebilmesi ve kullanıcı girişine yanıt vermek için UI iş parçacığını serbest bırakabilmesi amacıyla bir Task döndürür.
Çeşitli Visual Studio özellikleri aynı söz dizimi ağacını almaya çalışabilir, bu nedenle zaman ve ayırmalardan tasarruf etmek için ayrıştırma sonucunu önbelleğe almak için aşağıdaki kodu yazabilirsiniz. Ancak, bu kod bir ayırmaya neden olabilir:
class Compilation { /*...*/
private SyntaxTree cachedResult;
public async Task<SyntaxTree> GetSyntaxTreeAsync()
{
if (this.cachedResult == null)
{
var parser = new Parser(); // allocation
await parser.ParseSourceCode(); // expensive
this.cachedResult = parser.Syntax;
}
return this.cachedResult;
}
}
Önbelleğe alma içeren yeni kodda, cachedResult adlı bir SyntaxTree alanı olduğunu görürsünüz. Bu alan null olduğunda, GetSyntaxTreeAsync() çalışmayı yapar ve sonucu önbelleğe kaydeder.
GetSyntaxTreeAsync()
SyntaxTree nesnesini döndürür. Sorun, türünde bir async işleviniz olduğunda ve türünde Task<SyntaxTree>SyntaxTreebir değer döndürdüğünüzde, derleyicinin sonucu tutmak için bir Görev ayırmak için kod yaymasıdır (kullanarakTask<SyntaxTree>.FromResult()). Görev tamamlandı olarak işaretlenir ve sonuç hemen kullanılabilir. Yeni derleyicilerin kodunda, Task zaten tamamlanmış nesneler o kadar sık oluştu ki bu ayırmaların düzeltilmesi dikkat çekici bir şekilde yanıt verme hızını geliştirdi.
Örnek 6 için düzeltme
Tamamlanan Task ayırmayı kaldırmak için Task nesnesini tamamlanmış sonuçla önbelleğe alabilirsiniz:
class Compilation { /*...*/
private Task<SyntaxTree> cachedResult;
public Task<SyntaxTree> GetSyntaxTreeAsync()
{
return this.cachedResult ??
(this.cachedResult = GetSyntaxTreeUncachedAsync());
}
private async Task<SyntaxTree> GetSyntaxTreeUncachedAsync()
{
var parser = new Parser(); // allocation
await parser.ParseSourceCode(); // expensive
return parser.Syntax;
}
}
Bu kod, cachedResult türünü Task<SyntaxTree> olarak değiştirir ve GetSyntaxTreeAsync()'ten özgün kodu tutan bir async yardımcı işlevi kullanır.
GetSyntaxTreeAsync(), null değilse cachedResult döndürmek için artık null birleşim operatörünü kullanır. Null ise cachedResult , GetSyntaxTreeAsync() sonucu çağırır GetSyntaxTreeUncachedAsync() ve önbelleğe alır. Kod normalde olduğu gibi GetSyntaxTreeAsync()'nun çağrısını GetSyntaxTreeUncachedAsync() beklemediğini unutmayın. await kullanılmaması, GetSyntaxTreeUncachedAsync() nesnesi döndüğünde, Task tarafından GetSyntaxTreeAsync() hemen Taskdöndürüleceği anlamına gelir. Önbelleğe alınan sonuç bir Taskolduğundan, önbelleğe alınan sonucu döndürmek için ayırma yoktur.
Dikkat edilecek diğer noktalar
Burada, çok fazla veri işleyen büyük uygulamalarda veya uygulamalarda olası sorunlar hakkında birkaç nokta daha bulabilirsiniz.
Sözlük
Sözlükler birçok programda yaygın olarak kullanılır ve sözlükler çok kullanışlı ve doğal olarak verimli olsa da. Ancak bunlar genellikle uygunsuz bir şekilde kullanılır. Visual Studio'da ve yeni derleyicilerde çözümleme, sözlüklerin çoğunun tek bir öğe içerdiğini veya boş olduğunu gösterir. Boş bir Dictionary<TKey,TValue>, on alana sahiptir ve x86 makinesinde yığında 48 bayt yer kaplar. Sabit zamanlı arama ile eşleme veya ilişkilendirilebilir veri yapısına ihtiyaç duyduğunuzda sözlükler harikadır. Ancak, yalnızca birkaç öğeniz olduğunda, bir sözlük kullanarak çok fazla alan harcarsınız. Bunun yerine, örneğin, yinelemeli olarak bir List<KeyValuePair\<K,V>>içinde de aynı hızla bakabilirsiniz. Bir sözlüğü yalnızca verilerle yüklemek ve ondan okumak için kullanıyorsanız (çok yaygın bir desen), N(log(N)) aramasıyla sıralanmış bir dizi kullanmak, kullandığınız öğe sayısına bağlı olarak neredeyse o kadar hızlı olabilir.
Sınıflar ve yapılar
Bir şekilde, sınıflar ve yapılar uygulamalarınızı ayarlamak için klasik bir alan/zaman takası sağlar. Hiçbir alanı olmasa bile, sınıflar x86 makinesinde 12 bayt ek yük oluşturur, ancak bir sınıf örneğine başvurmak yalnızca bir işaretçi gerektirdiğinden, sınıf örneği olarak aktarmak ucuzdur. Yapılar kutulanmadıklarında yığın ayırması gerektirmezler, ancak büyük yapıları işlev bağımsız değişkenleri veya dönüş değerleri olarak geçtiğinizde, yapıların tüm veri üyelerini atomik bir şekilde kopyalamak CPU zamanını alır. Yapıları döndüren özelliklere yapılan yinelenen çağrılara dikkat edin ve aşırı veri kopyalamayı önlemek için özelliğin değerini yerel değişkende önbelleğe alın.
Önbellekleri
Yaygın bir performans püf noktası, sonuçları önbelleğe almaktır. Ancak boyut sınırı veya atma ilkesi olmayan bir önbellek bellek sızıntısı olabilir. Büyük miktarda veriyi işlerken, önbelleklerde çok fazla bellek tutarsanız, çöp toplama, önbelleğe alınmış aramalarınızdan elde edeceğiniz avantajları ortadan kaldırabilir.
Bu makalede, özellikle büyük miktarda veri işleyen büyük sistemler veya sistemler için uygulamanızın yanıt hızını etkileyebilecek performans sorunu belirtileri hakkında nasıl bilgi edinmeniz gerektiğini ele aldık. Yaygın sorunlar arasında kutulama, dize manipülasyonu, LINQ ve lambda, zaman uyumsuz yöntemlerde önbellekleme, boyut sınırı veya boşaltma politikası olmadan önbellek kullanımı, sözlüklerin uygunsuz kullanımı ve yapıların geçirilmesi yer alır. Uygulamalarınızı ayarlamaya yönelik dört olguyu aklınızda bulundurun:
Erken iyileştirmeyin; üretken olun ve sorunları tespit ettiğinizde uygulamanızı ayarlayın.
Profiller yalan söylemez; ölçmediğiniz için tahminde bulunursunuz.
İyi araçlar tüm farkı yaratır – PerfView'ı indirin ve deneyin.
Her şey ayırmalarla ilgilidir; derleyici platformu ekibi, zamanının çoğunu yeni derleyicilerin performansını geliştirmek için harcamış olduğu yerdir.