[JAVA] Grundkenntnisse der Informatik und ihrer Praxis, die Sie zur Leistungssteigerung kennen sollten

Einführung

Ich möchte diesen Artikel mit Qiita teilen, um einen Beitrag zur Community zu leisten. https://academy.tinybetter.com/Article/37407ec1-cd3a-50c8-2fa1-39f6ed3dae74/View

Es gibt viele Artikel im Internet, aber die Informationen sind fragmentiert und es ist für Anfänger, die das Programm gestartet haben, schwierig, sich ein Bild zu machen. Ich sehe nicht viele Artikel, die das grundlegende Verständnis und die Leistungstipps der Informatik zusammenfassen, deshalb habe ich sie zusammengefasst.

Alles messen

Das wichtigste Prinzip der Leistungsoptimierung ist die Messung. Ich werde die Messinstrumente zusammenfassen.

■BenchmarkDotNet https://benchmarkdotnet.org/articles/overview.html Ein Tool zum Messen von C # -Code. Erhältlich bei Nuget. Verwenden Sie dies anstelle der Stoppuhrklasse. Die Stoppuhr kann die Auswirkungen von GC usw. nicht richtig messen.

■ Visual Studio-Profilerstellungstool https://docs.microsoft.com/ja-jp/visualstudio/profiling/?view=vs-2019 Mit den Visual Studio-Profiling-Tools können Sie den Status von CPU, Speicher, Threads, GC, LOH und mehr überprüfen. Sie können schnell herausfinden, welcher Code in welcher Methode der kritische Pfad ist, indem Sie sich den Aufrufbaum ansehen.

■Fiddler https://www.telerik.com/fiddler Es ist ein Tool, das den Inhalt des Netzwerks erfasst. https://zappysys.com/blog/how-to-use-fiddler-to-analyze-http-web-requests/

■ Browser-Entwicklertools Sie können den Inhalt des Netzwerks auch mit den Entwicklertools Ihres Browsers erfassen, ohne Fiddler installieren zu müssen. Fiddler scheint anspruchsvoller zu sein, aber wenn Sie es einfach nachschlagen möchten, können Sie die Entwicklertools des Browsers verwenden.

■Wireshark https://www.wireshark.org/download.html Software, die die TCP / IP-Kommunikation erfassen kann. https://knowledge.sakura.ad.jp/6286/ https://hldc.co.jp/blog/2020/04/15/3988/

Es ist möglicherweise nicht sehr hilfreich bei der Anwendungsentwicklung, aber es kann vorkommen, dass Sie sich die Kommunikation genauer ansehen möchten. Lass uns erinnern.

■WinDbg https://docs.microsoft.com/ja-jp/windows-hardware/drivers/debugger/debugger-download-tools Sie können Windows-Dump-Dateien abrufen und analysieren. Sie können einen Stack-Trace erstellen und analysieren, welche Methode ihn verursacht. https://docs.microsoft.com/ja-jp/windows-hardware/drivers/debugger/debugging-using-windbg-preview https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugging-using-windbg [Erster Schritt der Crash-Dump-Analyse](https://blog.engineer-memo.com/2012/06/24/%E3%82%AF%E3%83%A9%E3%83%83%E3%82 % B7% E3% 83% A5% E3% 83% 80% E3% 83% B3% E3% 83% 97% E8% A7% A3% E6% 9E% 90% E3% 81% AE% E3% 81% AF % E3% 81% 98% E3% 82% 81% E3% 81% AE% E4% B8% 80% E6% AD% A9 /)

Ich denke, der Inhalt dieses Artikels ist im Allgemeinen korrekt, aber es kann Lügen geben. Da sich C # gegenüber der Vergangenheit weiterentwickelt und verändert hat, ist es wichtig, die Informationen im Netz nicht zu verschlucken, sondern selbst zu messen und zu bestätigen.

Computer und Netzwerke verstehen

Das Verständnis der Prinzipien von Computern und Netzwerken ist wichtig, um die Auswirkungen auf die Leistung zu verstehen. Ein Computer besteht aus einer CPU, einem Speicher und einer Festplatte. Die CPU ist der Berechnungsprozess, und der Speicher und die Festplatte sind die Speichergeräte. Der Speicher ist weiter in verschiedene Typen unterteilt.

Aufgrund der Verbesserung der CPU-Berechnungsverarbeitung ist das Lesen und Schreiben in den Speicher nicht mehr möglich. Als Ergebnis wurde ein Mechanismus namens Cache-Speicher vorbereitet. https://ascii.jp/elem/000/000/563/563800/ Der Cache-Speicher (primär) ist der schnellste und der Cache-Speicher (sekundär) → Cache-Speicher (tertiär) → Stapelspeicher → Heap-Speicher → SSD → Festplatte usw. Die Festplatte hat die größte Kapazität. Auch der Preis wird günstiger.

Übrigens, um eine bestimmte Zeichenfolge per Netzwerkkommunikation zu senden, konvertieren Sie die Zeichendaten in Byte, lösen Sie den Namen mit DNS auf, identifizieren Sie den Ziel-PC und tauschen Sie ihn mit TCP / IP per Drei-Wege-Handshake aus. Es ist ein Prozess, bei dem die Übertragungsmenge im Schiebefenster schrittweise erhöht wird. https://www.infraexpert.com/study/tcpip11.html https://ascii.jp/elem/000/000/619/619702/

Da der Prozess wie oben beschrieben ausgeführt wird, ist die Netzwerkkommunikation ein sehr zeitaufwändiger Prozess. Die Kommunikation wird schließlich in ein Spannungs-EIN / AUS-Signal umgewandelt, und das Spannungssignal geht aufgrund von Störungen von außen, wie z. B. Funkwellen am Kabel, verloren. TCP / IP erkennt es und sendet es erneut. Wird auftreten, so wird es langsamer sein. UDP ist etwas schneller, aber in jedem Fall ist die Netzwerkkommunikation der langsamste Prozess bei der Programmierung.

Prioritäten verstehen

Nachdem Sie Computer und Netzwerke besser verstanden haben, lernen Sie Prioritäten und grundlegende Ansätze kennen. Wir erklären Hot-Code-Pfade, Netzwerkaufrufe, Datei-E / A, Heap-Speicher, Threads, Asynchron usw. Dieses Mal werde ich den praktischen Teil anhand von C # und .NET als Beispiele erläutern, aber die Grundidee der Netzwerkkommunikation, des Heapspeichers und der Speicherbereinigung ist für Python, Java usw. dieselbe.

Hot-Code-Pfad

Das erste, was Sie verstehen müssen, ist das Konzept der Hot-Code-Pfade. Das Innere der iterativen Verarbeitung, z. B. für die Anweisung, wird viele Male ausgeführt. Betrachten Sie beispielsweise den folgenden Bildschirm. tasklist.png

Um einen solchen Bildschirm zu erstellen, müssen Sie die iterative Verarbeitung mit einer for-Anweisung usw. in der folgenden Reihenfolge schreiben. Mehrere Benutzer → mehrere Daten → mehrere Aufgaben Konzeptionell sieht es so aus.

private void ShowCalendar()
{
	List<UserRecord> userList = GetUserList(); //Netzwerkkommunikation Teil 1
	foreach (var user in userList)
	{
    	var startDate = new DateTime(2020, 9, 12);
    	for (var i = 0; i < 21; i++)
    	{
        	var date = startDate.AddDays(i);
        	var taskList = GetTaskList(date); //Netzwerkkommunikation Teil 2
        	foreach (var task in taskList)
        	{
            	CreateTaskTableCell(task);
        	}
    	}
	}
}
private void CreateTaskTableCell(TaskRecord record)
{
    //HOT PATH!!!!!!

    // Get color of Task
    var color = GetColorFromDatabase(); //Netzwerkkommunikation Teil 3
}

Nehmen wir an, es sieht so aus. Es gibt drei Netzwerkkommunikationscodes.

Der Code in der CreateTaskTableCell-Methode ist der Hot-Code-Pfad. Diese Methode wird sehr oft aufgerufen. Wenn Sie beispielsweise 100 Benutzer, 21 Tage und durchschnittlich 5 Aufgaben pro Tag haben, werden Sie 10500 Mal angerufen. Wenn Sie einen Code schreiben (Netzwerkkommunikation Teil 3), z. B. Farbdaten von der Datenbank erfassen, dauert es 10 Millisekunden x 10500 = 105 Sekunden, wenn der DB-Zugriff 10 Millisekunden beträgt. Es dauert weniger als zwei Minuten, bis die Seite angezeigt wird. Die Netzwerkkommunikation 2 ist 2100-mal, was besser ist als die Netzwerkkommunikation 3, aber dies muss ebenfalls verbessert werden. Die Netzwerkkommunikation Nr. 1 wird nur einmal aufgerufen, sodass Sie sie unverändert lassen können.

Der Teil des Codes, der häufig so ausgeführt wird, wird als Hot-Code-Pfad bezeichnet, und es ist erforderlich, diesen Code bevorzugt zu verbessern. Dieses Video https://www.youtube.com/watch?v=4yALYEINbyI Um 12:10 Uhr 20% des Codes verbrauchen 80% der Ressourcen. 4,0% des Codes verbrauchen 64% der Ressourcen. 0,8% des Codes verbrauchen 51% der Ressourcen. Sagt. Sie können sehen, wie der Hot-Code-Pfad Ressourcen verbraucht.

Verbessern Sie sich vom Ort mit der größten Wirkung

Der zu notierende Programmcode ist häufig in der folgenden Reihenfolge: · Netzwerk-Kommunikation ・ Festplattenbetrieb -Günstiger Speicher Abgesehen von der obigen Reihenfolge sollten Sie auch CPU, Threads, Async, Ausnahmen usw. verstehen und darauf achten, guten Code zu schreiben.

Kennen Sie den Netzwerkkommunikationscode

Die Netzwerkkommunikation ist langsam. Versuchen Sie daher, die Anzahl der Male so weit wie möglich zu reduzieren. Es ist wichtig zu wissen, wie die Netzwerkkommunikation in C # abläuft. Die Klassen, die die Netzwerkkommunikation durchführen, sind unten aufgeführt.

SqlConnection、SqlCommand

var cn = new SqlConnection();
var cm = new SqlCommand("insert into Table1.....");
cm.Connection = cn;
cn.Open(); //Tritt eine Netzwerkkommunikation auf? Hör zu.
cm.ExecuteNonQuery(); //Netzwerkkommunikation ist aufgetreten!

HttpClient

var cl = new HttpClient();
var res = await cl.GetAsync("https://www.hignull.com"); //Netzwerkkommunikation ist aufgetreten!

Google Calendar

var credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
    Scopes = new[] { CalendarService.Scope.Calendar }
});
var sv = new CalendarService(new BaseClientService.Initializer()
{
    HttpClientInitializer = credential,
    ApplicationName = "MyApp1",
});
var req = new EventsResouce.ListRequest(sv, "calendarID");
var eventList = req.Execute(); //Netzwerkkommunikation ist aufgetreten!

Azure Blob Storage

CloudStorageAccount account = CloudStorageAccount.Parse("My connection string");
var cl = account.CreateCloudBlobClient();
var container = cl.GetContainerReference("My Container name");
var list = container.ListBlobs(); //Netzwerkkommunikation ist aufgetreten!

Hast du ein Gefühl dafür? Netzwerkkommunikation findet statt, wenn Daten durch Aufrufen eines DB-Servers, eines externen WEB-Servers oder der API von Saas (Google, Stripe, Office365, Blob) erfasst werden. Es ist einfach, ein Image zu erhalten, wenn Sie das Programm auf Ihrem eigenen PC ausführen. Mit Google Kalender werden die auf dem Computer des Google-Rechenzentrums gespeicherten Daten erfasst, sodass eine Netzwerkkommunikation stattfindet.

Darüber hinaus werden diese Daten wahrscheinlich auf der Festplatte (oder SSD) gespeichert. Die Ausführung dieser Methoden ist sehr langsam, da es sich um einen Datenfluss handelt, der ebenfalls Zeit in Anspruch nimmt und den Wert durch Netzwerkkommunikation zurückgibt, nachdem eine Netzwerkkommunikation durchgeführt wurde, die sehr lange dauert.

Kennen Sie den Code für Dateioperationen

Neben dem Netzwerk befindet sich der Code für Dateivorgänge. Lassen Sie uns einige Klassen kennen, die Dateioperationen ausführen.

var text = System.IO.File.ReadAllText("C:\\Doc\\MyMemo.txt");

Bei Verwendung der FileStream-Klasse handelt es sich auch um eine Operation auf der Festplatte.

FileStream fs = new FileStream("C:\\Doc\\MyMemo1.txt", FileMode.Create);
StreamWriter sw = new StreamWriter(fs);
sw.WriteLine("WIFI Number 1234-5678-4563");
sw.Close();
fs.Close();

Heapspeicher verstehen

Dies ist eine Tabelle mit Tipps, die erforderlich sind, um den Heapspeicher effizient zu nutzen. ・ Vermeiden Sie unnötige Neuheiten ·Boxen ・ String ist unveränderlich ・ StringBuilder ・ String.Intern -Initialize List korrekt ・ Achten Sie auf die Lambda-Erfassung -Verwenden Sie ArrayPool

Vermeiden Sie unnötige neue

private void Main()
{
	List<UserRecord> userList = new List<UserRecord>();
	userList = GetUserList();
}
private List<UserRecord> GetUserList()
{
    List<UserRecord> userList = new List<UserRecord>();
    // get user list...
    return userList;
}

Unnötige neue Verschwendung von Heap-Speicher. Vermeiden wir es.

Vermeiden Sie beim Vergleichen von Buchstaben beide unteren Buchstaben.

var s1 = "a";
var s2 = "A";
var isSameString = s1.ToLower() == s2.ToLower(); //Speicher wird verschwendet

Verwenden Sie stattdessen Striing.Compare.

var s1 = "a";
var s2 = "A";
var isSameString = String.Compare(s1, s2, StringComparison.CurrentCultureIgnoreCase);

Boxen

Boxing tritt auf, wenn der Werttyp dem Objekttyp zugeordnet ist.

int x = 10;
object o = x; //Boxen ist aufgetreten!

Die Speicherzuweisung erfolgt beim Boxen. Boxing.png

Die Zuweisung von Heapspeicher ist ein zeitaufwändiger Prozess. (Netzwerkkommunikation> Festplattenbetrieb> Heap-Speicherzuordnung) https://ufcpp.net/study/computer/MemoryManagement.html Vermeiden Sie unnötiges Boxen.

Das Umwandeln eines Wertetyps in eine Schnittstelle führt zum Boxen.

struct MyStruct : IComparer<int>
{
    public int Compare(int x, int y)
    {
        return x.CompareTo(y);
    }
}
private Main()
{
	int s = new MyStruct();
	IComparer<int> comparer = s; //Boxen ist aufgetreten!
	s.Compare(0, 1); 
}

Wenn Sie es vermeiden können, vermeiden Sie es.

String verstehen

In erster Linie ist es weniger wichtig, dass String eine Klasse ist, aber unveränderlich. https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/reference-types

Was bedeutet das? Zum Beispiel, wenn Sie den folgenden Code haben

string s = "Hello";
s = "Hello World!";

Möglicherweise haben Sie ein Bild wie die folgende Abbildung als Verhalten des Speichers beim Schreiben dieses Codes. String1.png

Ordnen Sie tatsächlich einen neuen Bereich im Heapspeicher zu, wie unten gezeigt. String2.png

Deshalb soll String unveränderlich sein. Verwenden Sie StringBuilder, wenn Sie den String eine bestimmte Anzahl von Malen ändern möchten.

StringBuilder sb = new StringBuilder();
sb.Append("Hello");
sb.Append("World!");

In diesem Beispiel können Sie den Wert Hello World! Von Anfang an festlegen. In der Realität können Sie jedoch eine Abfrage aus dem Eingabewert des Textfelds erstellen, eine Nachricht an den Benutzer erstellen usw. und in solchen Fällen StringBuilder verwenden. Wird sein.

Wenn Sie Zeichen in String mehrmals verketten oder ändern, wird der Heapspeicher zugewiesen und der zuvor verwendete Wert (in diesem Beispiel Hallo) wird nicht verwendet, und die Anzahl der Speicherbereinigungen wird erhöht.

Verwenden Sie String.Intern

Wenn Sie die Intern-Methode verwenden, um Zeichenfolgen zu internieren, die in Ihrer Anwendung häufig verwendet werden, können Sie den Speicherverbrauch reduzieren und die Leistung verbessern. Zum Beispiel wäre das Wort "Hinzufügen" auf der Schaltfläche "Hinzufügen" ein gutes Ziel für ein Praktikum.

Liste richtig initialisieren

Listenklassenkonstruktoren haben Überladungen, die Kapazität empfangen. Mit dieser Überladung können Sie die Größe des intern vorbereiteten Arrays angeben. Wenn Sie beispielsweise eine Aufgabenlistenseite haben und 50 Aufgaben pro Seite anzeigen möchten, können Sie verhindern, dass das Array neu generiert wird, indem Sie von Anfang an 50 angeben. Dadurch kann der Speicherverbrauch reduziert und die Verarbeitungszeit verkürzt werden.

List<TaskRecord> taskList = new List<TaskRecord>(50);

Der Quellcode für List .

https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs

    public class List<T> : IList<T>, System.Collections.IList, IReadOnlyList<T>
    {
        private const int _defaultCapacity = 4;
        
        static readonly T[]  _emptyArray = new T[0];        
        public List() {
            _items = _emptyArray;
        }
        public List(int capacity) {
            if (capacity < 0) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
            Contract.EndContractBlock();
 
            if (capacity == 0)
                _items = _emptyArray;
            else
                _items = new T[capacity];
        }        

        //Kürzung.............................

        public void Add(T item) {
            if (_size == _items.Length) EnsureCapacity(_size + 1);
            _items[_size++] = item;
            _version++;
        }                
        private void EnsureCapacity(int min) {
            if (_items.Length < min) {
                int newCapacity = _items.Length == 0? _defaultCapacity : _items.Length * 2;
                if ((uint)newCapacity > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength;
                if (newCapacity < min) newCapacity = min;
                Capacity = newCapacity;
            }
        }        

Wenn Sie ein Element hinzufügen und die Länge des internen Arrays überschreiten, wird das Array mit der EnsureCapacity-Methode neu erstellt. Wenn Sie die Größe kennen, geben Sie sie von Anfang an an, um Speicherverschwendung zu vermeiden.

Seien Sie sich der Lambda-Erfassung bewusst

Wenn Sie eine externe Variable mit einem Lambda-Ausdruck erfassen, wird eine Klasse implizit definiert und ihre Instanz erstellt.

public Action GetWriteLineAction(int number)
{
    return () => Console.WriteLine(number);
}

Es kompiliert wie folgt:

public class __GeneratedClass
{
    public int number;
    public void GeneratedMethod()
    {
        Console.WriteLine(number);
    }
}
public Action GetWriteLineAction(int number)
{
    var c = new __GeneratedClass();
    c.number = number;
    return c.GeneratedMethod;
}

Durch das Erfassen einer externen Variablen wird eine Instanz erstellt und Heapspeicher belegt. Seien Sie besonders vorsichtig, wenn Sie Lambda-Ausdrücke in Hot-Code-Pfaden verwenden. Im neuesten C # können Sie einem Lambda-Ausdruck ein statisches Schlüsselwort hinzufügen, um die Erfassung externer Variablen zu verhindern. Wenn Sie also die unbeabsichtigte Erfassung externer Variablen verhindern möchten, verwenden Sie es positiv.

Vermeiden Sie es, statische Methoden an Lambda zu übergeben

Die Ableitung statischer Methoden ist langsam. Der Grund wird nachstehend ausführlich beschrieben ↓ https://ufcpp.net/study/csharp/functional/miscdelegateinternal/#static-method Darüber hinaus gibt es verschiedene Testergebnisse wie die langsame Ableitung statischer Methoden. https://gist.github.com/ufcpp/b2e64d8e0165746effbd98b8aa955f7e

Betrachten Sie den folgenden Code.

static void Main(string[] args)
{
	var textList = new List<String>();
	textList.Add("   "); //Empty
	textList.Add("Higty");
	//....Fügen Sie einige hinzu
	textList.Where(String.IsNullOrWhiteSpace); //Statische Methode
	textList.Where(x => String.IsNullOrWhiteSpace( x ) ); //Mit Lambda umgeben
}

Wenn Sie eine statische Methode als Argument schreiben, wird sie tatsächlich wie folgt kompiliert.

textList.Where(new Func<String, Boolean>(string.IsNullOrWhiteSpace));

Jedes Mal, wenn eine Instanz der Func-Klasse erstellt wird, wird Speicher verschwendet. Darüber hinaus ist der Aufruf selbst sehr langsam, da der Delegatenaufruf für die Instanzmethode optimiert wird.

Wenn Sie es mit Lambda umgeben https://ufcpp.net/study/csharp/sp2_anonymousmethod.html#static

class __GeneratedClass
{
	static Func<String, Boolean> _ActionCache = null;
	internal Boolean __GeneratedMethod(String text)
	{
		return String.IsNullOrWhiteSpace(text);
	}
}
static void Main(string[] args)
{
	if (__GeneratedClass._ActionCache == null)
	{
		__GeneratedClass._ActionCache = __GeneratedClass.__GeneratedMethod;
	}
	textList.Where(__GeneratedClass._ActionCache);
}

Es wird auf diese Weise erweitert und die Erzeugung nutzloser Instanzen wird Null sein. Sie können den verschwendeten Speicher und die Anzahl der Speicherbereinigungen reduzieren. Da es sich um einen Instanzmethodenaufruf handelt, wird der Aufruf nicht verzögert.

Verwenden Sie ArrayPool

Wenn Sie beispielsweise Dateidownloads oder auf Websites hochgeladene Dateien verarbeiten, werden Daten mit Byte [] ausgetauscht. Sie können auch Byte [] im Puffer verwenden. Mit ArrayPool können Sie vermeiden, dem Heapspeicher Byte [] zuzuweisen, und das bereits vorbereitete Byte [] verwenden. http://ikorin2.hatenablog.jp/entry/2020/07/25/113904 Wenn Sie die Zuordnung zum Heap-Speicher vermeiden, können Sie damit rechnen, die Anzahl der Speicherbereinigungen zu verringern.

Verwenden Sie ValueTask anstelle von Task

https://www.buildinsider.net/column/iwanaga-nobuyuki/009 Die Leistung kann verbessert werden, wenn asynchrone Methoden in mehreren Ebenen aufgerufen werden.

Nutzen Sie Span und Stakalloc

Sie können die Leistung verbessern und die Speichernutzung reduzieren, indem Sie Span und Stackalloc für Strings und Arrays verwenden. https://ufcpp.net/blog/2018/12/spanify/

GC (Garbage Collection) verstehen

In C # gibt es einen Mechanismus namens Garbage Collection, bei dem .NET Framework (.NET Core) den Speicher verwaltet. https://docs.microsoft.com/ja-jp/dotnet/standard/garbage-collection/fundamentals Einfach ausgedrückt, der Heap-Speicher wird verwaltet, indem er in drei Generationen unterteilt wird, und der Heap-Speicher, auf den von keiner Stelle mehr verwiesen wird, wird gelöscht, um den freien Speicherplatz zu füllen und den Speicher zu organisieren. https://ufcpp.net/study/csharp/rm_gc.html https://ufcpp.net/study/computer/MemoryManagement.html?sec=garbage-collection#garbage-collection

Wenn der Heapspeicher belegt ist, wird der Speicherbereinigungsprozess irgendwann ausgeführt. Dieser Prozess ist schwer. Versuchen Sie, die Häufigkeit der Speicherbereinigung zu verringern. Insbesondere der Gen2-Erfassungsprozess ist sehr schwer. Reduzieren wir die Anzahl der GC-Vorkommen, indem wir Methoden wie die Nichtverschwendung von Heap-Speicher und die Wiederverwendung von Array-Daten wie Byte [] verwenden.

Tipps, um zu verhindern, dass die Ausführungszeit der Speicherbereinigung lang wird. https://docs.microsoft.com/en-us/previous-versions/dotnet/articles/ms973837(v=msdn.10)

Zusammenfassung -Wenn es viele Zuordnungen gibt, ist die Ausführungszeit lang. -Wenn ein großes Objekt vorhanden ist (z. B. neues Byte [10000]), ist die Ausführungszeit lang. -Wenn es eine Klasse gibt, in der Objekte auf komplizierte Weise aufeinander verweisen, dauert es einige Zeit, um zu überprüfen, ob sie nicht referenziert sind. -Wenn es viele Stammobjekte gibt, dauert es einige Zeit, um zu überprüfen, ob sie nicht referenziert sind.

Was ist ein Finalizer?

https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/destructors Wenn es einen Finalizer gibt, wird die Generation automatisch von einer Generation hochgestuft. Dies bedeutet, dass keine Gen0-Wiederherstellung erfolgt. Mit einem Finalizer ist es ungefähr 300 Mal langsamer. ↓ https://michaelscodingspot.com/avoid-gc-pressure/ Wenn bei der Finalizer-Implementierung ein Problem auftritt, treten die folgenden Probleme auf. https://troushoo.blog.fc2.com/blog-entry-56.html Lass uns aufpassen.

Erfahren Sie mehr über gängige Speicherleckmuster

https://michaelscodingspot.com/ways-to-cause-memory-leaks-in-dotnet/ https://michaelscodingspot.com/find-fix-and-avoid-memory-leaks-in-c-net-8-best-practices/

Ereignishandler Anonyme Methode, Lambda-Ausdruckserfassung Das statische Feld wird zur Wurzel der Garbage Collection Informationen zur WPF-Bindung Wenn die zwischengespeicherte Datenmenge groß ist, ist der Speicher nicht ausreichend und es tritt eine OutOfMemoryException auf. Informationen zur Wochenreferenz Auf den Stapel von Threads (und Timern) geladene Variablen, die nicht enden, werden zum Stamm der Garbage Collection

Wenn ein Speicher verloren geht, wird er nicht freigegeben, der verfügbare Speicherbereich wird kleiner und die Speicherbereinigung wird häufig durchgeführt. Die Speicherbereinigung ist ein schwerer Prozess, der die Gesamtleistung der Anwendung verlangsamt.

Über Ausnahmen

Das Auslösen einer Ausnahme ist ein schwerer Prozess. Vermeiden Sie unnötige Ausnahmen.

private Int32? GetNumber(String text)
{
	try
	{
	    var x = Int32.Parse(text);
	    return x;
	}
	catch (Exception ex)
	{
	    return null;
	}
}

Es gibt eine TryParse-Methode anstelle der Parse-Methode.

private Int32? GetNumber(String text)
{
	if (Int32.TryParse(text, out var x))
	{
		return x;
	}
	return null;
}

Klassenbibliothek, um vorsichtig mit der Verwendung umzugehen

Erstens ist die Datenzugriffsklasse.

Verwenden Sie DataReader anstelle von DataSet

DataSet und DataTable haben verschiedene Funktionen und sind langsam. Wenn Sie nur die Daten lesen möchten, verwenden Sie DataReader.

Verarbeiten Sie mehrere Zeilen mit Table-Valued Parameter oder SqlBulkCopy

Verwenden Sie TVP (Table-Valued Parameter) oder SqlBulkCopy, um mehrere Zeilen gleichzeitig zu verarbeiten. https://www.sentryone.com/blog/sqlbulkcopy-vs-table-valued-parameters-bulk-loading-data-into-sql-server TVP ist schneller für Aufzeichnungen unter 1000. Dies liegt daran, dass die Initialisierung der Masseneinfügung einige Zeit in Anspruch nimmt. https://docs.microsoft.com/en-us/sql/relational-databases/tables/use-table-valued-parameters-database-engine?view=sql-server-ver15#BulkInsert Verwenden Sie zum Einfügen von mehr als 1000 Datensätzen SqlBulkCopy, um die Daten einzufügen.

Das Aufrufen von TVP, das in C # gespeichert ist, kann ziemlich ärgerlich sein. Mit DbSharp ist es einfach, automatisch C # -Aufrufcode zu generieren. Einführung von DbSharp (Tool zur automatischen Generierung von C # -Quellcode, das gespeicherte Daten ausführt) Lassen Sie es uns nutzen.

Achten Sie auf die Schlüssel im Wörterbuch

GetHashCode wird verwendet, um die Schlüssel im Wörterbuch zu vergleichen. Schlüsselvergleiche sind langsam, wenn String oder Enum als Schlüssel verwendet werden. http://proprogrammer.hatenadiary.jp/entry/2014/08/17/015345 Dies ist eine Methode zur Beschleunigung, wenn Enum als Schlüssel verwendet wird ↓ https://stackoverflow.com/questions/26280788/dictionary-enum-key-performance/26281533 Verwenden Sie nach Möglichkeit Int32 usw. als Schlüssel.

Verwenden Sie Async, warten Sie

Verwenden Sie nicht die Wait () -Methode oder die Result-Eigenschaft. Der Thread wird blockiert, bis der Vorgang abgeschlossen ist. Verwenden Sie Async, um Threads während der Latenz freizugeben. Der freigegebene Thread wird verwendet, um eine andere Verarbeitung durchzuführen. Anfragen werden auf Websites usw. parallel ausgeführt, und es wird auf die Datenerfassung aus der Datenbank gewartet. Indem alles asynchron gemacht wird, muss der Thread nicht nur warten, ohne etwas zu tun, sondern er führt immer die Verarbeitung einer der Anforderungen aus, sodass der Gesamtdurchsatz verbessert wird.

Verwenden Sie Stream

Daten können mithilfe von Stream nacheinander gelesen und geschrieben werden. Wenn du kannst, benutze es.

Verwenden Sie keinen HttpClient

Laut der offiziellen Dokumentation erfordert HttpClient eine spezielle Verwendung. https://docs.microsoft.com/ja-jp/dotnet/api/system.net.http.httpclient?view=netcore-3.1 Es ist ein Referenzartikel der richtigen Verwendung ↓ https://qiita.com/superriver/items/91781bca04a76aec7dc0

DI und Wiederholungsversuch können einfach mit HttpClientFactory eingestellt werden. https://docs.microsoft.com/en-US/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests

Verhindern Sie Speicherfragmentierung und Speichermangel, wenn Sie Socket verwenden

Byte [] wird zum Senden und Empfangen von Daten mit Socket verwendet.

private voi Send()
{
	var buffer = new Byte[1024];
	socket.BeginSend(buffer, ...);
}

Das an BeginSend übergebene Byte [] ist der E / A-Abschlussport, und Daten werden vom Betriebssystem geschrieben, wenn die E / A abgeschlossen ist. Sie müssen sicherstellen, dass das Betriebssystem fehlerfrei an diese Adresse schreiben kann. Dieser Puffer wird mit einem PIN versehen, um zu verhindern, dass sich die Adresse bei der Verarbeitung der Garbage Collection verschiebt. http://ahuwanya.net/blog/post/Buffer-Pooling-for-NET-Socket-Operations

Das Problem ist beispielsweise, dass SendCallback 1000 Mal ausgeführt wird, um 1 MB Daten zu senden, wenn Sie Daten mit einem Puffer der Größe 1024 senden, 1000 Instanzen von Byte [] generiert werden und alle PINd-Instanzen sind. Ich werde. Das PINd-Objekt wird nicht in die Garbage Collection verschoben, was zu einer Speicherfragmentierung führt. Infolgedessen tritt nicht genügend Speicher auf.

Reflexion, Ausdrucksbaum, dynamisch, asynchron, Thread

Zusätzlich werden wir verschiedene Beschleunigungstipps vorstellen.

Cache-Reflexion

Reflexion ist ein sehr langsamer Prozess.

List<UserRecord> userList = GetUserList();
foreach (var user in userList)
{
	PropertyInfo namePropertyInfo = Typeof(UserRecord).GetProperty("Name"); //Sehr langsam!
	String userName = (String)namePropertyInfo.GetValue(u);
}

Reduzieren wir die Anzahl der Ausführungen und vermeiden Sie eine Verlangsamung.

PropertyInfo namePropertyInfo = Typeof(UserRecord).GetProperty("Name");
 
List<UserRecord> userList = GetUserList();
foreach (var user in userList)
{
	String userName = (String)namePropertyInfo.GetValue(u);
}

GetProperty, GetMethod usw. sind langsam. Verwenden Sie einen Cache, um Verlangsamungen zu vermeiden. https://yamaokuno-usausa.hatenablog.com/entry/20090821/1250813986

Nutzen Sie die Dynamik

Wenn Sie Dynamic verwenden, führt der Compiler den Cache bis zu einem gewissen Grad aus ↓ https://ufcpp.net/study/csharp/misc_dynamic.html

Verwenden Sie generisches Caching

Das generische Typ-Caching ist schneller, da die Zuordnung zur Kompilierungszeit vollständig ist. Verwenden wir es, wenn der Typ statisch bestimmt werden kann. http://csharpvbcomparer.blogspot.com/2014/03/tips-generic-type-caching.html

Kompilierung des Cache-Ausdrucksbaums

Das Kompilieren von Expression ist ein schwerer Prozess. Stellen Sie sicher, dass die kompilierten Delegaten zwischengespeichert werden.

Kompilieren Sie die Regex-Klasse

Kompilieren Sie die Regex-Klasse mit dem folgenden Code. var rg= new Regex("[a-zA-Z0-9-]*", RegexOptions.Compiled); Sie können eine Leistungsverbesserung erwarten, indem Sie diese vorkompilieren.

Beschleunigen Sie ToString von Enum

Enums ToString verwendet die interne Reflexion. https://referencesource.microsoft.com/#mscorlib/system/type.cs,d5cd3cb0c6c2b6c1

public override String ToString()
{
    return Enum.InternalFormat((RuntimeType)GetType(), GetValue());
}
//************************Kürzung***************************
private void GetEnumData(out string[] enumNames, out Array enumValues)
{
    Contract.Ensures(Contract.ValueAtReturn<String[]>(out enumNames) != null);
    Contract.Ensures(Contract.ValueAtReturn<Array>(out enumValues) != null);

    FieldInfo[] flds = GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);

    object[] values = new object[flds.Length];
    string[] names = new string[flds.Length];

    for (int i = 0; i < flds.Length; i++)
    {
        names[i] = flds[i].Name;
        values[i] = flds[i].GetRawConstantValue();
    }

    // Insertion Sort these values in ascending order.
    // We use this O(n^2) algorithm, but it turns out that most of the time the elements are already in sorted order and
    // the common case performance will be faster than quick sorting this.
    IComparer comparer = Comparer.Default;
    for (int i = 1; i < values.Length; i++)
    {
        int j = i;
        string tempStr = names[i];
        object val = values[i];
        bool exchanged = false;

        // Since the elements are sorted we only need to do one comparision, we keep the check for j inside the loop.
        while (comparer.Compare(values[j - 1], val) > 0)
        {
            names[j] = names[j - 1];
            values[j] = values[j - 1];
            j--;
            exchanged = true;
            if (j == 0)
                break;
        }

        if (exchanged)
        {
            names[j] = tempStr;
            values[j] = val;
        }
    }

    enumNames = names;
    enumValues = values;
}

Es ist sehr langsam, weil es Reflexion verwendet. So beschleunigen Sie es ↓ https://qiita.com/higty/items/513296536d3b26fbd033

Entfalte die Schleife

Sie können beschleunigen, indem Sie die Schleife erweitern. Angenommen, Sie möchten alle Werte in einem Array um 1 erhöhen.

var numberList = new Int32[40000];
for (var i = 0; i < numberList.Length; i++)
{
	numberList[i] += 1;
}

Erweitern Sie die Schleife und ändern Sie sie, um die vier Elemente gleichzeitig zu behandeln.

var numberList = new Int32[40000];
for (var i = 0; i < 10000; i = i + 4)
{
	numberList[i] += 1;
	numberList[i + 1] += 1;
	numberList[i + 2] += 1;
	numberList[i + 3] += 1;
}

In der Schleifenverarbeitung werden die Ausdrucks- (i <numberList.Length) und Inkrementverarbeitung (i ++) ausgeführt, die die Bedingung überprüft, ob die Verarbeitung jedes Mal wiederholt werden soll. Durch das Erweitern wird die obige Verarbeitung von 40.000 Mal zu 10000 Mal, und die Verarbeitung der Bewertung und das Inkrementieren des Zustands von 30.000 Mal wird eliminiert. Wenn die Anzahl der Elemente im Array nicht ein Vielfaches von 4 ist, sind einige Anpassungen erforderlich, aber die Schleifenerweiterung kann verwendet werden, um den Prozess zu beschleunigen. Sie können noch schneller werden, indem Sie 10 oder mehr anstelle von 4 hinzufügen.

Verwenden Sie für statt für jeden

foreach ist etwas langsamer als for, da es auf die Current-Eigenschaft zugreift und jedes Mal die MoveNext-Methode aufruft. Es ist für die meisten Teile des Tages vernachlässigbar, aber wenn Sie nach sehr gravierenden Leistungsverbesserungen suchen, sollten Sie die Verwendung von for in Betracht ziehen. Insbesondere für die Kombination von Array und für wird der Compiler viele Optimierungen vornehmen.

Vermeiden Sie das Schreiben im LINQ-Stil und verwenden Sie foreach

Aufrufe, die LINQ-ähnliche Methodenketten verbinden, können aufgrund der Anzahl der Methodenaufrufe etwas langsam oder ziemlich langsam sein. Siehe den Austausch von Kommentaren auf dieser Seite ↓ https://michaelscodingspot.com/performance-problems-in-csharp-dotnet/

Schreiben im LINQ-Stil

var numbers = GetNumbers();
int total = numbers.Where(n => n % 2 == 0).Sum(n => n * 2);
return total;

Der Optimierer optimiert den LINQ-Stil so weit wie möglich, aber der normale foreach ist normalerweise schneller.

var numbers = GetNumbers();
int total = 0;
foreach (var number in numbers)
{
	if (number % 2 == 0)
	{
		total += number * 2;
	}
}
return total;

Informationen zum Kopieren von Arrays

Quelle ↓ https://stackoverflow.com/questions/5099604/any-faster-way-of-copying-arrays-in-c https://marcelltoth.net/article/41/fastest-way-to-copy-a-net-array https://www.youtube.com/watch?time_continue=4&v=-H5oEgOdO6U&t=1689 Laut diesem Video scheint Array.CopyTo das schnellste zu sein. Der Grund ist, dass die Betriebssystem-API direkt aufgerufen wird.

Array-Geheimnisse nicht in der Dokumentation enthalten

https://www.codeproject.com/Articles/3467/Arrays-UNDOCUMENTED

Array-Schleife ist die schnellste

int hash = 0;
for (int i=0; i< a.Length; i++)
{
    hash += a[i];
}

Das Speichern der Länge in einer lokalen Variablen ist langsam

int hash = 0;
int length = a.length;
for (int i=0; i< length; i++)
{
    hash += a[i];
}

Im Fall des folgenden Codes ist es schwierig zu garantieren, dass die Längenvariable nicht geändert wurde (das Kompilieren dauert einige Zeit), wenn Änderungen von anderen Threads berücksichtigt werden. Infolgedessen funktioniert die Grenzwertprüfung von Array, was sich verlangsamt. Im ersten Beispiel wird bestätigt, dass a.Length innerhalb der Länge des Arrays liegt, sodass die Grenzwertprüfung weggelassen wird und der Zwischencode, der die Verarbeitung für jedes Element des Arrays optimiert, generiert wird, sodass er schneller ist. ..

Verwenden Sie Felder anstelle von Eigenschaften

Eigenschaften sind eigentlich Methoden. Beim Zugriff auf die Eigenschaft wird ein Methodenaufruf eingefügt. Selbst die Kosten für Methodenaufrufe können in Umgebungen, in denen die Leistung von entscheidender Bedeutung ist, nicht akzeptabel sein. In solchen Fällen sollten Sie Felder anstelle von Eigenschaften verwenden.

Achten Sie auf den Index der Schleife

Wenn die Indexreihenfolge der Doppelschleife falsch ist, kann die Speicherlokalität der CPU nicht gut genutzt werden und die Verarbeitung kann verlangsamt werden. https://raygun.com/blog/c-sharp-performance-tips-tricks/

Code, der die Speicherlokalität nutzen kann

for (int i = 0; i < _map.Length; i++)
{
	for (int n = 0; n < _map.Length; n++)
	{
  		if (_map[i][n] > 0)
  		{
    	    result++;
  		}
	}
}

Code, der nicht gut verwendet werden kann

for (int i = 0; i < _map.Length; i++)
{
	for (int n = 0; n < _map.Length; n++)
	{
  		if (_map[n][i] > 0)
  		{
    	    result++;
  		}
	}
}

Der obige Code ist ungefähr achtmal schneller. Die vom obigen Code verwendeten Daten befinden sich näher am Speicher und können sofort gelesen werden.

Machen Sie die Benutzeroberfläche immer ansprechbar

Benutzer sind gestresst, wenn die Benutzeroberfläche nicht reagiert. Lassen Sie uns die Verarbeitung ausführen, die im Hintergrund-Thread lange dauert, und den UI-Thread immer reagieren lassen. Verwenden Sie die Thread-Klasse oder die asynchrone und warten Sie auf Muster.

Implementieren Sie die Cache-Funktion

Der Zugriff wird beschleunigt, indem DB-Daten, durch Netzwerkkommunikation erfasste Daten, durch Dateivorgänge erfasste Daten usw. im Speicher oder in Redis abgelegt werden. Daten mit den folgenden Eigenschaften sind zum Zwischenspeichern geeignet. ・ Kleine Datenmenge ・ Wird häufig erwähnt ・ Nicht viel verändert Wenn Sie zu viele Daten zwischenspeichern, um sie im Speicher zu halten, ist der Speicher erschöpft. Das Zwischenspeichern von Dingen, auf die weniger verwiesen wird, verschwendet wertvollen Speicherplatz. Wenn die Quelldaten aktualisiert werden, müssen Sie den Cache aktualisieren. Das Zwischenspeichern häufig aktualisierter Daten verwendet CPU und Speicher für den Aktualisierungsprozess, sodass diese Daten nicht zum Zwischenspeichern geeignet sind.

Basierend auf dem oben Gesagten sind beispielsweise die folgenden Daten als Cache-Ziel geeignet.

-Anwendungstext (das Wort "Hinzufügen" in der Schaltfläche "Hinzufügen" bleibt unverändert) ・ Organisationsstamm (nur einmal im Jahr geändert) ・ HTML der Verkaufsdatentabelle des letzten Monats (die Verkaufsdaten des letzten Monats bleiben unverändert) Und so weiter.

public class CacheData
{
	public static Dictionary<String, String> TextList = new Dictionary<String, String>();
}

Grundsätzlich wird es mit statischen Feldern (oder Eigenschaften) implementiert. Aktualisieren Sie diese Daten, wenn die Originaldaten aktualisiert werden. Im Fall einer WEB-Anwendung ist es erforderlich, Konflikte mit Sperren usw. zu verwalten, da von mehreren Threads aus auf sie zugegriffen wird.

Wenn Sie WEB-Apps in der Cloud skalieren, können Sie den Cache aller WEB-Apps aktualisieren, indem Sie mithilfe von Redis Publish and Subscribe Benachrichtigungen an mehrere WEB-Apps senden.

In diesem Artikel wird C # als Beispiel verwendet, um den Cache einzuführen. Der Cache-Mechanismus selbst ist jedoch hilfreich. Der Cache-Mechanismus wird an verschiedenen Stellen verwendet, z. B. in CDN-, DNS- und Bilddateien von Browsern.

Der Debug-Build ist langsam und der Release-Build ist schnell

Vergiss nicht, es zu einem Release-Build zu machen.

Bonus

Ich habe einige Artikel für diejenigen geschrieben, die daran interessiert sind, Hochleistungsanwendungen zu erstellen. Ich habe versucht, Task-Management-Tools und -Dienste zu erstellen, um sie vom Anfänger an zu verbessern- Techniken und Kenntnisse, die Sie benötigen, um die Leistung von WEB-Anwendungen zu verbessern Ich habe die schnellste Mapper-Bibliothek der Welt mit C # erstellt (3 bis 10 Mal schneller als AutoMapper usw.) Funktionales UI-Design für Programmierer Fortgeschrittene technische Artikel, um ein Ingenieur von Weltklasse zu werden

Blinddarm

Recommended Posts

Grundkenntnisse der Informatik und ihrer Praxis, die Sie zur Leistungssteigerung kennen sollten
[Rails] Eine Sammlung von Tipps, die sofort zur Verbesserung der Leistung hilfreich sind
Grundlegende Java-Grammatik sollten Sie zuerst kennen