Pourquoi le code pour diviser un gros fichier écrit en Perl exécute beaucoup plus rapidement le code écrit dans Korn Shell. Plus d'un million d'enregistrements d'entrée. Les 9 premiers caractères de chaque enregistrement sont utilisés pour déterminer dans quel fichier l'enregistrement est écrit, et il s'exécute en environ 4 à 5 minutes en Perl. J'ai essayé de convertir ce code en ksh et il semble fonctionner pour toujours (heures).
Je n'ai vraiment aucune idée de ce que j'ai fait de mal qui cause ce problème. Dans certains enregistrements, il y a un blanc et / ou des caractères alpha incorporés dans la chaîne de sorte que la comparaison doit être une comparaison de type chaîne. Des idées pour que mon script ksh obtienne les performances de perl ou pourquoi ce n'est pas le cas?
J'ai essayé plusieurs options différentes car ksh / bash a tendance à avoir de nombreuses façons de faire les mêmes choses ou des choses similaires dans la gestion variables et comparaisons. Je n'ai pas non plus une bonne idée du fonctionnement de ce très vieux code Perl.
Mon code Perl:
-rw-r--r--. 1 mfadjobt mfadset 461650125 Feb 13 10:07 base1.dat -rw-r--r--. 1 mfadjobt mfadset 519783625 Feb 13 10:07 base2.dat -rw-r--r--. 1 mfadjobt mfadset 1114362000 Feb 13 10:07 base3.dat
Mon script Shell (ksh):
-rw-r--r--. 1 mfadjobt mfadset 2095795750 Feb 13 10:07 base.dat
J'attends le script shell pour produire 3 fichiers de sortie correspondant à ceux produits par le code perl et à peu près la même durée;
input:
while read inrec # Read base file until EOF do # Start work loop v_pcn=${inrec:0:9} # Get 1st 9 Characters in v_pcn # v_pcn=${v_pcn/' '/0} # Replace blanks with '0' if [[ $v_pcn < '518000000' ]]; then # pcn < "518000000" echo $inrec >> base1.dat # write rec to "base1.dat" elif [[ $v_pcn > '525000000' || $v_pcn == '525000000' ]]; then # pcn >= "525000000" echo $inrec >> base3.dat # write rec to "base3.dat" else # else >= "518000000" & < "525000000" echo $inrec >> base2.dat # write rec to "base2.dat" fi done < base.dat
Output:
open(FILEIN,"base.dat") || die "Could not open FILEIN\n."; open(FILEOUT1,">base1.dat") || die "Could not open FILEOUT1\n."; open(FILEOUT2,">base2.dat") || die "Could not open FILEOUT2\n."; open(FILEOUT3,">base3.dat") || die "Could not open FILEOUT3\n."; $v_break = "518000000"; $v_break2 = "525000000"; #Run until end of file while (<FILEIN>) { $v_pcn = substr($_, 0, 9); if ($v_break gt $v_pcn) { print FILEOUT1 $_; } elsif (($v_pcn ge $v_break) && ($v_pcn lt $v_break2)) { print FILEOUT2 $_; } else { print FILEOUT3 $_; } } #(<FILEIN>) close(FILEIN); close(FILEOUT1); close(FILEOUT2); close(FILEOUT3);
4 Réponses :
Chaque fois que vous avez >> filename
, vous ouvrez à nouveau le fichier, déplacez le pointeur vers la fin du fichier, puis fermez à nouveau le fichier à la fin de l'instruction. Mieux vaut garder les fichiers ouverts.
while read inrec # Read base file until EOF do # Start work loop v_pcn=${inrec:0:9} # Get 1st 9 Characters in v_pcn # v_pcn=${v_pcn/' '/0} # Replace blanks with '0' if [[ $v_pcn < '518000000' ]]; then # pcn < "518000000" echo $inrec >&3 elif [[ $v_pcn > '525000000' || $v_pcn == '525000000' ]]; then # pcn >= "525000000" echo $inrec >&4 else # else >= "518000000" & < "525000000" echo $inrec >&5 fi done < base.dat 3>> base1.dat 4>> base2.dat 5>> base3.dat
Cela ouvrira les fichiers une fois, conservera leurs pointeurs dans les fichiers et devrait aider à accélérer énormément les choses.
Normalement, lorsque le shell est lent, cela est dû aux commandes que vous exécutez, mais rien ici ne génère de sous-shell, alors je regarde le prochain coupable le plus probable - la gestion des fichiers. Et c'est ce que je vois ici.
Si je peux, puisque c'est dans ksh, au lieu d'utiliser echo qui est un utilitaire externe, je devrais utiliser "print -u {file handler number}" puisque print est interne.
J'essaierai ceci immédiatement. Merci.
Merci Tanktalus! Ce changement a fait un changement radical dans la vitesse. Je vais ensuite passer à la commande "print" suggérée par André qui, je crois, apportera des améliorations supplémentaires. Je me demande également dans la comparaison elif que je voulais savoir comment exécuter l'équivalent d'un -ge dans une comparaison de chaînes plutôt que la comparaison composée que j'utilise dans ce code, ce qui pourrait également apporter une petite amélioration mais pas beaucoup. Bien que je me demande en interne si la différence n'en vaut pas la peine, si en interne, il n'y a qu'une seule fois que la deuxième condition entre en jeu.
@AndreGelinas Dans mon ksh, tapez echo
dit que echo est un shell intégré
Le code Perl est compilé en une représentation "binaire". Ensuite, cette représentation binaire est exécutée par un interpréteur hautement optimisé.
Les scripts shell par contre
Je ne suis pas sûr de ce que le shell Korn a intégré, mais bash en a un bon nombre.
L'exécution de commandes externes est coûteuse car elle implique au moins un appel système fork ()
et execve ()
.
En général, un script shell ne sera plus rapide qu'un script Perl que s'il est extrêmement court, c'est-à-dire lorsque le coût de démarrage du compilateur Perl est supérieur au temps réel d'exécution du code.
La réponse courte: ne soyez pas surpris lorsque vous traduisez un script shell en un script Perl équivalent qu'il s'exécutera beaucoup plus rapidement.
Pour éviter toute confusion avec les descripteurs de fichiers, vous pouvez utiliser la boucle for.
for inrec in `cat base.dat` # Read base file until EOF do # Start work loop v_pcn=${inrec:0:9} # Get 1st 9 Characters in v_pcn # v_pcn=${v_pcn/' '/0} # Replace blanks with '0' if [[ $v_pcn < '518000000' ]]; then # pcn < "518000000" echo $inrec >> base1.dat # write rec to "base1.dat" elif [[ $v_pcn > '525000000' || $v_pcn == '525000000' ]]; then # pcn >= "525000000" echo $inrec >> base3.dat # write rec to "base3.dat" else # else >= "518000000" & < "525000000" echo $inrec >> base2.dat # write rec to "base2.dat" fi done
Uniquement testé avec bash
, mais la solution suivante devrait également fonctionner avec certaines versions de ksh
.
Reconsidérez d'abord les limites. Les belles figures rondes nous permettent de regarder les 3 premiers personnages. Cela devrait vous être utile dans toutes les solutions.
Avec tee
, vous pouvez écrire dans stdout et un fichier, mais aussi dans différents processus.
tee < base.dat \ >(grep -E "^([0-4]|50|51[0-7])" > base1.dat) \ >(grep -E "^5(1[89]|2[0-4])" > base2.dat) | grep -E "^(52[5-9]|5[3-9]|6-9])" > base3.dat