2010-09-23 10 views
4

J'ai un fichier de données de près de 9 millions de lignes (bientôt plus de 500 millions de lignes) et je cherche le moyen le plus rapide de le lire. Les cinq colonnes alignées sont rembourrées et séparées par des espaces, donc je sais où sur chaque ligne à chercher les deux champs que je veux. routine Mon Python prend 45 secondes:Quel est le moyen le plus rapide de lire dans un grand fichier de données de colonnes de texte?

import sys,time 

start = time.time() 
filename = 'test.txt' # space-delimited, aligned columns 
trans=[] 
numax=0 
for line in open(linefile,'r'): 
    nu=float(line[-23:-11]); S=float(line[-10:-1]) 
    if nu>numax: numax=nu 
    trans.append((nu,S)) 
end=time.time() 
print len(trans),'transitions read in %.1f secs' % (end-start) 
print 'numax =',numax 

alors que la routine, je suis venu avec dans C est un plus agréable 4 secondes:

#include <stdio.h> 
#include <stdlib.h> 
#include <time.h> 

#define BPL 47 
#define FILENAME "test.txt" 
#define NTRANS 8858226 

int main(void) { 
    size_t num; 
    unsigned long i; 
    char buf[BPL]; 
    char* sp; 
    double *nu, *S; 
    double numax; 
    FILE *fp; 
    time_t start,end; 

    nu = (double *)malloc(NTRANS * sizeof(double)); 
    S = (double *)malloc(NTRANS * sizeof(double)); 

    start = time(NULL); 
    if ((fp=fopen(FILENAME,"rb"))!=NULL) { 
    i=0; 
    numax=0.; 
    do { 
     if (i==NTRANS) {break;} 
     num = fread(buf, 1, BPL, fp); 
     buf[BPL-1]='\0'; 
     sp = &buf[BPL-10]; S[i] = atof(sp); 
     buf[BPL-11]='\0'; 
     sp = &buf[BPL-23]; nu[i] = atof(sp); 
     if (nu[i]>numax) {numax=nu[i];} 
     ++i; 
    } while (num == BPL); 
    fclose(fp); 
    end = time(NULL); 
    fprintf(stdout, "%d lines read; numax = %12.6f\n", (int)i, numax); 
    fprintf(stdout, "that took %.1f secs\n", difftime(end,start)); 
    } else { 
    fprintf(stderr, "Error opening file %s\n", FILENAME); 
    free(nu); free(S); 
    return EXIT_FAILURE; 
    } 

    free(nu); free(S); 
    return EXIT_SUCCESS; 
    } 

Solutions en Fortran, C++ et Java prendre quantités intermédiaires de temps (27 secondes, 20 secondes, 8 secondes). Ma question est: ai-je fait des aberrations scandaleuses dans le ci-dessus (en particulier le C -code)? Et est-il possible d'accélérer la routine Python? Je me suis vite rendu compte que stocker mes données dans un tableau de tuples valait mieux que d'instancier une classe pour chaque entrée.

+2

S'il vous plaît expliquer les chiffres magiques dans votre code C (d'où venez-vous avec 47 et 858226?) – mikerobi

+6

vous devez exécuter le profileur sur votre code python pour voir où il est lent. Aussi, vous devriez essayer de suivre les conventions de style pep8 pour python, ils le rendent beaucoup plus facile à lire. – Daenyth

+1

Pour python: http://stackoverflow.com/questions/1896674/python-how-to-read-huge-text-text-into-memory – karlphillip

Répondre

3

Dans la mise en œuvre de C, vous pouvez essayer les échanger fopen()/fread()/fclose() fonctions de bibliothèque pour le système de niveau inférieur appelle open()/read()/close(). Une accélération peut venir du fait que fread() fait beaucoup de mise en mémoire tampon, alors que read() ne le fait pas. En outre, en appelant moins souvent read() avec des blocs plus volumineux réduira le nombre d'appels système et par conséquent vous aurez moins de basculer entre l'espace utilisateur et l'espace noyau. Ce que fait le noyau lorsque vous émettez un appel système read() (peu importe s'il a été appelé depuis la fonction de bibliothèque fread()) est de lire les données du disque, puis de les copier dans l'espace utilisateur. La partie copie devient coûteuse si vous lancez l'appel système très souvent dans votre code. En lisant en gros morceaux, vous finirez avec moins de changements de contexte et moins de copie. Gardez à l'esprit que read() n'est pas garanti de retourner un bloc du nombre exact d'octets que vous vouliez. C'est pourquoi dans une mise en œuvre fiable et correcte, vous devez toujours vérifier la valeur de retour de read().

+0

Je ne comprends pas. D'abord, vous suggérez de ne pas utiliser la mise en mémoire tampon en passant des fonctions fread()/... aux fonctions read()/.... Ensuite, vous proposez d'utiliser la mise en mémoire tampon en lisant des blocs de données plus volumineux. À mon humble avis vous suggérer d'éviter la mise en mémoire tampon automatique, et d'utiliser réellement la mise en mémoire tampon manuelle avec tout le bogue possible dans une mise en œuvre défectueuse. –

+0

Une implémentation correcte vérifiera toujours le nombre d'octets réellement lus par 'read()'. Le point était qu'appeler 'read (2)' avec un bloc de mémoire de 47 octets est beaucoup moins efficace que de le faire avec un bloc de 1024 octets par exemple. –

+1

Bien sûr, mais c'est exactement pourquoi vous utilisez 'fread()' à la place - il appellera 'read()' avec une valeur plus grande et fera la mise en mémoire tampon pour vous. – caf

3

Une approche qui pourrait probablement être appliquée à la version C, C++ et python serait d'utiliser le mappage mémoire du fichier. L'avantage le plus significatif est qu'il peut réduire la quantité de double manipulation des données car il est copié d'un tampon à l'autre. Dans de nombreux cas, il y a également des avantages en raison de la réduction du nombre d'appels système pour les E/S.

+0

+1; le mappage de la mémoire peut être effectué de manière native en Java et Fortran via des liaisons C ... – Christoph

-1

Une autre possible accélération, étant donné le nombre de fois que vous avez besoin de le faire, est d'utiliser des pointeurs vers S et nu au lieu de l'indexation dans des tableaux, par exemple,

double *pS = S, *pnu = nu; 
... 
*pS++ = atof(sp); 
*pnu = atof(sp); 
... 

De plus, puisque vous êtes toujours convertir de char en double aux mêmes emplacements dans buf, pré-calculer les adresses en dehors de votre boucle au lieu de les calculer chaque fois dans la boucle.

+2

Veuillez mesurer sans faire cela. Cela n'aide pas à la lisibilité et n'a généralement aucun effet sur les performances. –

+0

Merci pour votre réponse - je reçois un défaut de segmentation, quand je fais cela, cependant? Aussi, si BPL est # défini, le compilateur ne fera-t-il pas ce pré-calcul avant l'exécution pour moi? – Chris

+0

Je pense que la plupart des compilateurs modernes effectueraient ces types d'optimisations automatiquement. D'où le commentaire de Didier. – torak

4

Quelques points:

  1. Votre routine C est la tricherie; il est envoyé avec la taille de fichier, et est pré-allocation ...

  2. Python: envisager d'utiliser array.array('d') ... un chacun pour S et nu. Ensuite, essayez de pré-allocation.Python: écrivez votre routine en tant que fonction et appelez-la - accéder aux variables locales de la fonction est plutôt plus rapide que d'accéder aux variables globales du module.

+1

Merci - Je pense que votre point 2. est pourquoi la lecture dans des tableaux chiffrés pré-alloués m'a descendu à 20 secondes. L'appeler comme une fonction et nous sommes à 17,3 secondes. Quelqu'un peut-il donner des indications sur la façon d'implémenter la méthode itérateur? – Chris

1

Vous avez les 1 et les arguments BPL dans le mauvais sens autour de fread() (la façon dont vous l'avez, il pourrait lire une ligne partielle, que vous ne testez pas). Vous devriez également tester la valeur de retour de fread()avant de vous essayez d'utiliser les données retournées.

Vous pouvez peut-être en mesure d'accélérer la version C un peu en lisant plus d'une ligne à la fois

#define LINES_PER_READ 1000 
char buf[LINES_PER_READ][BPL]; 

/* ... */ 

    while (i < NTRANS && (num = fread(buf, BPL, LINES_PER_READ, fp)) > 0) { 
     int line; 

     for (line = 0; i < NTRANS && line < num; line++) 
     { 
      buf[line][BPL-1]='\0'; 
      sp = &buf[line][BPL-10]; S[i] = atof(sp); 
      buf[line][BPL-11]='\0'; 
      sp = &buf[line][BPL-23]; nu[i] = atof(sp); 
      if (nu[i]>numax) {numax=nu[i];} 
      ++i; 
     } 
    } 

Sur les systèmes de soutien posix_fadvise(), vous devez aussi le faire dès le départ, après l'ouverture du fichier:

posix_fadvise(fileno(fp), 0, 0, POSIX_FADV_SEQUENTIAL); 
Questions connexes