Forum |  HardWare.fr | News | Articles | PC | S'identifier | S'inscrire | Shop Recherche
1149 connectés 

  FORUM HardWare.fr
  Programmation
  PHP

  Traitement de gros fichiers CSV

 


 Mot :   Pseudo :  
 
Bas de page
Auteur Sujet :

Traitement de gros fichiers CSV

n°2264303
Malicote
Posté le 12-08-2015 à 13:13:36  profilanswer
 

Bonjour,
 
Je me permets d'ouvrir ce sujet car je dois traiter de gros fichiers CSV (1go par fichier pour environ 450 millions de lignes et y a 23 fichiers comme ça), la lecture ligne par ligne du fichier par PHP prend 200s (ce qui est raisonnable) mais le stockage de ces données dans de nouveaux fichiers prend lui énormément de temps (là ça fait 1h que ça tourne et je n'ai crée qu'une heure et y en a plus de 30 par fichier).  
 
En gros voici mon code :
 
Je lis chaque ligne du fichier csv avec fgetcsv, chaque ligne contient comme donnée l'heure / la latitude / longitude / et la valeur, ensuite je crée un dossier par couple latitude / longitude (avec un dossier par latitude arrondie pour amoindrir le nombre de dossiers) et un fichier par heure, le fichier par heure contenant toutes les données associées à cette heure-ci. J'utilise la commande echo pour écrire les données dans les fichiers / ajouter les données à ceux-ci.
 
J'avais essayé de stocker tout ça dans MySQL mais c'est encore bien plus long.
 
Vous avez une idée de la technique à employer pour traiter un tel volume de données ? y a-t-il un langage approprié pour ça ?
 
Je vous remercie d'avance pour votre aide.
 
Bonne journée à vous

mood
Publicité
Posté le 12-08-2015 à 13:13:36  profilanswer
 

n°2264508
NewsletTux
<Insérez ici votre vie />
Posté le 17-08-2015 à 16:30:54  profilanswer
 

salut,
clairement, je ne sais pas si PHP est le meilleur langage que ça ... J'aurais bien dit du shell ou du PowerShell (dépend de ton environnement), au moins tu ne serais pas tributaire d'une série de variables comme celles d'Apache (taille max mémoire, etc.)
surtout que créer des dossiers, en shell ça se fait très bien. Des requêtes MySQL ça doit pouvoir se faire (au besoin un appel d'un PHP). Idem avec PowerShell.

n°2264511
Malicote
Posté le 17-08-2015 à 16:47:01  profilanswer
 

Bonjour,
 
Oui PHP n'est pas adapté, c'est clair.  
 
J'ai donc testé le sh, ça marche bien mais ça met quand même du temps, je pense que le mieux serait de traiter en C++, je ferai un code prochainement et je le posterai sur le forum (si d'autres sont intéressés).
 
J'ai aussi commencé à tester Python, l'occasion d'apprendre un nouveau langage.
 
La solution que j'ai adopté, en attendant de traiter de gros fichiers csv en C, c'est de traiter à la volée les données initiales en découpant le fichier en plein de petits fichiers, c'est très rapide, aussi rapide que si je stockais ces données dans des fichiers en dur ou dans une BDD.

n°2264517
czh
Posté le 17-08-2015 à 22:34:46  profilanswer
 

salut,
 
D'après la doc, fgetcsv lit la totalité du fichier puis charge les données en mémoire, donc la mémoire contient au moins 1 Go de données après l'appel à fgetcsv .
Quand on travaille avec de gros fichiers, le mieux est d'ouvrir le fichier avec fopen et récupérer les lignes avec fgets voir fread. Ca permet de traiter des fichiers de plusieurs Go avec moins de 50 Mo. Ce qui revient à traiter le fichier à la volée comme dit plus haut, et donc de commencer à traiter le fichier dès la première seconde.
 
Comment sont écrit les fichiers ? Si le code utilisé ressemble à exec("echo toto > aaa" ); je comprend que ça prenne beaucoup de temps. A chaque appel de commande, php doit redémarrer un /bin/sh qui lui même démarre /bin/echo (création processus x2, allocation mémoire, réservation pid etc.) ce qui prend plus de temps que faire un fopen, fwrite, fclose. (ou en plus court file_put_contents)
 
Pour étayer mes propos :
 

Code :
  1. $ time echo '<?php for ($i=0;$i<1000;$i++) { exec("echo a > bb" ); }'|php
  2. real 0m2.703s
  3. user 0m0.199s
  4. sys 0m2.295s
  5. $ time echo '<?php for ($i=0;$i<1000;$i++) { file_put_contents("bb", "a" ); }'|php
  6. real 0m0.377s
  7. user 0m0.012s
  8. sys 0m0.197s
  9. $ time echo '<?php for ($i=0;$i<1000;$i++) { }'|php
  10. real 0m0.029s
  11. user 0m0.019s
  12. sys 0m0.008s
  13. $ time echo '<?php for ($i=0;$i<1000;$i++) { $f = fopen("bb", "w" ); fclose($f); }'|php
  14. real 0m0.073s
  15. user 0m0.020s
  16. sys 0m0.044s
  17. $ time echo '<?php for ($i=0;$i<1000;$i++) { $f = fopen("bb", "w" ); fwrite($f, "a" ); fclose($f); }'|php
  18. real 0m0.867s
  19. user 0m0.043s
  20. sys 0m0.174s


 
php en mode shell (autrement dit php-cli) n'est pas tributaire d'Apache.
 
Cette tâche est basique, et il n'y a pas de langage plus adapté que d'autre, tous se valent.


Message édité par czh le 18-08-2015 à 00:11:45
n°2264550
Soileh
Lurkeur professionnel
Posté le 18-08-2015 à 22:43:42  profilanswer
 

:hello: !
 
Si tu veux importer tes données dans une BdD, il me semble que chaque SGBDR propose sa solution : tu as la commande LOAD DATA INFILE pour MySQL, SQL*Loader pour Oracle,...
 
 :jap:


---------------
And in the end, the love you take is equal to the love you make
n°2264761
rufo
Pas me confondre avec Lycos!
Posté le 25-08-2015 à 10:53:48  profilanswer
 

Perso, j'ai été amené à faire un mix entre php, mysql et un binaire en C pour faire un gros traitement (analyse sémantique de tickets puis calcul des taux de corrélation entre eux afin d'identifier, pour un ticket donné, ceux similaires).
 
Je travaille sur un ensemble restreint d'environ 5000 tickets et 3500 termes, donc une matrice de 5000x3500. Je devais calculer le produit de la transposée de la matrice par elle-même. J'ai codé toute la partie d'analyse textuelle en php avec stockage de la matrice dans Mysql (une simple table de 3 champs : ligne, colonne, valeur pesant environ 160 Mo). Après, je faisais, via le script php, un export de la table en CSV (environ 300 Mo) avec une requête SQL puis le script php appelait un programme écrit en C pour faire le calcul matriciel. Enfin, le script php récupérait le csv généré et l'importait dans Mysql via une requête SQL.
 
A noter que j'avais aussi testé avec le calcul du produit matriciel via une requête SQL. En gros, avec le programme en C, ça met moins de 3 min; avec Mysql, environ 20 min. Avec PHP, c'est même pas la peine, ça se chiffre en heures :(
 
PHP n'étant pas compilé, il n'est pas adapté à de gros traitements. Pour ça, le mieux reste le C.


---------------
Astres, outil de help-desk GPL : http://sourceforge.net/projects/astres, ICARE, gestion de conf : http://sourceforge.net/projects/icare, Outil Planeta Calandreta : https://framalibre.org/content/planeta-calandreta
n°2264802
gilou
Modérateur
Modzilla
Posté le 25-08-2015 à 18:30:55  profilanswer
 

Oui, enfin, pour faire ce qu'il veut, j'utiliserais Perl ou Python. C'est le genre de langage adapté à ce genre de traitement (c'est pas pour rien qu'ils sont pas mal utilisé en traitement des données génétiques)
 
 
Grosso modo en perl ça ressemblerait à
 
#!/usr/local/bin/perl -w
use strict;
use warnings;
use autodie;
 
my $filename = '...';
open (my $data_fh, '<', $filename);
while ( <$data_fh> ) {
    if (/^...... un pattern correspondant a la structure des lignes..$/o) {
        # on a matché les groupes
        my ($heure, $latitude, $longitude, $valeur) = ($1, $2, $3, $4);
        $latitude = arrondir($latitude); # routine arrondir a écrire
        mkdir $latitude unless (-d $latitude);
        mkdir $latitude.'/'.$longitude unless (-d $latitude.'/'.$longitude);
        open ( my $out_fh, '>>', $latitude.'/'.$longitude.'/'.$heure);
        print $out_fh $valeur, "\n"; # ou ce qui conviendra
        close ( $out_fh );
    }
}
close ($data_fh);
 
Et les interfaces DBI pour faire communiquer du code perl avec une BDD, c'est un truc qui marche très bien.
 
Et si on dispose de plus de 1Go de mémoire pour le programme, ce qui n'est pas si rare de nos jours, on peut d'abord
- lire en mémoire tout le fichier dans une structure type hash complexe,%data:
  quand on a lu  ($heure, $latitude, $longitude, $valeur), on va faire
  push @{$data{$latitude}{$longitude}{$heure}}, $valeur
- parcourir le hash pour créer les répertoires et écrire dans les fichiers (ça permet de n'ouvrir qu'une fois chaque fichier dans lequel on va écrire)
Bref ce sera optimal en temps, puisqu'on ne fait que le nombre minimal possible d'appels au système pour créer et ouvrir des fichiers.
Le code ressemblerait à ceci:
 
#!/usr/local/bin/perl -w
use strict;
use warnings;
use autodie;
 
my $filename = '...';
open (my $data_fh, '<', $filename);
my %data;
while ( <$data_fh> ) {
    if (/^...... un pattern correspondant a la structure des lignes..$/o) {
        # on a matché les groupes
        my ($heure, $latitude, $longitude, $valeur) = ($1, $2, $3, $4);  
        # note un code optimal utilisera des "named capture groups" directement pour éviter 4 assignations par ligne lue
        $latitude = arrondir($latitude); # routine arrondir a écrire
        if ( $data{$latitude}{$longitude}{$heure} ) {
            push @{$data{$latitude}{$longitude}{$heure}}, $valeur;
        }
        else {
            $data{$latitude}{$longitude}{$heure} = [$valeur];
        }
    }
}
close ($data_fh);  
 
foreach my $latitude (keys %data) {
    mkdir $latitude unless (-d $latitude);
    foreach my $longitude (keys %{$data{$latitude}}) {
        mkdir $latitude.'/'.$longitude unless (-d $latitude.'/'.$longitude);
        foreach my $heure (keys %{$data{$latitude}{$longitude}}) {
            open ( my $out_fh, '>', $latitude.'/'.$longitude.'/'.$heure);
            foreach my $valeur (@{$data{$latitude}{$longitude}{$heure}}) {
                print $out_fh $valeur, "\n"; # ou ce qui conviendra
            }
            close ( $out_fh );
        }
    }
}
 
 
Le code  
        if ( $data{$latitude}{$longitude}{$heure} ) {
            push @{$data{$latitude}{$longitude}{$heure}}, $valeur;
        }
        else {
            $data{$latitude}{$longitude}{$heure} = [$valeur];
        }
peut peut-être être remplacé par
       $data{$latitude}{$longitude}{$heure} //= [];
       push @{$data{$latitude}{$longitude}{$heure}}, $valeur;
c'est a tester pour voir si c'est plus rapide.
 
Au prix d'un flag, on peut encore optimiser: quand on crée un répertoire, inutile de tester l'existence des sous répertoires ou fichiers, et les tests sur le file system sont probablement plus couteux en temps d'exécution que le test d'un flag.
 
A+,


Message édité par gilou le 25-08-2015 à 22:12:56

---------------
There's more than what can be linked! --    Iyashikei Anime Forever!    --  AngularJS c'est un framework d'engulé!  --

Aller à :
Ajouter une réponse
  FORUM HardWare.fr
  Programmation
  PHP

  Traitement de gros fichiers CSV

 

Sujets relatifs
compare 2 fichiers (lignes et colonnes)VBA sélectionner contenus de plusieurs fichiers excel
liste de fichiers dans un array trié par date : 3eme fichier non listéVBS copie de fichiers avec recherche de nom
[BATCH] Pb numérotation de fichiersException de sécurité tout en accédant à des données de fichiers XML (
Enorme difference de temps de traitement avec / sans debugdecoupage d'un fichier CSV
fichiers temporaires TomcatVBA : Compter le nombre de fichiers PDF dans des sous-dossiers
Plus de sujets relatifs à : Traitement de gros fichiers CSV


Copyright © 1997-2022 Hardware.fr SARL (Signaler un contenu illicite / Données personnelles) / Groupe LDLC / Shop HFR