Pourquoi ?
les deux fonctions se basent sur le chargement de l'ensemble du fichier dans la mémoire. Puisqu'il s'agit de fichiers texte de grande taille, ça risque de coincer sur la RAM disponible.
Charger une ligne sans utiliser un index
Le but de cet article est de trouver une méthode et un script PHP permettant de lire le plus rapidement que possible une ligne dans un fichier texte. Pour l'exemple, mettons que l'on veuillez charger la ligne numéro 4 000 dans un fichier texte. En langage informatique, le script PHP doit repérer le 4 000 ème retour à la ligne dans le fichier texte puis retourner les caractères contenus jusqu'au prochain retour de chariot.Pour avoir des mesures et un benchmark qui tiennent la route, nous simulons 4200 chargements de lignes.
<?php
$t0 = microtime(true);
$file = 'corpus.txt';
for ($i = 0; $i < 4200; $i++) {
//ouverture du fichier corpus en mode lecture seule
$fp = @fopen($file, "r");
if ($fp) {
//probablement inutile
rewind($fp);
$count_line = 0;
//Boucle sur toutes les lignes du fichier
while (!feof($fp)) {
//Lire la ligne, c-a-d jusqu'au prochain line break
$lines = fgets($fp);
//Test du n° de ligne
if ($count_line == $i) {
$line_contents = fread($fp, $len);
break;
}
$count_line++;
}
}
fclose($fp);
}
echo (microtime(true) - $t0);
?>
Temps de traitement: 3.2104468345642 secondes
Qu'a fait le script PHP pour nous retourner les lignes ?
Le script a cherché dans le fichier texte les lignes 1, 2, 3, 4 .... jusqu'à 4200. Pour ce faire, à chaque fois, les fonctions PHP parcourent l'ensemble du fichier texte pour trouver la bonne ligne puisque on ne sait pas où se trouver le retour de chariot n° 1, 2, 3, ... dans le fichier texte.
Parcourir un fichier texte à l'aide d'un index
Afin d'optimiser l'extraction d'une ligne à partir d'un fichier texte, faisons un test avec un index. Le principe est de mémoriser l'emplacement des retours chariots dans le corpus. Ainsi, à chaque lecture, le script PHP peut se placer directement à la position à lire sans avoir à parcourir l'ensemble du fichier texte.Création d'un index
En amont, le texte est analysé puis la position de chaque retour chariot est mémorisée. Pour le stockage de ces données, nous allons utiliser un base Sqlite et un fichier.Pour la base de données Sqlite, les fonctions sont rassemblées dans une classe PHP nommée Map_corpus
CREATE TABLE IF NOT EXISTS TB_MAP (
MAP_LINE_ID INTEGER PRIMARY KEY AUTOINCREMENT,
MAP_OFFSET_START INTEGER NOT NULL,
MAP_OFFSET_END INTEGER NOT NULL
)
Voyons comment créer l'index :
<?php
//Class Sqlite
$map_corpus = new Map_corpus();
$map_corpus->connect();
$count_line = 0;
$offset_start = 0;
$offset_end = 0;
//Fichier texte à analyser
$fp = @fopen('corpus.txt', "r");
//Fichier index
$fwrite = @fopen('corpus.idx', "w");
if ($fp) {
$map_corpus->transaction_start();
while (!feof($fp)) {
$lines = fgets($fp);
$offset_end+=strlen($lines);
$count_line++;
//Insérer dans la base de donnée
$map_corpus->append_line_index($offset_start, $offset_end);
$rec = str_pad((string) $offset_start, 10, '0', STR_PAD_LEFT);
$rec.= str_pad((string) $offset_end, 10, '0', STR_PAD_LEFT);
//Ecrire dans le fichier index
fwrite($fwrite, $rec);
$offset_start = $offset_end;
}
$map_corpus->transaction_commit();
fclose($fwrite);
fclose($fp);
}
?>
L'utilisation d'un index au format fichier texte et en même temps au format Sqlite sert uniquement à tester les 2 systèmes, à savoir lequel est le plus performant.
Lecture d'une ligne avec un index
L'index que ce soit sous forme d'une base de donnée ou que ce soit sous forme d'un fichier, nous permet de savoir à l'avance où se trouve exactement chaque ligne dans le texte.<?php
//Méthode avec l'index Sqlite
$t0 = microtime(true);
$map_corpus = new Map_corpus();
$map_corpus->connect();
for ($i = 0; $i < 4200; $i++) {
$fp = @fopen($src_file, "r");
if ($fp) {
$n = $map_corpus->get_offset($i); //debut
rewind($fp);
fseek($fp, $n['MAP_OFFSET_START']);
$len = intval($n['MAP_OFFSET_END'] - $n['MAP_OFFSET_START']);
$contents = fread($fp, $len);
}
fclose($fp);
}
echo (microtime(true) - $t0) . '<br>';
//Méthode avec un fichier index
$t0 = microtime(true);
for ($i = 0; $i < 4199; $i++) {
$fwrite = @fopen('corpus.idx', "r");
$fp = @fopen('corpus.txt', "r");
if ($fwrite) {
rewind($fwrite);
fseek($fwrite, $i * 20);
$contents = fread($fwrite, 20);
$offset_start = (int) substr($contents, 0, 10);
$offset_end = (int) substr($contents, 10, 10);
rewind($fp);
fseek($fp, $offset_start);
$contents = fread($fp, ($offset_end - $offset_start));
}
fclose($fwrite);
fclose($fp);
}
echo (microtime(true) - $t0) . '<br>';
?>
Résultats de la lecture de 4200 lignes de texte
0.20169281959534 pour la base de donnée.
0.065440893173218 pour le fichier index.
L'utilisation d'un index pour lire une ligne est environ 50 fois plus rapide que la lecture directe. Le résultat était prévisible puisque le script PHP se contente de lire le corpus à une position déterminée sans avoir à parcourir l'ensemble du texte.
S'agit-il d'un défi de Geeks pour gagner quelques millisecondes ?
En fait, je travaille sur des textes de grande taille et chaque milliseconde compte pour éviter d'attendre des plombes pour avoir le résultat du script PHP.
Bref, si vous avez à extraire une ligne d'un fichier texte de plusieurs centaines de Mo ou de plusieurs giga, pensez à créer un index pour réduire considérablement le temps de lecture d'une ligne.