optimisation sed (modification importante de fichier basé sur ensemble de données plus petit)

StackOverflow https://stackoverflow.com/questions/848914

Question

Je dois faire face à de très gros fichiers de texte brut (plus de 10 giga-octets, oui, je sais que cela dépend de ce que nous devrions appeler grand), avec des lignes très longues.

Ma tâche la plus récente implique une édition en ligne à partir des données d'un autre fichier.

Le fichier de données (qui devrait être modifié) contient 1500000 lignes, chacun d'entre eux sont par exemple 800 caractères longs. Chaque ligne est unique, et ne contient qu'un seul numéro d'identification, chaque numéro d'identification unique)

Le fichier de modification est, par exemple 1800 lignes, contient un numéro d'identification et un montant et une date qui devrait être modifié dans le fichier de données.

Je viens transformais (avec Vim regex) le fichier de modification à sed, mais il est très inefficace.

Disons que j'ai une ligne comme celui-ci dans le fichier de données:

(some 500 character)id_number(some 300 character)

Et je dois modifier les données dans la partie 300 char.

Sur la base du fichier de modification, je viens avec des lignes sed comme ceci:

/id_number/ s/^\(.\{650\}\).\{20\}/\1CHANGED_AMOUNT_AND_DATA/

J'ai donc 1800 lignes comme ça.

Mais je sais que, même sur un serveur très rapide, si je fais un

sed -i.bak -f modifier.sed data.file

Il est très lent, car il doit lire chaque modèle x chaque ligne.

N'y at-il une meilleure façon?

Remarque: Je ne suis pas un programmeur, n'avais jamais appris (à l'école) sur les algorithmes. Je peux utiliser awk, sed, une version obsolète de Perl sur le serveur.

Était-ce utile?

La solution

Mes approches proposées (par ordre de préférence) serait de traiter ces données comme:

  1. Une base de données (même un simple DB à base SQLite avec un indice interprétera beaucoup mieux que sed / awk sur un fichier 10GB)
  2. un fichier plat contenant longueurs d'enregistrement fixes
  3. un fichier plat contenant des longueurs variables d'enregistrement

En utilisant une base de données prend soin de tous ces petits détails qui ralentissent le traitement du fichier texte (trouver l'enregistrement que vous aimez, modifier les données, le stockage de nouveau au DB). Jetez un oeil DBD :: SQLite dans le cas de Perl.

Si vous voulez coller avec des fichiers plats, vous aurez envie de maintenir un index manuellement à côté du gros fichier afin que vous puissiez regarder plus facilement les numéros de disques dont vous aurez besoin pour manipuler. Ou, mieux encore, peut-être vos numéros d'identification sont vos numéros d'enregistrement?

Si vous avez des longueurs d'enregistrement variables, je vous suggère de convertir en longueurs d'enregistrements fixes (car il apparaît que votre ID est longueur variable). Si vous ne pouvez pas faire cela, peut-être toutes les données existantes se déplaceront jamais autour dans le fichier? Ensuite, vous pouvez maintenir cet indice a été mentionné précédemment et ajouter de nouvelles entrées au besoin, à la différence est qu'au lieu de l'index pointant pour enregistrer le numéro, vous pointez maintenant à la position absolue dans le fichier.

Autres conseils

Je vous suggère un programm écrit en Perl (comme je ne suis pas un gourou sed / awk et je ne sais pas ce qu'ils sont exactement capables de).

« algorithme » est simple: vous avez besoin de construire, d'abord, un hashmap qui pourrait vous donner la nouvelle chaîne de données à appliquer pour chaque ID. Ceci est réalisé à lire le fichier de modification bien sûr.

Une fois cette hasmap à population vous pouvez parcourir chaque ligne de votre fichier de données, lisez l'ID au milieu de la ligne, et de générer la nouvelle ligne que vous avez décrit ci-dessus.

Je ne suis pas un gourou Perl aussi, mais je pense que le programm est assez simple. Si vous avez besoin d'aide pour l'écrire, le demander: -)

Avec Perl, vous devez utiliser substr pour obtenir id_number, surtout si id_number a une largeur constante.

my $id_number=substr($str, 500, id_number_length);

Après que si id_number $ est à portée, vous devez utiliser substr pour remplacer le reste du texte.

substr($str, -300,300, $new_text);

expressions régulières de Perl sont très rapides, mais pas dans ce cas.

Ma suggestion est, ne pas utiliser la base de données. script perl bien écrit devancera base de données dans un ordre de grandeur dans ce genre de tâche. Croyez-moi, j'ai beaucoup d'expérience pratique avec elle. Vous n'avez données importées dans la base de données lorsque perl seront terminés.

Lorsque vous écrivez 1500000 lignes avec 800 caractères, il semble 1.2GB pour moi. Si vous avez disque très lent (30 Mo / s) que vous le lirez dans un 40 secondes. Avec 50 meilleurs -> 24s, 100 -> 12s et ainsi. Mais la vitesse perl recherche de hachage (comme db rejoindre) sur le processeur 2GHz est au-dessus 5Mlookups / s. Cela signifie que votre CPU travail lié sera en quelques secondes et vous IO travail lié sera en dizaines de secondes. Si c'est vraiment un nombre 10Go changeront, mais la proportion est la même.

Vous avez pas spécifié si la modification de données change de taille ou non (si la modification peut être fait en place) ainsi nous ne l'assumer et travaillerons comme filtre. Vous n'avez pas spécifié quel format de votre fichier « modificateur » et quel type de modification. On suppose qu'il est séparé par onglet quelque chose comme:

<id><tab><position_after_id><tab><amount><tab><data>

Nous allons lire les données de stdin et écrire à stdout et script peut être quelque chose comme ceci:

my $modifier_filename = 'modifier_file.txt';

open my $mf, '<', $modifier_filename or die "Can't open '$modifier_filename': $!";
my %modifications;
while (<$mf>) {
   chomp;
   my ($id, $position, $amount, $data) = split /\t/;
   $modifications{$id} = [$position, $amount, $data];
}
close $mf;

# make matching regexp (use quotemeta to prevent regexp meaningful characters)
my $id_regexp = join '|', map quotemeta, keys %modifications;
$id_regexp = qr/($id_regexp)/;     # compile regexp

while (<>) {
  next unless m/$id_regexp/;
  next unless $modifications{$1};
  my ($position, $amount, $data) = @{$modifications{$1}};
  substr $_, $+[1] + $position, $amount, $data;
}
continue { print }

Sur un ordinateur portable à moi, il faut environ une demi minute pour 1,5 million de lignes, 1800 ids de consultation, les données 1.2Go. Pour 10Go, il ne devrait pas être plus de 5 minutes. Est-il raisonnable rapide pour vous?

Si vous commencez à penser que vous n'êtes pas IO lié (par exemple, si utiliser un NAS), mais CPU lié peut sacrifier une certaine lisibilité et changer à ceci:

my $mod;
while (<>) {
  next unless m/$id_regexp/;
  $mod = $modifications{$1};
  next unless $mod;
  substr $_, $+[1] + $mod->[0], $mod->[1], $mod->[2];
}
continue { print }

Vous devriez certainement utiliser une base de données, comme MikeyB suggéré .

Si vous ne souhaitez pas utiliser une base de données pour une raison quelconque, si la liste des modifications s'adaptera en mémoire (comme il actuellement à 1800 lignes), la méthode la plus efficace est un Hashtable peuplé avec les modifications suggéré par yves Baumes .

Si vous arrivez au point où même la liste des modifications est énorme, vous devez trier les fichiers par leurs ID, puis effectuer une fusion de la liste - essentiellement:

  1. Comparez l'ID au « top » du fichier d'entrée avec l'ID au « top » du fichier des modifications
  2. Régler le dossier en conséquence si elles correspondent
  3. Écrivez-
  4. Jeter la ligne « top » de quel que soit le fichier le plus faible (par ordre alphabétique ou numérique) ID et lire une autre ligne de ce fichier
  5. Aller à 1.

Dans les coulisses, une base de données utilisera presque certainement une liste de fusion si vous effectuez cette modification à l'aide d'une seule commande SQL UPDATE.

Bonne affaire sur le sqlloader ou DataDump décision. C'est la voie à suivre.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top