FileStream très lent au démarrage de l’application à froid

Une question très similaire a également été posée ici sur SO au cas où vous seriez intéressé, mais comme nous le verrons, la réponse acceptée à cette question n’est pas toujours la même (et ce n’est jamais le cas pour mon modèle d’utilisation des applications).

Le code de détermination des performances consiste en un constructeur FileStream (pour ouvrir un fichier) et un hachage SHA1 (mise en œuvre du cadre .Net). Le code est à peu près la version C # de ce qui a été demandé dans la question que j’ai liée ci-dessus.

Cas 1: L’application est démarrée pour la première fois ou à la nième fois, mais avec un ensemble de fichiers cible différent. Il est maintenant demandé à l’application de calculer les valeurs de hachage sur les fichiers auxquels vous n’avez jamais eu access auparavant.

  • ~ 50ms
  • 80% constructeur de FileStream
  • Calcul du hachage de 18%

Cas 2: L’ application est à présent complètement terminée et redémarrée, on vous demande de calculer le hachage sur les mêmes fichiers:

  • ~ 8ms
  • 90% de calcul de hachage
  • 8% constructeur de FileStream

Problème
Mon application est toujours utilisée Cas 1 . On ne vous demandera jamais de recalculer un hachage sur un fichier déjà visité une fois.

Donc, mon étape déterminante est FileStream Constructor! Est-ce que je peux faire quelque chose pour accélérer ce cas d’utilisation?

Je vous remercie.

Les statistiques PS ont été recueillies à l’aide du profileur JetBrains.

… mais avec un ensemble de fichiers cible différent.

Phrase clé, votre application ne pourra pas tirer parti du cache du système de fichiers. Comme dans la deuxième mesure. Les informations du répertoire ne peuvent pas provenir de la RAM car elles n’ont pas encore été lues, le système d’exploitation doit toujours se rabattre sur le lecteur de disque et c’est lent.

Seul un matériel plus performant peut l’accélérer. 50 ms correspond à la durée standard requirejse pour un lecteur de broche, 20 ms étant à peu près aussi faible. Le temps de recherche de la tête de lecture est la limite mécanique difficile. C’est facile à battre aujourd’hui, SSD est largement disponible et raisonnablement abordable. Le seul problème, c’est que lorsque vous vous y êtes habitué, vous ne revenez jamais 🙂

Le système de fichiers et / ou le contrôleur de disque mettront en cache les fichiers / secteurs récemment accédés.

L’étape déterminant le taux est la lecture du fichier, pas la construction d’un object FileStream , et il est tout à fait normal qu’il soit significativement plus rapide lors de la seconde exécution lorsque les données sont dans le cache.

Hors piste suggestion, mais c’est quelque chose que j’ai fait beaucoup et a obtenu nos parsings 30% – 70% plus rapide:

Mise en cache


Écrivez un autre code qui:

  • parcourir tous les fichiers;
  • calculer le hachage; et,
  • stockez-le dans un autre fichier d’ index .

Maintenant, n’appelez pas un constructeur FileStream pour calculer le hachage au démarrage de votre application. Au lieu de cela, ouvrez le fichier d’index (beaucoup plus que prévu) et lisez-le.

De plus, si ces fichiers sont des fichiers journaux, etc., nouvellement créés chaque fois avant le démarrage de votre application, ajoutez du code dans le créateur du fichier pour mettre à jour le fichier d’index avec le hachage du fichier nouvellement créé.

De cette façon, votre application peut toujours lire le hash du fichier d’index uniquement.


Je suis d’accord avec la suggestion de @ HansPassant d’utiliser des SSD pour accélérer la lecture de votre disque. Cette réponse et sa réponse sont complémentaires. Vous pouvez implémenter les deux pour optimiser les performances.

Comme indiqué précédemment, le système de fichiers a son propre mécanisme de mise en cache qui perturbe votre mesure.

Cependant, le constructeur FileStream exécute plusieurs tâches qui, la première fois, sont coûteuses et nécessitent un access au système de fichiers (donc quelque chose qui pourrait ne pas se trouver dans le cache de données). Pour des raisons explicatives, vous pouvez consulter le code et voir que les classes CompatibilitySwitches sont utilisées pour détecter l’utilisation des sous-fonctionnalités. Associé à cette classe, Reflection est fortement utilisé à la fois directement (pour accéder à l’assembly actuel) et indirectement (pour les sections protégées par CAS, demandes de lien de sécurité). Le moteur Reflection possède son propre cache et nécessite l’access au système de fichiers lorsque son propre cache est vide.

Il est un peu étrange que les deux mesures soient si différentes. Nous avons actuellement quelque chose de similaire sur nos machines équipées d’un logiciel antivirus configuré avec une protection en temps réel. Dans ce cas, le logiciel antivirus est au milieu et le cache est frappé ou manqué la première fois en fonction de l’implémentation d’un tel logiciel.

Le logiciel antivirus peut décider de vérifier de manière agressive certains fichiers d’image, tels que les fichiers PNG, en raison de vulnérabilités de décodage connues. De telles vérifications introduisent un ralentissement supplémentaire et des comptes le temps dans la classe .NET la plus externe, c’est-à-dire la classe FileStream .

Le profilage en utilisant des symboles natifs et / ou avec le débogage du kernel, devrait vous donner plus d’informations.

D’après mon expérience, ce que vous décrivez ne peut pas être atténué, car plusieurs couches cachées sont hors de notre contrôle. En fonction de votre utilisation, ce qui n’est pas parfaitement clair pour moi à l’heure actuelle, vous pouvez transformer l’application dans un service, vous pouvez donc répondre plus rapidement à toutes les demandes ultérieures. Alternativement, vous pouvez regrouper plusieurs demandes en un seul appel pour obtenir un coût réduit amorti.

Vous devriez essayer d’utiliser le FILE_FLAG_SEQUENTIAL_SCAN natif, vous devrez appeler CreateFile pour obtenir un handle et le transmettre à FileStream