Multi threaded " traitement du fichier avec .NET
Il y a un dossier qui contient 1000s de petits fichiers de texte. Mon but est d'analyser et de traiter tous d'entre eux, tandis que plus les fichiers sont placés dans le dossier. Mon intention est de multithread cette opération que le thread unique prototype, qui a duré six minutes pour le processus de 1000 fichiers.
J'aime avoir du lecteur et de l'écrivain, fil(s) à la suivante. Alors que le thread de lecture(s) de la lecture des fichiers, j'aimerais avoir thread d'écriture(s) à traiter. Une fois que le lecteur est de commencer la lecture d'un fichier, je tiens à le marquer comme " en cours de traitement, comme en le renommant. Une fois qu'il est lu, le renommer en "terminé".
Comment aborder une telle application multithread?
Est-il préférable d'utiliser un distributed hash table ou une file d'attente?
Lequel la structure de données dois-je utiliser pour éviter les verrous?
Est-il une meilleure approche de ce dispositif?
L'un des principaux facteurs limitant c'est I/O contention, peu importe comment vous essayez de paralléliser le travail, tout doit passer par le même I/O.
ok, qui s bien, je tiens à utiliser pleinement les IO
.net 3.5. J'en doute .net 4 est une option pour moi.
Craver je voudrais vraiment creuser voir un .Net 4 suggestion en plus de la 3.5. Mais seulement si il ne serait pas vous mettre à tous, et que si d'autres personnes sont intéressées aussi bien (ils ont peut-mod de ce commentaire).
OriginalL'auteur DarthVader | 2010-05-11
Vous devez vous connecter pour publier un commentaire.
Puisqu'il y a de la curiosité sur la façon dont .NET 4 fonctionne avec ce, dans les commentaires, voici cette approche. Désolé, c'est probablement pas une option pour l'OP. Avertissement: Ce n'est pas très scientifique, de l'analyse, il suffit de montrer qu'il y a clairement un avantage de performance. Basé sur le matériel, votre kilométrage peut varier largement.
Voici un test rapide (si vous voyez une grosse erreur dans ce test simple, c'est juste un exemple. S'il vous plaît commentaire, et l'on peut fixer à être plus utiles, exactes). Pour cela, j'ai simplement baissé de 12 000 ~60 KO des fichiers dans un répertoire comme un échantillon (le feu jusqu'à LINQPad; vous pouvez jouer avec vous, gratuitement! - assurez-vous d'obtenir LINQPad 4 si):
Modifiant légèrement votre boucle de paralléliser la requête est tout ce qui est nécessaire dans
plus des situations simples. Par de "simples" j'ai surtout dire que le résultat d'une action n'a pas d'incidence sur la prochaine. Quelque chose à garder à l'esprit le plus souvent, c'est que certaines collections, par exemple notre pratique
List<T>
est de ne sont pas thread-safe, afin de l'utiliser en parallèle scénario n'est pas une bonne idée 🙂 Heureusement il y avait des les collections simultanées .NET 4 qui sont thread-safe. Aussi garder à l'esprit si vous utilisez un système de blocage de la collection, ce peut être un goulot d'étranglement ainsi, en fonction de la situation.Il utilise le
.AsParallel<T>(IEnumeable<T>)
et.Pourtout<T>(ParallelQuery<T>)
extensions disponibles dans .NET 4.0. Le.AsParallel()
appel enveloppe leIEnumerable<T>
dans unParallelEnumerableWrapper<T>
(classe interne) qui met en œuvreParallelQuery<T>
. Cela permet maintenant d'utiliser le parallèle les méthodes d'extension, dans ce cas, nous utilisons.ForAll()
..ForAll()
en interne des caisses unForAllOperator<T>(query, action)
et l'exécute de manière synchrone. Cela gère le filetage et la fusion des filets après c'est de la course... Il est tout à fait un peu là-dedans, je vous suggère de a partir de là si vous voulez en savoir plus, y compris des options supplémentaires.Les résultats (Ordinateur 1 - Disque Dur Physique):
Ordinateur specs - pour la comparaison:
Les résultats (Ordinateur 2 - Lecteur à État Solide):
Spécifications de l'ordinateur - pour la comparaison:
Je n'ai pas de liens pour le CPU/RAM cette fois, ce fut installé. C'est un Dell M6400 ordinateur Portable (voici un lien vers le M6500... Dell propres liens à la 6400 sont cassé).
Ces chiffres sont à partir de 10 pistes, en prenant le min/max de l'intérieur de la 8 résultats (suppression de l'original min/max pour chaque que possible des valeurs aberrantes). Nous avons touché un I/O goulot d'étranglement ici, en particulier sur l'entraînement physique, mais pensez à ce que le numéro de série de la méthode. Il lit, des processus, des lectures, des processus, rincer répéter. Avec l'approche parallèle, vous êtes (même avec un I/O goulot d'étranglement) de lecture et de traitement simultanément. Dans le pire goulot d'étranglement de la situation, vous êtes de traitement d'un fichier pendant la lecture de la prochaine. Que seul (sur n'importe quel ordinateur actuel!) devrait entraîner certains gain de performance. Vous pouvez voir que nous pouvons obtenir un peu plus que l'un va à la fois dans les résultats ci-dessus, de nous donner une bonne impulsion.
Un autre avertissement: Quad core + .NET 4 parallèle ne va pas vous donner quatre fois plus de performance, il n'est pas à l'échelle linéaire... Il y a d'autres considérations et les goulets d'étranglement dans le jeu.
J'espère que c'était sur l'intérêt de montrer l'approche et les avantages possibles. Hésitez pas à critiquer ou de les améliorer... Cette réponse n'existe que pour ceux qui sont curieux comme il est indiqué dans les commentaires 🙂
Nick, ça ne serait pas mieux si l'OP utilise un producteur et plusieurs consommateurs, au lieu de tâches en parallèle pour chaque fichier? Si l'OP fait trop de tâches en parallèle, puis la commutation entre eux se dégradera les performances... après la grande-autrement!
Pas sûr que je comprends tout à fait, vous n'êtes pas vraiment un changement de contexte ici, cela crée le nombre de threads en fonction du nombre de cœurs, de sorte que vous n'êtes pas la commutation de contexte, sauf lors d'une interruption, comme toujours. Quel serait le producteur dans votre cas (de préciser un peu, par exemple!)? Depuis la capacité à l'échelle de la lecture du fichier dépend de la source de données, si c'est un physique HD, fibre channel, un SSD, de la RAM, etc...c'est de la capacité de lire un nombre x de fichiers plus rapide dépendra de la moyenne...donc pas sûr que d'un seul producteur est de plus en plus vite...il pourrait en fait devenir un goulot d'étranglement 🙂
Je suis à essayer de comprendre comment votre exemple crée un certain nombre de fils correspondant aux noyaux? Est-il automatique avec TPL, ou est-il une autre magie, là?
Le nombre de travailleurs (par défaut) automatiquement mis à l'échelle par PLINQ en interne, mais vous pouvez spécifier une limite (et beaucoup d'autres options) si vous voulez en utilisant MaxDegreeOfParallelism, Reed Copsey a une bonne explication ici: reedcopsey.com/2010/02/11/...
OriginalL'auteur Nick Craver
Conception
Producteur/Consommateur modèle sera probablement le plus utile pour cette situation. Vous devez créer assez de fils pour maximiser le débit.
Voici quelques questions au sujet du Producteur/Consommateur motif pour vous donner une idée de comment cela fonctionne:
Vous devez utiliser une file d'attente de blocage et le producteur doit ajouter des fichiers à la file d'attente alors que les consommateurs traiter les fichiers de la file d'attente. La file d'attente de blocage nécessite pas de blocage, c'est donc le moyen le plus efficace pour résoudre votre problème.
Si vous êtes en utilisant .NET 4.0 il y a plusieurs les collections simultanées que vous pouvez utiliser de la boîte:
Filetage
Un seul producteur thread sera probablement le moyen le plus efficace pour charger les fichiers à partir du disque et de les pousser sur la file d'attente; par la suite, plusieurs consommateurs vont apparaître les éléments de la file d'attente et qu'ils vous traitent. Je suggère que vous essayez 2-4 consommateur threads par noyau et de prendre certaines mesures de rendement pour déterminer lequel est le plus optimal (c'est à dire le nombre de threads que de vous fournir le débit maximal). Je pas recommandons l'utilisation d'un pool de threads pour cet exemple précis.
P. S. je ne comprends pas quel est le souci avec un point de défaillance unique et de l'utilisation des distribué des tables de hachage? Je sais Dht sonne comme une chose vraiment cool à utiliser, mais je voudrais essayer les méthodes conventionnelles, sauf si vous avez un problème spécifique à l'esprit que vous essayez de résoudre.
Corriger Subvention, producteurs et plusieurs consommateurs.
OriginalL'auteur Kiril
Je vous recommande de file d'attente d'un thread pour chaque fichier et de suivre les threads en cours d'exécution dans un dictionnaire, le lancement d'un nouveau thread lorsqu'un thread se termine, jusqu'à une limite maximale. Je préfère créer mon propre fils quand ils peuvent être de longue durée, et l'utilisation de rappels au signal quand ils ont terminé ou a rencontré une exception. Dans l'exemple ci-dessous j'ai utiliser un dictionnaire pour garder une trace du travail en cours d'exécution des instances. De cette façon, je peux appeler dans un exemple, si je veux arrêter de travailler plus tôt. Les rappels peuvent également être utilisés pour la mise à jour d'une INTERFACE utilisateur avec le progrès et le débit. Vous pouvez également dynamiquement des gaz le thread en cours d'exécution limite pour l'ajout de points.
L'exemple de code est une version abrégée de démonstration, mais il fonctionne.
OriginalL'auteur Ed Power
Généralement parlant, 1000 fichiers de petite taille (petite comment, d'ailleurs?) ne devrait pas prendre de six minutes. Comme un test rapide, faire un
find "foobar" *
dans le répertoire contenant les fichiers (le premier argument dans le devis n'a pas d'importance; il peut être n'importe quoi) et de voir combien de temps il faut pour traiter chaque fichier. Si cela prend plus d'une seconde, je vais être déçu.En supposant que ce test confirme mes soupçons, alors le processus est lié au PROCESSEUR, et vous n'aurez pas d'amélioration de la séparation de la lecture dans son propre thread. Vous devez:
Je crois que j'aurais été plus précis avec ce que je voulais dire par processus continu. Je voulais dire, ce processus sera exécuté aussi longtemps que les fichiers sont en cours de création.
Avez-vous d'exécuter le test que j'ai proposé? Je suis curieux de connaître le résultat.
Je vais vous laisser savoir une fois que je le fais. je n'ai pas accès aux fichiers maintenant.
OriginalL'auteur Marcelo Cantos
Vous pourriez avoir une file d'attente centrale, le lecteur de fils aurait besoin d'accès en écriture au cours de la poussée du contenu de la mémoire de la file d'attente. Les threads de traitement aurait besoin d'un accès en lecture à cette file d'attente centrale pour faire apparaître le prochain flux de mémoire à être traitées. De cette façon, vous réduisez le temps passé dans les serrures et ne pas avoir à faire face à la complexité de la serrure de code libre.
EDIT: Idéalement, vous souhaitez gérer toutes les exceptions et les conditions d'erreur (le cas échéant) gracieusement, de sorte que vous n'avez pas de points de défaillance.
Comme alternative, vous pouvez avoir plusieurs threads, chacun des "revendications" d'un fichier en le renommant avant le traitement, ainsi que le système de fichiers devient la mise en œuvre de verrouillé l'accès. Aucune idée si c'est plus performant que ma réponse originale à cette question, seuls des essais dirais.
Peut-être, mais le fait, pour chaque thread "intelligent" et de travailler ensemble avec les autres threads peut être compliqué. La croix-problèmes de threading peut être un cauchemar à déboguer. Un seul (ou centrale) de la file d'attente est plus simple.
Que voulez-vous dire je n'ai pas de points de défaillance ? ce qui se passe si le serveur tombe en panne ? approche centralisée est toujours introduit le point de défaillance unique.
OriginalL'auteur Chris O
Vous pourriez envisager de faire une file d'attente de fichiers à traiter. Remplir la file d'attente une fois par la numérisation du répertoire lorsque vous démarrez et ont la file d'attente de mise à jour avec un FileSystemWatcher efficacement ajouter de nouveaux fichiers à la file d'attente sans cesse ré-analyse du répertoire.
Si possible, de lire et d'écrire sur des disques physiques différents. Qui va vous donner un maximum de performances.
Si vous disposez d'une première bouffée de nombreux fichiers à traiter, puis un inégale rythme des nouveaux fichiers ajoutés et tout cela se fait sur le même disque (lecture/écriture), vous pourriez envisager la mise en mémoire tampon les fichiers traités à la mémoire jusqu'à ce que l'une des deux conditions s'appliquent:
vous ne voulez pas utiliser plus de mémoire pour
mise en mémoire tampon (idéalement un configurables
seuil)
Si votre réel de traitement des dossiers est de temps CPU, vous pourriez envisager d'avoir un traitement d'un thread par cœur de PROCESSEUR. Cependant, pour que la "normale" de traitement de temps CPU sera négligeable par rapport à IO du temps et de la complexité ne serait pas la peine de tout mineur gains.
Dans ce cas, une lecture thread et écrire fil d'améliorer le débit, depuis l'écriture qui se passe sur le réseau et pas le disque local.
OriginalL'auteur Eric J.