Question

J'utilise C # pour lire un fichier CSV en texte brut d'environ 120 Mo. Au départ, j’ai procédé à l’analyse en le lisant ligne par ligne, mais j’ai récemment déterminé que la lecture du contenu du fichier dans son intégralité en mémoire était plusieurs fois plus rapide. L'analyse est déjà assez lente car le CSV a des virgules incorporées entre guillemets, ce qui signifie que je dois utiliser un fractionnement regex. C’est le seul que j’ai trouvé qui fonctionne de manière fiable:

string[] fields = Regex.Split(line, 
@",(?!(?<=(?:^|,)\s*\x22(?:[^\x22]|\x22\x22|\\\x22)*,)
(?:[^\x22]|\x22\x22|\\\x22)*\x22\s*(?:,|$))");
// from http://regexlib.com/REDetails.aspx?regexp_id=621

Pour effectuer l'analyse après avoir lu l'intégralité du contenu en mémoire, je scissionne une chaîne sur le caractère de nouvelle ligne pour obtenir un tableau contenant chaque ligne. Cependant, lorsque je le fais sur le fichier de 120 Mo, un System.OutOfMemoryException est généré. Pourquoi manque-t-il si rapidement de mémoire lorsque mon ordinateur dispose de 4 Go de RAM? Existe-t-il un meilleur moyen d’analyser rapidement un fichier CSV complexe?

Était-ce utile?

La solution

Vous pouvez obtenir une exception OutOfMemoryException pour pratiquement n'importe quelle taille d'allocation. Lorsque vous allouez un morceau de mémoire, vous demandez réellement un morceau de mémoire continu de la taille demandée. Si cela ne peut pas être respecté, vous verrez une exception OutOfMemoryException.

Vous devez également savoir que, sauf si vous utilisez Windows 64 bits, vos 4 Go de RAM sont divisés en 2 Go d'espace noyau et 2 Go d'espace utilisateur, de sorte que votre application .NET ne peut pas accéder à plus de 2 Go par défaut.

Lorsque vous effectuez des opérations sur des chaînes dans .NET, vous risquez de créer de nombreuses chaînes temporaires en raison du fait que les chaînes .NET sont immuables. Par conséquent, l'utilisation de la mémoire peut augmenter considérablement.

Autres conseils

Ne lancez pas votre propre analyseur sauf si vous devez le faire. J'ai eu de la chance avec celui-ci:

Un lecteur rapide de fichiers CSV

Si rien d'autre ne peut vous permettre de regarder sous le capot et de voir comment quelqu'un d'autre le fait.

Si vous avez lu l'intégralité du fichier dans une chaîne, utilisez probablement un StringReader .

StringReader reader = new StringReader(fileContents);
string line;
while ((line = reader.ReadLine()) != null) {
    // Process line
}

Cela devrait être pratiquement identique à la diffusion à partir d'un fichier, à la différence que le contenu est déjà dans la mémoire.

Modifier après le test

J'ai essayé ce qui précède avec un fichier de 140 Mo où le traitement consistait à incrémenter une variable de longueur avec line.Length. Cela a pris environ 1,6 seconde sur mon ordinateur. Après cela, j’ai essayé ce qui suit:

System.IO.StreamReader reader = new StreamReader("D:\\test.txt");
long length = 0;
string line;
while ((line = reader.ReadLine()) != null)
    length += line.Length;

Le résultat était d'environ 1 seconde.

Bien sûr, votre kilométrage peut varier, en particulier si vous lisez sur un lecteur réseau ou si votre traitement prend suffisamment de temps pour que le disque dur puisse chercher ailleurs. Mais également si vous utilisez FileStream pour lire le fichier et que vous ne mettez pas en mémoire tampon. StreamReader fournit une mise en mémoire tampon qui améliore considérablement la lecture.

Vous ne pourrez peut-être pas allouer un seul objet avec autant de mémoire contiguë, et vous ne devriez pas vous attendre à pouvoir. Le streaming est la façon habituelle de faire cela, mais vous avez raison de dire que cela pourrait être plus lent (bien que je ne pense pas que cela devrait normalement être aussi lent.)

En guise de compromis, vous pouvez essayer de lire une partie plus importante du fichier (mais pas la totalité) en une fois, avec une fonction comme StreamReader.ReadBlock () , et en traitant chaque partie dans tourner.

Comme d’autres affiches le disent, OutOfMemory est dû au fait qu’il ne trouve pas un bloc de mémoire contigu de la taille demandée.

Cependant, vous dites que l'analyse ligne par ligne était plusieurs fois plus rapide que de tout lire en une fois, puis d'effectuer votre traitement. Cela n’a de sens que si vous suivez l’approche naïve du blocage des lectures, par exemple (en pseudo-code):

while(! file.eof() )
{
    string line = file.ReadLine();
    ProcessLine(line);
}

Vous devriez plutôt utiliser la diffusion en continu, où votre flux est rempli par les appels Write () d'un autre thread qui lit le fichier. Le fichier lu n'est donc pas bloqué par ce que votre ProcessLine () fait, et vice-versa. Cela devrait être comparable à la performance de lire l’ensemble du fichier en une fois puis d’effectuer votre traitement.

Vous devriez probablement essayer Le profileur CLR pour déterminer votre utilisation réelle de la mémoire. Il se peut qu’il y ait des limites de mémoire autres que la RAM de votre système. Par exemple, s’il s’agit d’une application IIS, votre mémoire est limitée par les pools d’applications.

Avec ces informations de profil, vous pouvez être amené à utiliser une technique plus évolutive, telle que la diffusion en continu du fichier CSV que vous avez tenté à l'origine.

Vous manquez de mémoire sur la pile, pas du tas.

Vous pouvez essayer de re-factoriser votre application de manière à traiter les entrées de manière plus gérable "chunks". de données plutôt que de traiter 120 Mo à la fois.

Je suis d’accord avec la plupart des gens ici, vous devez utiliser le streaming.

Je ne sais pas si quelqu'un a déjà dit, mais vous devriez regarder une méthode d'exstention.

Et je sais, bien sûr, haut la main, la meilleure technique de fractionnement CSV sur .NET / CLR est celui-ci

Cette technique m'a généré + 10 Go de sorties XML à partir d'un fichier CSV d'entrée, y compris de nombreux filtres d'entrée et le tout, plus rapidement que tout ce que j'ai vu auparavant.

Vous devriez lire un bloc dans un tampon et travailler dessus. Ensuite, lisez un autre morceau et ainsi de suite.

Il existe de nombreuses bibliothèques qui le feront efficacement pour vous. J'en entretiens un qui s'appelle CsvHelper . Vous devez gérer un grand nombre de cas extrêmes, par exemple lorsqu'une virgule ou une fin de ligne se trouve au milieu d'un champ.

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