[JAVA] Connaissance de base de l'informatique et de sa pratique que vous devez connaître pour améliorer les performances

introduction

J'aimerais partager cet article avec Qiita pour contribuer à la communauté. https://academy.tinybetter.com/Article/37407ec1-cd3a-50c8-2fa1-39f6ed3dae74/View

Il y a beaucoup d'articles sur le net, mais les informations sont fragmentées et il est difficile pour les débutants qui ont commencé le programme d'avoir une vue d'ensemble. Je ne vois pas beaucoup d'articles qui résument les connaissances de base et les conseils de performance de l'informatique, alors je les ai résumés.

Mesurez tout

Le principe le plus important de l'optimisation des performances est la mesure. Je vais résumer les outils de mesure.

■BenchmarkDotNet https://benchmarkdotnet.org/articles/overview.html Un outil pour mesurer le code C #. Disponible auprès de Nuget. Utilisez ceci au lieu de la classe Chronomètre. Le chronomètre ne peut pas mesurer correctement les effets de GC, etc.

■ Outil de profilage Visual Studio https://docs.microsoft.com/ja-jp/visualstudio/profiling/?view=vs-2019 Vous pouvez utiliser les outils de profilage de Visual Studio pour vérifier l'état du processeur, de la mémoire, des threads, du GC, de la LOH, etc. Vous pouvez trouver rapidement quel code dans quelle méthode est le chemin critique en regardant l'arborescence des appels.

■Fiddler https://www.telerik.com/fiddler C'est un outil qui capture le contenu du réseau. https://zappysys.com/blog/how-to-use-fiddler-to-analyze-http-web-requests/

■ Outils de développement de navigateur Vous pouvez également capturer le contenu du réseau avec les outils de développement de votre navigateur sans avoir à installer Fiddler. Fiddler semble être plus sophistiqué, mais si vous voulez simplement le rechercher facilement, vous pouvez utiliser les outils de développement du navigateur.

■Wireshark https://www.wireshark.org/download.html Logiciel capable de capturer la communication TCP / IP. https://knowledge.sakura.ad.jp/6286/ https://hldc.co.jp/blog/2020/04/15/3988/

Cela peut ne pas être très utile dans le développement d'applications, mais il peut arriver un moment où vous souhaiterez approfondir la communication. Souvenons-nous.

■WinDbg https://docs.microsoft.com/ja-jp/windows-hardware/drivers/debugger/debugger-download-tools Vous pouvez obtenir et analyser les fichiers de vidage Windows. Vous pouvez effectuer une trace de pile et analyser la méthode qui en est la cause. 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 [Première étape de l'analyse de vidage sur incident](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 /)

Je pense que le contenu de cet article est généralement correct, mais il peut y avoir des mensonges. Puisque C # a évolué et a changé par rapport au passé, il est important de ne pas avaler les informations sur le net, mais de les mesurer et de les confirmer par vous-même.

Comprendre les ordinateurs et les réseaux

Comprendre les principes des ordinateurs et des réseaux est essentiel pour comprendre l'impact sur les performances. Un ordinateur se compose d'un processeur, d'une mémoire et d'un disque dur. Le processeur est le processus de calcul, et la mémoire et le disque dur sont les périphériques de stockage. La mémoire est en outre subdivisée en plusieurs types.

En raison de l'amélioration du traitement des calculs du processeur, il n'est plus possible de lire et d'écrire dans la mémoire. En conséquence, un mécanisme appelé mémoire cache a été préparé. https://ascii.jp/elem/000/000/563/563800/ La mémoire cache (principale) est la plus rapide, et la mémoire cache (secondaire) → la mémoire cache (tertiaire) → la mémoire de pile → la mémoire de tas → SSD → le disque dur, et ainsi de suite. Le disque dur a la plus grande capacité. De plus, le prix deviendra moins cher.

À propos, afin d'envoyer une certaine chaîne de caractères par communication réseau, convertir les données de caractères en octets, résoudre le nom avec DNS, identifier le PC cible et échanger avec TCP / IP avec une prise de contact à trois. Il s'agit d'un processus d'augmentation graduelle de la quantité de transmission dans la fenêtre coulissante. https://www.infraexpert.com/study/tcpip11.html https://ascii.jp/elem/000/000/619/619702/

Puisque le processus est effectué comme décrit ci-dessus, la communication réseau est un processus très long. La communication est finalement convertie en un signal de tension ON / OFF, et le signal de tension est perdu en raison d'interférences extérieures telles que les ondes radio sur le câble, donc TCP / IP le détecte et le renvoie à nouveau. Cela se produira, donc ce sera plus lent. UDP est un peu plus rapide, mais dans tous les cas, la communication réseau est le processus le plus lent de la programmation.

Comprendre les priorités

Maintenant que vous avez une meilleure compréhension des ordinateurs et des réseaux, vous en apprendrez davantage sur les priorités et les approches de base. Nous expliquerons les chemins de code à chaud, les appels réseau, les E / S de fichiers, la mémoire de tas, les threads, asynchrones, etc. Cette fois, j'expliquerai la partie pratique en utilisant C # et .NET comme exemples, mais l'idée de base de la communication réseau, de la mémoire de tas et du ramasse-miettes est la même pour Python, Java, etc.

Chemin du code actif

La première chose que vous devez comprendre est le concept de chemins de code à chaud. L'intérieur du traitement itératif tel que l'instruction for est exécuté plusieurs fois. Par exemple, considérez l'écran suivant. tasklist.png

Pour créer un tel écran, vous devrez écrire un traitement itératif avec une instruction for, etc. dans l'ordre suivant. Plusieurs utilisateurs → plusieurs dates → plusieurs tâches Conceptuellement, cela ressemble à ceci.

private void ShowCalendar()
{
	List<UserRecord> userList = GetUserList(); //Communication réseau partie 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); //Communication réseau partie 2
        	foreach (var task in taskList)
        	{
            	CreateTaskTableCell(task);
        	}
    	}
	}
}
private void CreateTaskTableCell(TaskRecord record)
{
    //HOT PATH!!!!!!

    // Get color of Task
    var color = GetColorFromDatabase(); //Communication réseau partie 3
}

Disons que ça ressemble à ça. Il existe trois codes de communication réseau.

Le code à l'intérieur de la méthode CreateTaskTableCell est le chemin du code actif. Cette méthode est appelée très souvent. Par exemple, si vous avez 100 utilisateurs, 21 jours et une moyenne de 5 tâches par jour, vous serez appelé 10500 fois. Si vous écrivez un code (partie 3 de la communication réseau) tel que l'acquisition de données de couleur à partir de la base de données, si l'accès à la base de données est de 10 millisecondes, cela prendra 10 millisecondes x 10500 = 105 secondes. Cela prendra moins de deux minutes pour que la page apparaisse. La communication réseau 2 est 2100 fois, ce qui est mieux que la communication réseau 3, mais cela doit également être amélioré. La communication réseau n ° 1 n'est appelée qu'une seule fois, vous pouvez donc la laisser telle quelle.

La partie du code qui est fréquemment exécutée comme celle-ci est appelée le chemin du code actif, et il est nécessaire d'améliorer ce code de préférence. Cette vidéo https://www.youtube.com/watch?v=4yALYEINbyI À 12h10 20% du code consomme 80% des ressources. 4,0% du code consomme 64% des ressources. 0,8% du code consomme 51% des ressources. Est en train de dire. Vous pouvez voir comment le chemin du code actif consomme des ressources.

Améliorez-vous de l'endroit avec le plus grand impact

Le code de programme à noter est souvent dans l'ordre suivant: · Communication réseau ・ Fonctionnement du disque dur -Heap mémoire Outre l'ordre ci-dessus, vous devez également comprendre le processeur, les threads, l'asynchrone, les exceptions, etc. et faire attention à écrire un bon code.

Connaître le code de communication réseau

La communication réseau est lente, essayez donc de réduire le nombre de fois autant que possible. Il est important de connaître la méthode dans laquelle la communication réseau se produit en C #. Les classes qui effectuent la communication réseau sont répertoriées ci-dessous.

SqlConnection、SqlCommand

var cn = new SqlConnection();
var cm = new SqlCommand("insert into Table1.....");
cm.Connection = cn;
cn.Open(); //Une communication réseau a-t-elle lieu? Vérifiez-le.
cm.ExecuteNonQuery(); //Une communication réseau s'est produite!

HttpClient

var cl = new HttpClient();
var res = await cl.GetAsync("https://www.hignull.com"); //Une communication réseau s'est produite!

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(); //Une communication réseau s'est produite!

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(); //Une communication réseau s'est produite!

Avez-vous une idée? La communication réseau se produit lorsque les données sont acquises en appelant un serveur DB, un serveur WEB externe ou l'API de Saas (Google, Stripe, Office365, Blob). Il est facile d'obtenir une image si vous exécutez le programme sur votre propre PC. Avec Google Agenda, les données stockées sur l'ordinateur du centre de données de Google sont acquises, ce qui permet une communication réseau.

De plus, ces données sont probablement stockées sur le disque dur (ou SSD). L'exécution de ces méthodes est très lente car il s'agit d'un flux de lecture de données à partir du disque dur, qui prend également du temps, et de restitution de la valeur par communication réseau après avoir effectué une communication réseau qui prend très longtemps.

Connaître le code des opérations sur les fichiers

À côté du réseau se trouve le code des opérations sur les fichiers. Connaissons quelques classes qui effectuent des opérations sur les fichiers.

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

Lorsque vous utilisez la classe FileStream, il s'agit également d'une opération sur le disque dur.

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();

Comprendre la mémoire du tas

Voici un tableau des conseils nécessaires pour utiliser efficacement la mémoire du tas. ・ Évitez les nouvelles ·boxe ・ La chaîne est immuable ・ StringBuilder ・ String.Intern -Initialiser correctement la liste ・ Soyez conscient de la capture lambda -Utiliser ArrayPool -Utiliser ValueTask au lieu de Task ・ Utilisez Span et StackAlloc

Évitez les nouveaux

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;
}

Une nouvelle mémoire inutile gaspille de la mémoire. Evitons cela.

Évitez de faire les deux lettres minuscules lorsque vous comparez des lettres.

var s1 = "a";
var s2 = "A";
var isSameString = s1.ToLower() == s2.ToLower(); //La mémoire est gaspillée

Utilisez plutôt Striing.Compare.

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

boxe

La boxe se produit lorsque le type de valeur est affecté au type d'objet.

int x = 10;
object o = x; //La boxe a eu lieu!

L'allocation de mémoire se produit lors de la boxe. Boxing.png

L'allocation de mémoire de tas est un processus qui prend du temps. (Communication réseau> Fonctionnement du disque dur> Allocation de mémoire de tas) https://ufcpp.net/study/computer/MemoryManagement.html Évitez la boxe inutile.

Le cast d'un type valeur vers une interface provoque la boxe.

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; //La boxe a eu lieu!
	s.Compare(0, 1); 
}

Si vous pouvez l'éviter, évitez-le.

Comprendre la chaîne

D'abord et avant tout, la chose la moins importante écrite est que String est une classe mais immuable. https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/reference-types

Qu'est-ce que cela signifie? Par exemple, si vous avez le code suivant

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

Vous pouvez avoir une image comme la figure ci-dessous comme le comportement de la mémoire lors de l'écriture de ce code. String1.png

En fait, allouez une nouvelle zone dans la mémoire du tas comme indiqué ci-dessous. String2.png

C'est pourquoi String est dit immuable. Utilisez StringBuilder si vous souhaitez modifier la chaîne un certain nombre de fois.

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

Dans cet exemple, vous pouvez définir la valeur Hello World! Depuis le début, mais en réalité, vous pouvez créer une requête à partir de la valeur d'entrée de la zone de texte, créer un message à l'utilisateur, etc., et utiliser StringBuilder dans de tels cas. Sera.

Si vous concaténez ou modifiez des caractères dans String plusieurs fois, la mémoire du tas sera allouée et la valeur précédemment utilisée (Hello dans cet exemple) ne sera pas utilisée, et le nombre de garbage collection augmentera.

Utilisez String.Intern

Si vous utilisez la méthode Intern pour interner des chaînes qui sont utilisées plusieurs fois dans votre application, vous pouvez réduire la consommation de mémoire et améliorer les performances. Par exemple, le mot «ajouter» sur le bouton d'ajout serait une bonne cible pour un stage.

Initialiser correctement la liste

Les constructeurs de classe de liste ont des surcharges qui reçoivent de la capacité. Cette surcharge vous permet de spécifier la taille du tableau préparé en interne. Par exemple, si vous disposez d'une page de liste de tâches et que vous souhaitez afficher 50 tâches par page, vous pouvez empêcher la régénération du tableau en spécifiant 50 depuis le début. En conséquence, on peut s'attendre à réduire la consommation de mémoire et à raccourcir le temps de traitement.

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

Le code source de 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];
        }        

        //réduction.............................

        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;
            }
        }        

Si vous ajoutez un élément et dépassez la longueur du tableau interne, la méthode EnsureCapacity est utilisée pour recréer le tableau. Si vous connaissez la taille, spécifiez-la dès le début pour éviter de gaspiller de la mémoire.

Soyez conscient de la capture lambda

Lorsque vous capturez une variable externe avec une expression lambda, une classe est définie implicitement et son instance est créée.

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

Il se compile comme ceci:

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;
}

La capture d'une variable externe crée une instance et consomme de la mémoire du tas. Soyez particulièrement prudent lorsque vous utilisez des expressions lambda dans des chemins de code actif. Dans le dernier C #, vous pouvez ajouter un mot clé statique à une expression lambda pour interdire la capture de variables externes, donc si vous souhaitez empêcher la capture involontaire de variables externes, utilisez-le de manière positive.

Évitez de passer des méthodes statiques à lambda

La dérivation des méthodes statiques est lente. La raison est décrite en détail ci-dessous ↓ https://ufcpp.net/study/csharp/functional/miscdelegateinternal/#static-method En outre, il existe divers résultats de test tels que la dérivation lente de méthodes statiques. https://gist.github.com/ufcpp/b2e64d8e0165746effbd98b8aa955f7e

Considérez le code suivant.

static void Main(string[] args)
{
	var textList = new List<String>();
	textList.Add("   "); //Empty
	textList.Add("Higty");
	//....Ajoute-en
	textList.Where(String.IsNullOrWhiteSpace); //Méthode statique
	textList.Where(x => String.IsNullOrWhiteSpace( x ) ); //Entourer de lambda
}

Si vous écrivez une méthode statique comme argument, elle sera en fait compilée comme suit.

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

Chaque fois qu'une instance de la classe Func est créée, elle gaspille de la mémoire. De plus, l'appel lui-même est très lent en raison de l'optimisation de l'appel du délégué vers la méthode d'instance.

Si vous l'entourez de lambda 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);
}

Il sera étendu comme ça, et la génération d'instances inutiles sera nulle. Vous pouvez réduire le gaspillage de mémoire et réduire le nombre de garbage collection. De plus, puisqu'il s'agit d'un appel de méthode d'instance, l'appel ne sera pas retardé.

Utiliser ArrayPool

Par exemple, lors du traitement des téléchargements de fichiers ou du traitement des fichiers téléchargés sur des sites Web, les données sont échangées à l'aide de Byte []. Vous pouvez également utiliser Byte [] dans le tampon. En utilisant ArrayPool, vous pouvez éviter d'allouer Byte [] à la mémoire du tas et utiliser le Byte [] déjà préparé. http://ikorin2.hatenablog.jp/entry/2020/07/25/113904 En évitant l'allocation à la mémoire du tas, vous pouvez vous attendre à réduire le nombre de garbage collection.

Utilisez ValueTask au lieu de Task

https://www.buildinsider.net/column/iwanaga-nobuyuki/009 Les performances peuvent être améliorées si les méthodes asynchrones sont appelées dans plusieurs couches.

Tirer parti de Span et Stakalloc

Vous pouvez améliorer les performances et réduire l'utilisation de la mémoire en utilisant Span et Stackalloc pour les chaînes et les tableaux. https://ufcpp.net/blog/2018/12/spanify/

Comprendre GC (Garbage Collection)

En C #, il existe un mécanisme appelé garbage collection dans lequel le .NET Framework (.NET Core) gère la mémoire. https://docs.microsoft.com/ja-jp/dotnet/standard/garbage-collection/fundamentals En termes simples, c'est un mécanisme qui gère la mémoire du tas divisée en trois générations, efface la mémoire du tas qui n'est plus référencée de n'importe où, remplit l'espace libre et organise la mémoire. https://ufcpp.net/study/csharp/rm_gc.html https://ufcpp.net/study/computer/MemoryManagement.html?sec=garbage-collection#garbage-collection

Lorsque la mémoire du tas est consommée, le processus de garbage collection est exécuté à un moment donné. Ce processus est lourd. Essayez de réduire le nombre de fois que le garbage collection se produit. En particulier, le processus de collecte Gen2 est très lourd. Réduisons le nombre d'occurrences de GC en utilisant des méthodes telles que ne pas gaspiller la mémoire du tas et réutiliser des données de tableau telles que Byte [].

Conseils pour éviter que le temps d'exécution du garbage collection ne devienne long. https://docs.microsoft.com/en-us/previous-versions/dotnet/articles/ms973837(v=msdn.10)

Résumé -S'il y a beaucoup d'allocations, le temps d'exécution sera long. -S'il y a un objet énorme (comme new Byte [10000]), le temps d'exécution sera long. -S'il existe une classe dans laquelle les objets se réfèrent entre eux de manière compliquée, il faut du temps pour vérifier qu'ils ne sont pas référencés. -S'il existe de nombreux objets racine, il faudra du temps pour vérifier qu'ils ne sont pas référencés. -Mettre en œuvre avec soin la création d'une classe avec un finaliseur.

Qu'est-ce qu'un finaliseur?

https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/destructors S'il y a un finaliseur, la génération sera automatiquement promue d'une génération. Cela signifie qu'il n'y aura pas de récupération Gen0. Avec un finaliseur, c'est environ 300 fois plus lent. ↓ https://michaelscodingspot.com/avoid-gc-pressure/ En outre, s'il y a un problème avec l'implémentation du finaliseur, les problèmes suivants se produiront. https://troushoo.blog.fc2.com/blog-entry-56.html Faisons attention.

En savoir plus sur les modèles de fuite de mémoire courants

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/

Gestionnaire d'événements Méthode anonyme, capture d'expression lambda Le champ statique devient la racine du garbage collection À propos de la liaison WPF Si la quantité de données à mettre en cache est importante, la mémoire sera insuffisante et OutOfMemoryException se produira. À propos de la référence de la semaine Les variables chargées sur la pile de threads (et les minuteries) qui ne se terminent pas deviennent la racine du garbage collection

Si une mémoire fuit, elle ne sera pas libérée, la zone de mémoire disponible diminuera et le garbage collection se produira fréquemment. Le garbage collection est un processus lourd qui ralentit les performances globales des applications.

À propos des exceptions

Lever une exception est un processus lourd. Évitez de soulever des exceptions inutiles.

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

Il existe une méthode TryParse au lieu de la méthode Parse.

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

Bibliothèque de classes pour faire attention à l'utilisation

La première est la classe d'accès aux données.

Utilisez DataReader plutôt que DataSet

DataSet et DataTable ont diverses fonctions et sont lents. Si vous souhaitez simplement lire les données, utilisez DataReader.

Traitez plusieurs lignes à l'aide du paramètre table ou de SqlBulkCopy

Utilisez TVP (Table-Valued Parameter) ou SqlBulkCopy pour traiter plusieurs lignes à la fois. https://www.sentryone.com/blog/sqlbulkcopy-vs-table-valued-parameters-bulk-loading-data-into-sql-server TVP est plus rapide pour les enregistrements inférieurs à 1000. C'est parce que Bulk Insert prend un certain temps pour s'initialiser. https://docs.microsoft.com/en-us/sql/relational-databases/tables/use-table-valued-parameters-database-engine?view=sql-server-ver15#BulkInsert Pour insérer plus de 1 000 enregistrements, utilisez SqlBulkCopy pour insérer les données.

Appeler TVP stocké à partir de C # peut être assez ennuyeux. DbSharp facilite la génération automatique de code d'appel C #. Introduction de DbSharp (outil de génération automatique de code source C # qui exécute stocké) Utilisons-le.

Faites attention aux clés du dictionnaire

GetHashCode est utilisé pour comparer les clés dans le dictionnaire. Les comparaisons de clés sont lentes lorsque vous utilisez String ou Enum comme clés. http://proprogrammer.hatenadiary.jp/entry/2014/08/17/015345 C'est une méthode pour accélérer lorsque vous utilisez Enum comme clé ↓ https://stackoverflow.com/questions/26280788/dictionary-enum-key-performance/26281533 Si possible, utilisez Int32, etc. comme clé.

Utilisez async, attendez

N'utilisez pas la méthode Wait () ou la propriété Result. Cela bloquera le thread jusqu'à ce que le processus soit terminé. Utilisez async pour libérer les threads pendant la latence. Le thread libéré sera utilisé pour effectuer d'autres traitements. Les demandes volent en parallèle sur les sites Web, etc., et il y a une attente pour l'acquisition des données de la base de données. En rendant tout asynchrone, le thread n'a pas seulement à attendre sans rien faire, et il exécute toujours le traitement de l'une des requêtes, ce qui améliore le débit global.

Utiliser Stream

Les données peuvent être lues et écrites en séquence à l'aide de Stream. Si vous le pouvez, utilisez-le.

N'utilisez pas HttpClient

Selon la documentation officielle, HttpClient nécessite une utilisation particulière. https://docs.microsoft.com/ja-jp/dotnet/api/system.net.http.httpclient?view=netcore-3.1 C'est un article de référence d'utilisation correcte ↓ https://qiita.com/superriver/items/91781bca04a76aec7dc0

DI et réessayer peuvent être définis facilement à l'aide de HttpClientFactory. https://docs.microsoft.com/en-US/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests

Empêcher la fragmentation de la mémoire et la mémoire insuffisante lors de l'utilisation de Socket

L'octet [] est utilisé pour envoyer et recevoir des données avec Socket.

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

L'octet [] passé à BeginSend est le port d'achèvement d'E / S et les données sont écrites à partir du système d'exploitation lorsque les E / S sont terminées. Vous devez vous assurer que le système d'exploitation peut écrire à cette adresse sans échec, et ce tampon sera PINned pour empêcher l'adresse de se déplacer lors du traitement du garbage collection. http://ahuwanya.net/blog/post/Buffer-Pooling-for-NET-Socket-Operations

Le problème est, par exemple, si vous envoyez des données en utilisant un tampon de taille 1024, SendCallback sera exécuté 1000 fois pour envoyer 1 Mo de données, 1000 instances de Byte [] seront générées, et toutes seront PINd. Je vais. L'objet PINd n'est pas déplacé dans le garbage collection, ce qui provoque une fragmentation de la mémoire. En conséquence, la mémoire insuffisante se produit.

Réflexion, arbre d'expression, dynamique, asynchrone, thread

De plus, nous présenterons divers conseils d'accélération.

Réflexion du cache

La réflexion est un processus très lent.

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

Réduisons le nombre d'exécutions et évitons de ralentir.

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

GetProperty, GetMethod, etc. sont lents. Utilisez un cache pour éviter les ralentissements. https://yamaokuno-usausa.hatenablog.com/entry/20090821/1250813986

Faites bon usage de dynamique

Si vous utilisez dynamique, le compilateur fera le cache dans une certaine mesure ↓ https://ufcpp.net/study/csharp/misc_dynamic.html

Utiliser la mise en cache de type générique

La mise en cache de type générique est plus rapide car la carte est complète au moment de la compilation. Utilisons-le lorsque le Type peut être déterminé statiquement. http://csharpvbcomparer.blogspot.com/2014/03/tips-generic-type-caching.html

Compilation d'arborescence d'expressions de cache

La compilation d'expression est un processus lourd. Assurez-vous de mettre en cache les délégués compilés.

Rendre la classe Regex compilée

Rendez la classe Regex compilée avec le code suivant. var rg= new Regex("[a-zA-Z0-9-]*", RegexOptions.Compiled); Vous pouvez vous attendre à une amélioration des performances en le rendant précompilé.

Accélérer ToString d'énumération

Le ToString d'Enum utilise la réflexion en interne. https://referencesource.microsoft.com/#mscorlib/system/type.cs,d5cd3cb0c6c2b6c1

public override String ToString()
{
    return Enum.InternalFormat((RuntimeType)GetType(), GetValue());
}
//************************réduction***************************
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;
}

C'est très lent car il utilise la réflexion. Voici comment l'accélérer ↓ https://qiita.com/higty/items/513296536d3b26fbd033

Déplier la boucle

Vous pouvez accélérer en élargissant la boucle. Par exemple, supposons que vous souhaitiez incrémenter toutes les valeurs d'un tableau de 1.

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

Développez la boucle et modifiez-la pour gérer les quatre éléments à la fois.

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;
}

Dans le traitement en boucle, le traitement d'expression (i <numberList.Length) et d'incrémentation (i ++) qui vérifie s'il faut répéter le traitement à chaque fois est exécuté. En se développant, le traitement ci-dessus de 40 000 fois devient 10 000 fois, et le traitement d'évaluation et d'incrémentation de la condition de 30 000 fois est éliminé. Si le nombre d'éléments dans le tableau n'est pas un multiple de 4, quelques ajustements sont nécessaires, mais l'expansion de la boucle peut être utilisée pour accélérer le processus. Vous pouvez être encore plus rapide en ajoutant 10 ou plus au lieu de 4.

utiliser pour au lieu de pour chacun

foreach est légèrement plus lent que pour car il accède à la propriété Current et appelle la méthode MoveNext à chaque fois. C'est négligeable pour la plupart des moments de la journée, mais si vous recherchez des améliorations de performances très importantes, envisagez d'utiliser pour. Surtout pour la combinaison de array et for, le compilateur fera beaucoup d'optimisation.

Évitez l'écriture de style LINQ et utilisez foreach

Les appels qui connectent des chaînes de méthodes de type LINQ peuvent être un peu lents ou assez lents en raison du nombre d'appels de méthodes. Reportez-vous à l'échange de commentaires sur cette page ↓ https://michaelscodingspot.com/performance-problems-in-csharp-dotnet/

Écriture de style LINQ

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

L'optimiseur optimisera le style LINQ autant que possible, mais le foreach normal est généralement plus rapide.

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

À propos de la copie de matrice

Source ↓ 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 D'après cette vidéo, Array.CopyTo semble être le plus rapide. La raison en est qu'il appelle directement l'API du système d'exploitation.

Array secrets non dans la documentation

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

La boucle Array est la plus rapide

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

L'enregistrement de la longueur dans une variable locale est lent

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

Dans le cas du code ci-dessous, il est difficile de garantir que la variable de longueur n'a pas été modifiée (la compilation prend du temps) compte tenu des modifications d'autres threads. En conséquence, la vérification de la valeur limite de Array fonctionne, ce qui ralentit. Dans le premier exemple, il est confirmé que a.Length est compris dans la longueur du tableau, de sorte que la vérification de la valeur limite est omise et le code intermédiaire qui optimise le traitement pour chaque élément du tableau est généré, donc il est plus rapide. ..

Utilisez des champs plutôt que des propriétés

Les propriétés sont en fait des méthodes. Un appel de méthode sera inséré lors de l'accès à la propriété. Même le coût des appels de méthode peut être inacceptable dans les environnements où les performances sont essentielles. Dans de tels cas, envisagez d'utiliser des champs au lieu de propriétés.

Faites attention à l'index de la boucle

Si l'ordre d'index de la double boucle est incorrect, la localité mémoire du CPU ne peut pas être utilisée correctement et le traitement peut être ralenti. https://raygun.com/blog/c-sharp-performance-tips-tricks/

Code qui peut tirer parti de la localité mémoire

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

Code qui ne peut pas être utilisé correctement

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

Le code ci-dessus est environ 8 fois plus rapide. Les données utilisées par le code ci-dessus sont placées plus près de la mémoire et peuvent être lues à la fois.

Rendre l'interface toujours réactive

Les utilisateurs sont stressés lorsque l'interface utilisateur ne répond pas. Exécutons le traitement qui prend beaucoup de temps dans le thread d'arrière-plan et faisons toujours réagir le thread d'interface utilisateur. Utilisez la classe Thread ou les modèles async et wait.

Implémenter la fonction de cache

L'accès est accéléré en plaçant les données de base de données, les données acquises par communication réseau, les données acquises par les opérations de fichiers, etc. sur la mémoire ou Redis. Les données présentant les caractéristiques suivantes conviennent à la mise en cache. ・ Petite quantité de données ・ Fréquemment mentionné ・ Pas beaucoup changé Si vous mettez en cache trop de données pour les conserver en mémoire, la mémoire sera épuisée. La mise en cache d'objets moins référencés gaspille un espace mémoire précieux. Si les données source sont mises à jour, vous devez mettre à jour le cache. La mise en cache des données fréquemment mises à jour utilise le processeur et la mémoire pour le processus de mise à jour, de sorte que ces données ne conviennent pas à la mise en cache.

Sur la base de ce qui précède, par exemple, les données suivantes sont appropriées comme cible de cache.

-Texte de l'application (le mot «ajouter» dans le bouton d'ajout est inchangé) ・ Organisation master (changé une fois par an seulement) ・ HTML du tableau de données de ventes du mois dernier (les données de ventes du mois dernier restent inchangées) Etc.

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

Fondamentalement, il sera implémenté avec des champs statiques (ou propriétés). Mettez à jour ces données lorsque les données d'origine sont mises à jour. Dans le cas d'une application WEB, puisqu'elle est accessible à partir de plusieurs threads, il est nécessaire de gérer les conflits avec verrou etc.

Si vous développez des applications WEB dans le cloud, vous pouvez mettre à jour le cache de toutes les applications WEB en envoyant des notifications à plusieurs applications WEB à l'aide de Redis Publish and Subscribe.

Cet article utilise C # comme exemple pour introduire le cache, mais le mécanisme de cache lui-même est utile. Le mécanisme de cache est utilisé à divers endroits tels que CDN, DNS et fichiers image des navigateurs.

La génération de débogage est lente et la génération de version est rapide

N'oubliez pas d'en faire une version release.

prime

J'ai écrit quelques articles pour ceux qui s'intéressent à la création d'applications hautes performances. J'ai essayé de créer des outils et des services de gestion des tâches pour m'améliorer à partir du niveau débutant- Techniques et connaissances à connaître pour améliorer les performances des applications WEB J'ai créé la bibliothèque Mapper la plus rapide au monde avec C # (3 à 10 fois plus rapide qu'AutoMapper, etc.) Conception d'interface utilisateur fonctionnelle pour les programmeurs Articles techniques avancés pour devenir un ingénieur de classe mondiale

appendice

Recommended Posts

Connaissance de base de l'informatique et de sa pratique que vous devez connaître pour améliorer les performances
[Rails] Une collection de conseils qui sont immédiatement utiles pour améliorer les performances
Grammaire Java de base à connaître en premier