2
votes

Diviser le fichier avec If-Then-Else en 3 fichiers

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


0 commentaires

4 Réponses :


3
votes

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.


4 commentaires

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é



2
votes

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

  • analyser chaque ligne à chaque exécution,
  • les redirections de fichiers sont répétées chaque fois qu'elles sont exécutées,
  • exécute généralement des commandes externes , sauf si la commande se trouve être un shell intégré.

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.


0 commentaires

0
votes

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


0 commentaires

0
votes

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


0 commentaires