10
votes

Le moyen le plus rapide des valeurs multiples Grep du fichier en python

  • J'ai un fichier de lignes de 300 m (INTERPORTFILE), avec 2 colonnes séparées par un onglet. li>
  • J'ai aussi une liste de 1000 articles uniques (Vals). Li>

    Je souhaite créer un dictionnaire avec la colonne 1 en tant que clé et colonne 2 en tant que valeur pour toutes les lignes dans entréeFile em> où les premières colonnes se produisent dans Vals em>. Quelques éléments dans Vals em> ne se produisent pas dans le fichier, ces valeurs doivent être enregistrées dans une nouvelle liste. Je peux utiliser jusqu'à 20 threads pour accélérer ce processus. P>

    Quel est le moyen le plus rapide d'y parvenir? P>

    Mon meilleur essai jusqu'à présent: P>

    [1]
    


13 commentaires

Pouvez-vous fournir un échantillon des fichiers d'entrée et la sortie souhaitée? Juste pour faire clairement les choses.


Essayez peut-être d'utiliser la commande Open (File, R) de Python et voyez si cela vous donne un coup de pouce.


@ Sshashank124 C'était mon premier essai, mais cette approche est beaucoup plus rapide ...


@Jeanrjc a ajouté un exemple


@ Sshashak124, Grep est un code optimisé incroyablement bien puiser, l'utiliser pour filtrer les lignes d'intérêt diminuera un peu sur la charge de Python (en supposant qu'une petite partie des lignes dans le fichier d'intérêt). Il a également un avantage supplémentaire de courir dans son propre fil.


@MarkRansom était une faute de frappe, résolue


Si VAL se trouve sur plusieurs lignes dans votre fichier TSV, vous écrasez les hits antérieurs et finissez par stocker uniquement le succès le plus récent. Cela ne ressemble pas à ce que vous voulez (aller par "un dictionnaire avec la colonne 1 en tant que clé et colonne 2 en tant que valeur pour toutes les lignes dans l'entrée de l'entrée où les premières colonnes se produisent dans les vals").


@ALP Je ne l'ai pas mentionné, mais les valeurs de la colonne 1 sont commandées et uniques. Votre solution rend la commande grep comme ceci: grep "\ | val1 \ | val2" file.txt au lieu de grep "VAL1 \ | VAL1 \ | VAL1 | TTXT


@Alp, je changerais le pour en boucle à une jointure avec un générateur: cmd + = "\\ |" .join ("^" + val + "[[[: Espace:] ] "Pour Val in Vals) Ensuite, laissez le Vals [0] .


@MarkRansom Juste dans le processus d'écriture qui! Merci @JetSe: le point crucial est que si vous incluez val [0] sans l'ancre et la classe de caractères d'espace, vous pourriez obtenir un faux coup.


Combien de temps le grep prend-il seul pour traiter le fichier, sans python impliqué? Combien de temps faut-il juste pour copier le fichier à NULL? Ce sera votre limite inférieure.


@MarkRansom presque tout le temps est passé dans grep . Étant donné que chaque clé ne se produit qu'une seule fois dans les données et il n'y a que 1000 d'entre eux, la quantité de travail effectuée à Python est triviale.


La vraie question est donc de savoir s'il y a un moyen d'améliorer sur grep , raison pour laquelle je vous ai demandé de le comparer à une copie simple. Grep est bien optimisé, mais je ne sais pas à quel point c'est efficace avec une expression aussi compliquée. Il est important que vous faites la comparaison pour vous assurer que le fichier n'est pas dans le cache du système d'exploitation ou produira des temps trop optimistes.


3 Réponses :


0
votes

Ce n'est pas terriblement efficace de mémoire (pour un fichier de 300 millions de lignes pouvant poser un problème). Je ne peux pas penser à un moyen de sauvegarder les valeurs non trouvées dans une compréhension, sauf en enregistrant toutes les valeurs (ou en lisant le fichier deux fois). Je ne pense pas que les threads aideront beaucoup, car le fichier d'E / S sera probablement le goulot d'étranglement de la performance. Je suppose qu'un onglet est le caractère de délimitation dans le fichier. (Vous n'avez pas dit, mais l'exemple de données semble avoir un onglet.)

vals = [1,2,594592888]

with open(self.inputfile,'r') as i_file:
    all_vals = {
        int(t[0]):int(t[1])
        for t in (
                line.strip().split('\t')
                for line in i_file
        )
    }

newDict = {
    t[0]:t[1] for t in filter(lambda t: t[0] in vals, all_vals.items())
}

notFound = list(set(all_vals.keys()).difference(newDict.keys()))


2 commentaires

Les goulots d'étranglement io sont précisément le genre de situation où le threading aide à Python.


Cette méthode est en effet une mémoire inefficace (plus de 30 g), cela n'a causé aucun problème. Toujours Grep est beaucoup plus rapide ...



0
votes

Si je vous comprends correctement, vous ne voulez pas de ligne de fichier qui ne correspond pas à vous Vals code> Puisque vous parlez d'énormes fichiers et un nombre assez petit de valeurs recherchées, j'irais quelque chose comme:

split -l 1000000 filename


1 commentaires

Je vois ... uhm ... je vais enquêter sur elle et modifier ma réponse. Cependant, j'ai déjà édité cela suggérant un moyen facile d'utiliser votre CPU multiple



9
votes

Vous avez clarifié dans un commentaire que chaque clé se produit au plus une fois dans les données. Il découle de cela et du fait qu'il n'y a que 1000 clés que la quantité de travail effectuée dans Python est triviale; Presque tout votre temps est passé à attendre la sortie de grep . Qui va bien; Votre stratégie de délégation de la ligne d'extraction à une utilité spécialisée reste sonore. Mais cela signifie que les gains de performance doivent être trouvés sur le côté de l'extraction de ligne.

Vous pouvez accélérer les choses en optimisant votre regex. Par exemple, au lieu de xxx

, vous pouvez utiliser: xxx

de sorte que l'ancre ne doit pas être assortie séparément pour chaque alternative. Je vois une amélioration de 15% sur les données de test (10 millions de lignes, 25 clés) avec ce changement.

Une optimisation supplémentaire consiste à unifier les préfixes courants dans l'alternance: 266 \ | 801 \ | 810 peut être remplacé par l'équivalent 266 \ \ | 8 \ (01 \ | 10 \) . La réécriture de la réégalité de 25 clés de cette manière donne près d'une vitesse de 50% sur les données de test.

à ce point grep commence à afficher ses limites. Il semble que c'est la CPU-liée: iostat révèle que chaque amélioration successive de la regex augmente le nombre de demandes IO par seconde, tandis que GREP est en cours d'exécution. Et réexécuter grep avec un cache de page chauffé et l'option - mmap ne pas accélérer les choses (car elle serait probablement si le fichier io était un goulot d'étranglement). Une vitesse supérieure nécessite donc probablement une utilité avec un moteur de regex plus rapide.

un tel est AG (source ici ), dont la mise en œuvre de la regex effectue également une optimisation automatique, vous n'avez donc pas besoin de faire beaucoup de main-d'œuvre. Bien que je n'ai pas pu obtenir grep pour traiter les données de test en moins de ~ 12s sur ma machine, AG se termine dans ~ 0.5S pour toutes les variantes de regex décrit ci-dessus.


2 commentaires

Comment ressemble votre commande AG? J'ai installé AG, lorsque vous utilisez exactement la même regex que dans Grep, AG ne trouvez rien pendant que grep fait ... j'ai utilisé Grep "^ (266 \ | 801 \ 0. 810) [[: espace:]]" File.txt and: AG "^ (266 \ | 801 \ | 810) [[[: espace:]]" File.txt


AG utilise la syntaxe PCRE pour des expressions régulières, vous ne devez donc pas échapper à l'opérateur de l'alternatif (le | ). En outre, je ne sais pas si cela prend en charge les classes de caractère nommées; Vous devrez peut-être remplacer [[: espace]] avec \ s . BTW, pour grep Vous devez également échapper aux parenthèses.