0
votes

Tandis que la performance de la boucle: extrêmement lente

J'ai INPUT.TXT et PARTS.TXT Fichier comme ci-dessous:

 (IFS=*; printf "%s~" "${data[*]}";)
done < input.txt > output.txt 


11 commentaires

lire ne fait aucun tampon; Il ne peut consommer que exactement les données qu'il attribuera à toutes les variables, de sorte qu'un ultérieur de lecture ne manquait aucune donnée. En conséquence, il ne consomme que son entrée un personnage à la fois. Si vous êtes préoccupé par la performance, bash est la mauvaise langue à utiliser.


J'ai deuxième la dernière ligne de ce commentaire.


Pouvez-vous expliquer la règle qui s'applique à la dernière ligne de sortie, où vous vous retrouvez avec voiture * gm * yukon ** ~ plutôt que voiture * gm * yukon * (partie aléatoire) * ~ ? Veuillez également clarifier si le fichier d'entrée ressemble à data ~ ndata (comme exemple Input.txt indique) ou Data ~ Data Comme votre exemple de code et de description semblent indiquer.


Pourquoi avez-vous besoin d'un "délimiteur de ligne" supplémentaire? Ce sont déjà des lignes distinctes.


@Davido @Melpomene: Merci d'avoir examiné la question. Dans le code, je vérifie si la longueur de données en position 3 est supérieure à 0 est supérieure à 0, seules seules l'élément `` `` `$ {# data [$ positionnement]} -gt 0` `` `. Pas de retour de chariot et d'alimentation en ligne (CRLR) après délimiteur ~. Exemple: données ~ Data ~. Pour la compréhension des objectifs, j'ai posté comme celle-là dans la question originale.


En outre, vous JAMAIS VOUS METTRE UNE SUBULTATION DE COMMANDE DANS UNE BOUCLE INTERRE. $ (...) tout est mauvais. $ (shuf ...) est pire, car non seulement est-ce une substitution de commande, mais c'est une commande externe. Pour la même raison, dans une boucle où vous vous souciez de la performance, créez un sous-groupe à la portée de votre ifs ne valent pas la peine.


... * Aussi *, ne pas exécuter >> /tgt/output.txt à l'intérieur d'une boucle - qui redoublez le fichier de sortie chaque itération. Déplacez la redirection pour appliquer à toute la boucle: effectué /tgt/output.txt .


Cela dit, grand-image, monde réel? Bash est le mauvais outil pour le travail. Personnellement, étant donné une tâche de traitement de texte sensible à la performance, j'ai tendance à atteindre AWK (si ce n'est pas un problème suffisant intéressant pour justifier à l'aide d'utiliser Julia ).


Merci @charlesduffy. Je vais faire des recherches sur le remplacement de $ (...). Comme Davido a suggéré de conserver le fichier Parts.txt dans la mémoire tampon et lisez les valeurs de la mémoire tampon. Je vais essayer cela. Aussi j'ai essayé de rediriger la sortie à la fin de la boucle tandis que /tgt/output.txt. Mais toujours aucune amélioration de la performance que je posté à l'origine. C'est donc la raison pour laquelle j'ai demandé si je peux y parvenir dans le script Perl. Merci encore.


Notez que "Veuillez réécrire mon script en langue-x / to-to-to-be-plus rapide / autre" est généralement une question qui est suffisamment large pour être en dehors de nos directives pour une portée autorisée.


... revenir au shuf , chose: à l'extérieur de votre boucle, vous pouvez ajouter une redirection (après le effectué ) comme 5 << ( shuf /tmp/parts.txt) , puis vous lisez LIVE LIGNE <& 5 à l'intérieur de la boucle lorsque vous souhaitez lire une ligne à partir de ce flux en cours sans toute la surcharge de performance du démarrage une nouvelle copie de shuf . ( 5 comme numéro de descripteur de fichier est arbitraire; il doit juste être plus de 2 de sorte qu'il n'interfère pas avec stdin / stdout / stardr, et c'est une bonne habitude lorsque cela est possible d'utiliser une valeur de moins que 10 pour des raisons historiques / héritées / portabilité)


3 Réponses :


2
votes

Lorsque vous travaillez vers l'optimisation, la première étape consiste à faire du temps combien de temps il faut juste pour lire le fichier d'entrée et ne rien faire avec elle. Sur mon système qui ne prend que quelques centièmes de seconde pour un fichier de 10 Mo.

Alors maintenant, nous connaissons le moins de temps qu'il faudra prendre, nous devons examiner des stratégies d'optimisation. Dans votre code d'exemple, vous ouvrez PARTS.TXT et lisez ce fichier à partir du système de fichiers pour chaque enregistrement de votre fichier d'entrée. Donc, vous développez considérablement la quantité de travail nécessaire. Il serait plus agréable de garder le fichier de pièces en mémoire et de saisir un élément aléatoire à partir de celui-ci pour chaque enregistrement de votre fichier d'entrée.

L'optimisation suivante que vous pouvez faire est d'éviter de mélanger la liste des pièces chaque fois que vous avez besoin d'une partie. Mieux vaut attraper un élément aléatoire, que de mélanger les éléments.

Vous pouvez également ignorer tout traitement pour tous les enregistrements qui ne commencent pas avec la voiture, mais cela semble être un avantage moins élevé.

Quoi qu'il en queluille, ce qui suit accomplit ces objectifs: xxx

sur mon système Un fichier composé de 488321 enregistrements (environ 10 Mo de taille) prend 0,588 secondes à traiter.

Pour vos propres besoins, vous voudrez prendre ce script Perl et la modifier pour avoir une gestion plus robuste des noms de fichiers et des chemins de fichiers. Cela ne fait pas partie de la question qui a été posée, cependant. L'objectif principal de ce code est de démontrer lorsque des optimisations peuvent être prises; Déplacement du travail hors de la boucle, par exemple; Nous ouvrons uniquement le fichier de pièces une fois, nous l'avons lu une fois et nous ne mélangez jamais; Nous venons de saisir un élément aléatoire de notre liste dans la mémoire de pièces.

puisque la ligne de commande "one-liners" est tellement pratique, nous devrions voir si cela peut être bouilli à un. La plupart des fonctionnalités équivalentes peuvent être obtenues dans une perl "une doublure" à l'aide du -l , -a , -p , -F et -E (Je prends la liberté de la laisser couler à plusieurs lignes, si): xxx

Voici comment cela fonctionne:

Le commutateur -p indique à Perl sur chaque ligne du fichier spécifié sur la ligne de commande ou si aucun n'est spécifié, sur STDIN. Pour chaque ligne, placez la valeur de la ligne dans $ _ et avant de passer à la ligne suivante, imprimez le contenu de $ _ à stdout. Cela nous donne la possibilité de modifier $ _ telle que des modifications sont écrites sur stdout. Mais nous utilisons le commutateur -l qui nous permet de spécifier une valeur octale représentant un séparateur d'enregistrements différent. Dans ce cas, nous utilisons la valeur octale pour le caractère ~ . Ceci provoque des -p à itérer sur des enregistrements séparés par ~ au lieu de \ n . Aussi le -l Bandes de commutateur Séparateurs d'enregistrement sur l'entrée et les remplace sur la sortie.

Cependant, nous utilisons également le -A et et -F commutateurs. -A indique à Perl de diviser automatiquement l'entrée dans le tableau @f et -f nous permet de préciser que nous voulons autosplit sur le * caractère. Parce que -f accepte un motif PCRE et * est considéré comme un quantifier dans PCRE, nous nous échappons avec une barre oblique inverse.

Suivant the -E Switch dit pour évaluer la chaîne suivante en tant que code. Enfin, nous pouvons discuter de la chaîne de code. Il y a d'abord un commencer {...} bloc qui déplace une valeur éteinte de @argv et l'utilise comme nom d'un fichier à ouvrir pour lire la liste des pièces de . Une fois que ce nom de fichier a été transféré, il ne sera pas pris en compte pour la lecture par le commutateur ultérieurement dans le script (le bloc Beggen se produit avant la boucle implicite -p . ). Donc, considérez simplement que le code dans le bloc commence {...} Le bloc définit temporairement le séparateur d'enregistrement sur les nouvelles lignes, lit le fichier de pièces dans une matrice, puis relie le séparateur d'enregistrement à être ~ à nouveau.

Nous pouvons maintenant passer au-delà du bloc de début. @f est devenu le conteneur qui détient les champs dans un enregistrement donné. Le 4ème champ (offset 3) est celui que vous souhaitez échanger. Vérifiez si le premier champ (décalage 0) commence par voiture . Si tel est le cas, définissez le contenu du 4ème champ sur un élément aléatoire à partir de notre réseau de pièces, mais uniquement si ce champ est constitué d'un ou de plusieurs caractères.

puis nous nous réunissons ensemble les champs, délimité avec un Astérisque et assignez ce résultat de ce résultat vers $ _ . Notre travail est fait. Grâce au commutateur -P , Perl écrit le contenu de $ _ à stdout, puis ajoute le séparateur d'enregistrement, ~ . .

Enfin sur la ligne de commande, nous spécifiez d'abord le chemin d'accès au fichier de pièces, puis le chemin d'accès au fichier d'entrée, puis rediriger stdout dans notre fichier de sortie.


2 commentaires

Merci David. Je vais essayer le script donné par vous. Puis-je simplement ajouter Perl Scrip donné par vous à l'intérieur du script Bash? comme perl -e "$ (script)"


J'ai fourni un script Perl car la question initiale a été étiquetée avec "Perl". Quoi qu'il en soit, vous ne l'invoqueriez pas comme perl -e "$ (script)" , vous le feriez exécuter et l'invoquerait comme une autre commande: / chemin / à / à / à / à / à / code> ou alternativement perl / chemin / à / script . Vous voudrez également travailler sur le fait que les noms de fichiers de déséquilibre sont codés dans des chemins relatifs. Vous voudrez la modifier pour être plus robuste dans la manière dont il traite des noms de fichiers. Mais encore une fois, la question a été étiquetée "Perl" pour une raison, je suppose.



2
votes

awk est votre réponse ici, je pense: xxx

explication:

r [] est un Array qui vient de contenir toutes les lignes de parties.txt .

champ d'entrée et de sortie et des séparateurs d'enregistrement sont définis pour correspondre au format de votre INPUT.TXT fichier.

srand () graines the rand () fonction (avec heure de la journée) afin que vous n'obtiens pas la même séquence de éléments aléatoires à chaque fois.

Si les conditions de modification du 4ème champ sont remplies, le 4ème champ est remplacé par un élément aléatoire de R .

Le Final 1 provoque une impression de la ligne, qu'elle soit modifiée ou inchangée.


0 commentaires

2
votes

Je suis absolument d'accord pour dire qu'il existe des langues autres que Bash qui seront à la fois plus faciles et plus rapides.

Néanmoins, certains jours, je ne peux pas résister à un défi. La clé pour fabriquer des scripts shell fonctionne rapidement est de faire le moins possible dans la coquille; Essayez de trouver un moyen d'utiliser des utilitaires externes pour travailler en vrac au lieu de la ligne par ligne. p>

Le script de shell suivant est un exemple brut. Il fait plusieurs choses pour éviter la boucle en shell: p>

  • la version GNU de shuf code> fournit le drapeau -r code> pour générer une séquence (potentiellement infinie) de lignes aléatoires extraites de son entrée, au lieu de shuffler le INPUT. P> LI>

  • La commande Coller code> fait la concaténation de la ligne de ligne de deux flux d'entrée. (Malheureusement, il n'a pas de moyen d'arrêter lorsque le flux le plus court se termine, vous ne pouvez donc pas l'utiliser avec un flux infini. Cela oblige une analyse supplémentaire inconfortable du texte d'entrée afin de compter le nombre de lignes.) li>

  • Il est possible d'encoder les critères "Le premier champ est voiture code> et le quatrième champ n'est pas vide" comme une seule expression régulière. Qui nous permet de faire toute la sélection et la substitution avec une seule invocation de sed code>. P> li>

  • Le fichier d'entrée utilise ~ code> au lieu de nouvelles lignes sur Delimit Records, qui est gênant pour la plupart des outils de fichier texte Linux. Nous pouvons utiliser tr '~' '\ n' code> pour activer les tildes en nouvelles lignes et TR '\ n' '~' code> pour retourner les nouvelles lignes dans des tildes à la fin. p> li> ul>

    Voici le script: p> xxx pré>

    et voici un échantillon exécuté: p> xxx pré>

    Voici une version du script ci-dessus qui vous oblige à spécifier la valeur du champ 0 (" $ segment code>") et le numéro de champ à remplacer (" $ positionnement code>") comme paramètres de script. Il manque complètement de vérifications sur la validité des arguments et ne fournit pas non plus de valeurs par défaut. Un script robuste ferait mieux. Mais j'espère donner une idée de la façon de paramétrer le script. (Ça fait en construisant le SED CODE> REGEX à l'aide des paramètres fournis). P>

    $ time ./xform.sh CAR 3 > output.txt
    
    real    0m1.519s
    user    0m1.712s
    sys     0m0.120s
    


9 commentaires

WOW, vous avez écrit la logique en 1 ligne. Avant de poster cette question, j'ai étudié et appris sur SED. Il semble que SED a une limite de taille de ligne et il n'y a pas de limite si nous utilisons la version GNU de SED. Donc, toutes mes options sont sorties avec Bash et ont demandé à Perl. Je crois que vous utilisez la version GNU de SED. Vos réponses me ramènent pour utiliser le code Linux et différentes manières de réaliser la sortie souhaitée. Juste curieux d'où avez-vous mentionné la position 3ème élément dans la commande SED? Souhaitez-vous expliquer sed -e 's / ^ ([^ *] +) ([*] voiture ([*] [^ *] +) {2} [*]) [^ *] + / \ 1 \ 2 \ 1 / '?


@skv: compter les étoiles :-) Un champ est un * suivi de n'importe quel nombre de caractères qui ne sont pas * : (regex: [*] *] * ). Le quatrième champ utilise [^ *] + afin de vous garantir que ce soit au moins un caractère. Il n'y a pas de problème avec la longueur de la ligne ici parce que j'ai transformé tous les ~ dans les lignes neuves avant de le transmettre par SED.


Merci pour l'explication. Je suis très nouveau pour sed en utilisant la regex. Toujours pas comprendre. Où dans SED, vous comptez et comment vous utilisez ce nombre pour trouver la position 3? La raison pour laquelle je demande est que j'essaie de garder la valeur de position en tant que variable d'entrée, de sorte que je puisse utiliser une position variable $ en position SED.


@skv: Je pense que ce n'est pas vraiment l'endroit pour un tutoriel de regex; il y a beaucoup de. Mais le comte est codé dans l'opérateur de répétition ( {2} ); Le modèle de champ est répété deux fois pour la position = 3 car le premier champ a déjà été associé à voiture . J'ai fait une version qui vous permet de spécifier le segment et la position comme des arguments de script.


$ (($ 2-1)) signifie "le deuxième paramètre moins celui"; Les expressions arithmétiques écrites avec $ ((...)) ne sont pas requises par POSIX, mais sont implémentées par la plupart des coquillages, y compris Bash.


Merci. J'essaie ce code maintenant. Mettra à jour le post concernant la performance


Je viens d'essayer le script et comparé à la boucle vieille. Juste wow. Énorme amélioration. SED avec regex terminé en quelques secondes et pendant que la boucle prend plus de 15 minutes. Pouvons-nous ajouter audit au script donné par vous comme je l'ai fait dans ma boucle? Exemple: dans ma boucle alors que j'ai ajouté des variables A et B. $ A donne des segments totaux avec une voiture et une variable de $ B donne des éléments totaux qui ont été remplacés


En plus de la boucle, j'ai la capacité de filtrer davantage l'entrée. Exemple; SUV * FORD * EXPLORER * VERRE * SAFFÉ ~ , DATA [0] = SUV && DATA [1] = Ford puis recherchez le champ de verre dans la ligne si vous avez suivi la valeur à côté de la vitre si non vide avec fichier de pièces. Pour filtrer la ligne avec SUV et FORD, je peux simplement ajouter une deuxième variable pour Ford comme SEDCMD = 'S / ^ ([^ *] *) ([*]' $ 1 '[*] $ 2' [*] ( [^ *] * [*]) {'$ (($ 2-1))'}) ([^ *] +) / \ 1 \ 2 \ 1 / ', mais je ne sais pas comment je Peut ensuite rechercher une valeur de verre et de position à côté du verre


Oui, si vos exigences sont plus compliquées, vous devriez vraiment regarder le faire avec Awk.