IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C# Discussion :

Lecture de fichiers .txt volumineux


Sujet :

C#

  1. #1
    Membre du Club
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mai 2013
    Messages
    80
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Mai 2013
    Messages : 80
    Points : 54
    Points
    54
    Par défaut Lecture de fichiers .txt volumineux
    Bonjour,

    Cette discussion vient en parallèle à un autre post : http://www.developpez.net/forums/d14...hiers-paradox/

    Je dois lire des fichiers .txt qui peuvent dans certains cas être très volumineux. Je me suis dit que peut être il y'aurait moyen d'alléger la méthode de lecture que j'utilise.

    Voici ce que je fais :
    On définit dans mon programme une date de début et une date de fin.
    Je dois lire dans un à 3 fichiers (boucles imbriquées) qui sont formés comme ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    01/01/2014;1;1;0;3.5;2
    01/01/2014;1;2;0;7;2
    02/01/2014;3;1;0;5;4
    //Ainsi de suite ...
    Ce que je fais dans mon code : je parcours chaque ligne du fichier en séparant par le ; chaque élement et en comparant l'élement 0 (la date) si elle est entre les dates de début et fin, si oui je fais récupère d'autres éléments de la ligne en cours pour calculer, vérifier autre chose.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    string monFichier = _file.GetCheminFichierExport("MON_GROS_FICHIER.TXT"); // Je récupère le chemin du fichier passé en paramètre
    string[] sep = {";"}; // Je définis le ; comme séparateur
    foreach (
    var elem in
    File.ReadLines(monFichier, System.Text.Encoding.Default)
                            .Select(elem => elem.Split(sep, StringSplitOptions.None)) // Je split les éléments de chaque ligne par le séparateur que j'ai défini
                                {
                                        //Ici mon traitement
                                }
    Voilà à peu près ce que je fais, en sachant que je peux avoir un ou deux foreach qui peuvent s'imbriquer dans le principal foreach; en sachant que certains fichiers ont des dates qui remontent à 2012 (environ 400Mo le .txt), je comprends que ça peut prendre du temps.

    Ma méthode de traitement est elle bonne ou y'a t il un moyen d'optimiser cette fonction pour faire plus rapide ?

    Merci.

  2. #2
    Membre régulier
    Inscrit en
    Novembre 2006
    Messages
    40
    Détails du profil
    Informations forums :
    Inscription : Novembre 2006
    Messages : 40
    Points : 117
    Points
    117
    Par défaut
    Avec ta methode il risque de tout monter en mémoire pour gerer toutes les lignes.

  3. #3
    Expert éminent Avatar de Graffito
    Profil pro
    Inscrit en
    Janvier 2006
    Messages
    5 993
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2006
    Messages : 5 993
    Points : 7 903
    Points
    7 903
    Par défaut
    Si l'optimisation est bien l'objectif recherché et si les dates sont triées en ordre croissant, on peut utiliser un StreamReader sur le fichier et faire appel à sa fonction BaseStream.Seek().

    Dans un premier temps, on peut "découper" le fichier en blocks :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    for (int SeekPos=0;seekPos<FileSize-1000;SeekPos+=BlockSize)
    {
      // on fait un Seek(SeekPos) pour se positionner dans le fichier par pas de quelques mega,
      // on recherche la premiere occurence d'une date et on note dans une liste cette date et sa position dans le fichier
    }
    Quand on cherche une date supérieure à D1 et inférieure à D2, on se repositionne au début et on fait un Seek() avec la position de la liste correspondant à la dernière date inférieure à D1 (et on arrète le traitement dès qu'on trouve une date > D2).
    " Le croquemitaine ! Aaaaaah ! Où ça ? " ©Homer Simpson

  4. #4
    Membre du Club
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mai 2013
    Messages
    80
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Mai 2013
    Messages : 80
    Points : 54
    Points
    54
    Par défaut
    La taille du fichier peut varier Graffito.
    Et dans tous les cas il faudra quand même arriver à la première occurence qui correspond à la date D1.
    Enfin je me renseigne quand même sur ta proposition je ne connaissais pas

    Drezounet, j'ai essayé mais ça reste pareil, merci quand même !

  5. #5
    Expert éminent Avatar de Graffito
    Profil pro
    Inscrit en
    Janvier 2006
    Messages
    5 993
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2006
    Messages : 5 993
    Points : 7 903
    Points
    7 903
    Par défaut
    il faudra quand même arriver à la première occurence qui correspond à la date D1.
    En découpant vitruellement le grand fichier en intervalles (de 1 Mo par exemple), on a juste besoin d'analyser le tout début de chaque intervalle (quelques dizaines d'octets) pour identifier un block avec sa première date.

    On peut ensuite démarrer sur le dernier block dont la date est inférieure à D1, sans lire les blocks précédents.


    J'avais divisé la methode en 2 étapes pour plus de compréhension, mais on peut opérer ainsi :
    1) Déplacer la position de lecture sur le debut de l'intervalle n (SeekPos = n*Taille_de_l'intervalle),
    2) identifier la position Pn de la première date de l'intervalle n,
    3) si Dn inférieure à D1, boucler sur 1)
    4) si Dn supérieure ou égale à D1, on commence le traitement séquentiel à la position Pn-1.
    " Le croquemitaine ! Aaaaaah ! Où ça ? " ©Homer Simpson

  6. #6
    Membre du Club
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mai 2013
    Messages
    80
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Mai 2013
    Messages : 80
    Points : 54
    Points
    54
    Par défaut
    Oops toutes mes excuses Graffito ! J'avais complètement oublié de poster une réponse.

    Du coup oui, ta solution a l'air d'être intéressante, mais je n'ai pas trouvé d'exemple concret en C#. Tu as un exemple de code ?

  7. #7
    Membre du Club
    Inscrit en
    Février 2013
    Messages
    34
    Détails du profil
    Informations forums :
    Inscription : Février 2013
    Messages : 34
    Points : 43
    Points
    43
    Par défaut
    SAlut.
    Une idée . procede par dichotomie.

    Recupere la taille du fichier. Par dichotomie, cherche adresses a1 et a2 de début et fin de bloc intéressant.
    A1 sera la position de octet de la date juste inférieure a celle de ta période.
    A2 sera la position de l'octet correspondant a la date supérieure.

    Exemple pour chercher une date Ddans ton fichier:
    Long b= taille fichier.
    Long a=0
    While b>a
    Long m=(a+b)/2
    Date dm=calcdate(m)
    If DM<D
    A=m
    Else
    B=m
    End if

    Pour calcdate ça se corse.
    Date calcdate(d)
    Buffer.seek(d)
    Ignore. les octets depuis d jusqu'à trouver une fin de ligne. Puis récupéré les 2+1+2+4 octets de date , convertis les en string avec encoding.ascii . ensuite par se la date.

  8. #8
    Membre du Club
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mai 2013
    Messages
    80
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Mai 2013
    Messages : 80
    Points : 54
    Points
    54
    Par défaut
    J'avais aussi pensé à la dichotomie il y'a peu de temps. Mais c'est la gestion par taille que je n'arrive pas à saisir/mettre en place. Les buffers, "seek", etc ..

  9. #9
    Expert éminent
    Avatar de StringBuilder
    Homme Profil pro
    Chef de projets
    Inscrit en
    Février 2010
    Messages
    4 149
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Chef de projets
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2010
    Messages : 4 149
    Points : 7 392
    Points
    7 392
    Billets dans le blog
    1
    Par défaut
    Les principales difficultés du travail par "blocs", c'est qu'effectivement :
    1/ Tu ne récupères pas un string ni string[] mais un byte[]
    => Potentiellement, si c'est un encodage sur plusieurs octets, le décodage va être compromis.
    2/ Le découpage ensuite en lignes n'est pas "évident"
    => En effet, le premier octet de chaque bloc ne correspondra pas forcément au premier caractère d'une ligne. Il faudra donc analyser à l'intérieur du bloc chaque octet jusqu'à trouver le premier puis le dernier saut de ligne inclu dans le bloc. Ensuite, tu peux convertir en string et faire tes analyses dessus (split et autres)
    3/ Les lignes à cheval entre deux blocs sont à recoller
    => Tu peux même avoir une date à cheval entre deux blocs. Par conséquent, le traitement du contenu des blocs se trouve plus difficile.

    C'est pourquoi il est possible, avec la plupart des outils, de générer des fichiers non pas simplement "CSV", mais "à pas fixe" (record).
    A ce moment, déjà, on économise tous les retours à la ligne (donc gain de taille conséquent sous Windows avec des record de petite taille). En effet, si je sais que chaque "record" fait un nombre défini de caractères, alors j'ai pas besoin de les séparer.
    Ensuite, chaque valeur du record a une taille fixe : pas besoin de délimiteur encore (donc pas de ; ou autres " pour les chaînes de caractères). On gagne encore en taille et énormément en simplicité de relecture.
    De facto, les recherches par bloc sont grandement simplifiées, puisqu'il suffit de seeker sur un multiple de la taille du record, et de lire des blocs d'une taille multiple de celle du record.
    Le revers de la médaille, c'est que chaque élément doit faire la taille du plus grand élément : donc pour une date, elles feront toutes 10 caractères (dd/mm/yyyy) ou 8 (yyyymmdd) selon le format choisi. Mais pour un libellé, si une valeur fait 200 caractères de long, toutes les valeurs des autres record devront faire la même taille... grosse perte de place (on peut pas gagner à tous les coups).
    Ce type de fichier, qui a fait le bonheur des lecteurs de bandes et autres cartes perforées pendant des décennies, est abusivement jugé déprécié à l'heure du XML et autres CSV. Il a pourtant l'avantage d'être "typé" comme le XML, tout en étant bien plus léger et facile à manipuler. Donc parfaitement adapté pour les gros volumes. Par dichotomie, on peut retrouver des lignes dans un fichier de plusieurs Go en un rien de temps.
    On ne jouit bien que de ce qu’on partage.

  10. #10
    Membre du Club
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mai 2013
    Messages
    80
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Mai 2013
    Messages : 80
    Points : 54
    Points
    54
    Par défaut
    J'ai l'impression de passer pour un joyeux ignorant mais je pense ne pas avoir saisi le sens de ton message StringBuilder.
    Dans la première partie tu expliques bien mon souci mais je ne saisis pas bien la seconde partie.

    A noter par contre que je ne peux pas modifier le .txt. Un logiciel tiers me le donne et je dois me dém*rder pour le lire/traiter le plus vite.

  11. #11
    Expert éminent
    Avatar de StringBuilder
    Homme Profil pro
    Chef de projets
    Inscrit en
    Février 2010
    Messages
    4 149
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Chef de projets
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2010
    Messages : 4 149
    Points : 7 392
    Points
    7 392
    Billets dans le blog
    1
    Par défaut
    Si tu peux pas changer le format du fichier, alors ignore la seconde partie.

    Pour faire simple, actuellement, quand t'as un fichier qui doit contenir par exemple deux lignes de produit :

    Stylo Bic / 0.25 € / Jaune
    Voiture / 25000 € / Rouge

    Tu vas avoir comme choix de fichier :

    XML : (usine à gaz, mais simple à lire pour un humain)
    Code xml : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    <produits>
       <produit nom="Stylo Bic" prix="0.25" couleur="Jaune"/>
       <produit nom="Voiture" prix="25000" couleur="Rouge"/>
    </produit>

    CSV : (usine à gaz, illisible pour un humain, ou presque)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    "Stylo Boc";0.25;"Jaune"
    "Voiture";25000;"Rouge"
    Mais on oublie presque systématiquement le fichier à pas fixe (ou record) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    Stylo Bic 0.25JauneVoiture  25000Rouge
    Pourtant, ce format est guère moins lisible que le CSV (et même plus lisible si on stocke un saut de ligne entre chaque record, ce qui n'est pas obligatoire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    Stylo Bic 0.25Jaune
    Voiture  25000Rouge
    Et il a l'avantage d'être infiniment plus rapide à traiter : chaque "record" fait la même taille.

    Donc ici, un record faisant 19 caractères, il suffit de seeker en position 19 du fichier pour trouver le second. En position (n - 1) * 19 pour trouver le record en position n

    Et ça change tout au niveau des performances pour les fichiers volumineux : on peut faire de la dichotomie en lisant un minimum de données.
    On ne jouit bien que de ce qu’on partage.

  12. #12
    Membre du Club
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mai 2013
    Messages
    80
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Mai 2013
    Messages : 80
    Points : 54
    Points
    54
    Par défaut
    Ah je vois mieux où tu voulais en venir effectivement je ne connaissais pas ce format.

    Malheureusement le format que j'ai est comme dit dans mon premier message et je ne peux pas modifier cela (le fichier .txt étant généré par une application tierce).

  13. #13
    Expert éminent
    Avatar de StringBuilder
    Homme Profil pro
    Chef de projets
    Inscrit en
    Février 2010
    Messages
    4 149
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Chef de projets
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2010
    Messages : 4 149
    Points : 7 392
    Points
    7 392
    Billets dans le blog
    1
    Par défaut
    Bonsoir,

    Comme petit défi, je me suis lancé dans le benchmarking de différentes solutions.

    Bon, quelques mises en garde :
    - Mes mesures sont loin d'être précises : il existe certainement des moyens de mesures les performances bien plus précis et fiables que eux que j'ai employé.
    - J'ai une machine particulièrement capricieuse : y'a un truc, je sais pas où qui fait que par moment elle est "normalement rapide" et d'autres où j'ai l'impression d'avoir un Goupil.
    - Mon programme est codé pour être compilé en 64 bits (non pas que j'utilise du code spécifique, c'est juste que les constantes utilisés font qu'il atteint 2,5 Go de mémoire occupée.
    - Mes algos sont écrits "tels quels". Ils sont très certainement optimisables. D'autant que le "DichotomyBlockReader" présente visiblement un bug, puisque par moment il ne trouve pas le même nombre de lignes que les autres

    SimpleTextReader
    => Algo le plus simple : on fait un ReadAllText, Split dessus, puis lecture séquentielle... Forcément, ça bouffe 2,5 Go de mémoire et c'est très lent

    DichotomyTextReader
    => Algo plus complexe : il est censé êtrep lus rapide (et à plusieurs reprises il l'était), et bouffe étrangement 200 Mo de moins. Aucune idée de pourquoi. Il s'agit de faire ReadAll + Split suivit d'une recherche dichotomique (merdique, j'étais pas réveillé)

    SimpleBlockReader
    => Lecture de blocks de 360 bytes, et traitement séquentiel. Assez rapide et bouffe presque plus de mémoire (15 Ko). Bon compromis entre complexité du code et performances donc.

    DichotomyBlockReader
    => Recherche dichotomique dans le fichier par blocs de 360 bytes. Franchement rapide, ça commence à devenir intéressant.

    SimpleRecordReader
    => Utilisation d'un fichier de type record et lecture séquentielle. Je suis assez surpris de voir que c'est plus lent que le SimpleBlockReader. Des optimisations doivent être possible, car dans les faits, les deux font la même chose, ça n'explique donc pas que ce soit 30% plus lent.

    DichotomyRecordReader
    =>Utilisation d'un fichier de type record, et recherche dichotomique. Deux fois plus rapide que le DichotomyBlockReader, ce qui était tout à fait prédictible.

    Sortie du programme :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
     
    SimpleTextReader
    Trouvé : 889
    Used time : 00:09.73
     
    DichotomyTextReader
    Trouvé : 889
    Used time : 00:10.74
     
    SimpleBlockReader
    Trouvé : 889
    Used time : 00:00.90
     
    DichotomyBlockReader
    Trouvé : 888
    Used time : 00:00.09
     
    SimpleRecordReader
    Trouvé : 889
    Used time : 00:01.16
     
    DichotomyRecordReader
    Trouvé : 889
    Used time : 00:00.04
     
    Finished
    Et le code :
    Code csharp : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364
    365
    366
    367
    368
    369
    370
    371
    372
    373
    374
    375
    376
    377
    378
    379
    380
    381
    382
    383
    384
    385
    386
    387
    388
    389
    390
    391
    392
    393
    394
    395
    396
    397
    398
    399
    400
    401
    402
    403
    404
    405
    406
    407
    408
    409
    410
    411
    412
    413
    414
    415
    416
    417
    418
    419
    420
    421
    422
    423
    424
    425
    426
    427
    428
    429
    430
    431
    432
    433
    434
    435
    436
    437
    438
    439
    440
    441
    442
    443
    444
    445
    446
    447
    448
    449
    450
    451
    452
    453
    454
    455
    456
    457
    458
    459
    460
    461
    462
    463
    464
    465
    466
    467
    468
    469
    470
    471
    472
    473
    474
    475
    476
    477
    478
    479
    480
    481
    482
    483
    484
    485
    486
    487
    488
    489
    490
    491
    492
    493
    494
    495
    496
    497
    498
    499
    500
    501
    502
    503
    504
    505
    506
    507
    508
    509
    510
    511
    512
    513
    514
    515
    516
    517
    518
    519
    520
    521
    522
    523
    524
    525
    526
    527
    528
    529
    530
    531
    532
    533
    534
    535
     
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.IO;
     
    namespace TestBigFile
    {
        class Program
        {
            static void Main(string[] args)
            {
                Random rnd = new Random();
                //File.Delete("csv.txt");
                //File.Delete("rec.txt");
                DateTime tst = DateTime.Today.AddDays(rnd.Next(0, 36500));
                DateTime start = DateTime.Now;
     
                //Benchmark.CreateFiles();
                //Console.WriteLine("Used time : {0:mm\\:ss\\.ff}", DateTime.Now.Subtract(start));
                //GC.Collect();
     
                start = DateTime.Now;
                Console.WriteLine("SimpleTextReader");
                Benchmark.SimpleTextReader("csv.txt", tst);
                Console.WriteLine("Used time : {0:mm\\:ss\\.ff}", DateTime.Now.Subtract(start));
                Console.WriteLine();
                GC.Collect();
     
                start = DateTime.Now;
                Console.WriteLine("DichotomyTextReader");
                Benchmark.DichotomyTextReader("csv.txt", tst);
                Console.WriteLine("Used time : {0:mm\\:ss\\.ff}", DateTime.Now.Subtract(start));
                Console.WriteLine();
                GC.Collect();
     
                start = DateTime.Now;
                Console.WriteLine("SimpleBlockReader");
                Benchmark.SimpleBlockReader("csv.txt", tst);
                Console.WriteLine("Used time : {0:mm\\:ss\\.ff}", DateTime.Now.Subtract(start));
                Console.WriteLine();
                GC.Collect();
     
                start = DateTime.Now;
                Console.WriteLine("DichotomyBlockReader");
                Benchmark.DichotomyBlockReader("csv.txt", tst);
                Console.WriteLine("Used time : {0:mm\\:ss\\.ff}", DateTime.Now.Subtract(start));
                Console.WriteLine();
                GC.Collect();
     
                start = DateTime.Now;
                Console.WriteLine("SimpleRecordReader");
                Benchmark.SimpleRecordReader("rec.txt", tst);
                Console.WriteLine("Used time : {0:mm\\:ss\\.ff}", DateTime.Now.Subtract(start));
                Console.WriteLine();
                GC.Collect();
     
                start = DateTime.Now;
                Console.WriteLine("DichotomyRecordReader");
                Benchmark.DichotomyRecordReader("rec.txt", tst);
                Console.WriteLine("Used time : {0:mm\\:ss\\.ff}", DateTime.Now.Subtract(start));
                Console.WriteLine();
                GC.Collect();
     
                Console.WriteLine("Finished");
                Console.ReadKey(true);
            }
        }
     
        static class Benchmark
    	{
            const int BUFFER_SIZE = 360;
            const int RECORD_SIZE = 18;
            const int RECORD_PER_BUFFER = 20;
     
    		public static void SimpleTextReader(string filename, DateTime dte)
    		{
                int nbFound = 0;
                DateTime currentDate;
    			string[] lines = File.ReadAllLines(filename);
    			for (int i = 0, cpt = lines.Length; i < cpt; i++)
    			{
                    currentDate = DateTime.Parse(lines[i].Split(';')[0]);
    				if (dte == currentDate)
    				{
    					//Console.WriteLine(lines[i]);
                        nbFound++;
    				}
    				else if (dte < currentDate)
    				{
    					break;
    				}
    			}
     
                Console.WriteLine("Trouvé : {0}", nbFound);
    		}
     
            public static void DichotomyTextReader(string filename, DateTime dte)
    		{
                int nbFound = 0;
                string[] lines = File.ReadAllLines(filename);
                int min = 0, max = lines.Length;
    			int first = -1;
                DateTime currentDate;
    			while (true)
    			{
    				int idx = min + ((max - min) / 2);
                    currentDate = DateTime.Parse(lines[idx].Split(';')[0]);
    				if (currentDate > dte)
    				{
    					max = idx;
    				}
    				else if (currentDate < dte)
    				{
    					min = idx;
    				}
    				else
    				{
    					while (idx-- > min)
    					{
                            currentDate = DateTime.Parse(lines[idx].Split(';')[0]);
    						if (currentDate < dte)
    						{
                                first = idx + 1;
    							break;
    						}
    					}
                        first = idx + 1;
    					break;
    				}
    			}
    			for (int i = first, cpt = max; i < cpt; i++)
    			{
                    currentDate = DateTime.Parse(lines[i].Split(';')[0]);
    				if (dte == currentDate)
    				{
                        //Console.WriteLine(lines[i]);
                        nbFound++;
    				}
    				else
    				{
    					break;
    				}
    			}
     
                Console.WriteLine("Trouvé : {0}", nbFound);
    		}
     
            public static void SimpleBlockReader(string filename, DateTime dte)
    		{
                int nbFound = 0;
                int count = 0;
                string data = string.Empty;
                bool exitWhile = false;
                char[] buffer = new char[BUFFER_SIZE];
                StreamReader sr = File.OpenText(filename);
                count = sr.Read(buffer, 0, BUFFER_SIZE);
                DateTime currentDate;
                while (count > 0)
                {
                    int p = data.LastIndexOf('\n') + 1;
                    if (p > 0)
                    {
                        data = string.Concat(data.Substring(p), (new string(buffer)).Substring(0, count).Replace("\r", string.Empty));
                    }
                    else
                    {
                        data = (new string(buffer)).Substring(0, count).Replace("\r", string.Empty);
                    }
                    p = data.LastIndexOf('\n');
                    string[] lines = data.Substring(0, p).Split('\n');
                    for (int i = 0, cpt = lines.Length; i < cpt; i++)
                    {
                        currentDate = DateTime.Parse(lines[i].Split(';')[0]);
                        if (currentDate == dte)
                        {
                            //Console.WriteLine(lines[i]);
                            nbFound++;
                        }
                        else if (currentDate > dte)
                        {
                            exitWhile = true;
                            break;
                        }
                    }
                    if (exitWhile)
                    {
                        break;
                    }
                    count = sr.Read(buffer, 0, BUFFER_SIZE);
                }
                sr.Close();
                Console.WriteLine("Trouvé : {0}", nbFound);
    		}
     
            public static void DichotomyBlockReader(string filename, DateTime dte)
    		{
                int nbFound = 0;
                int count = 0;
                bool found = false;
                bool rewind = false;
                string data = string.Empty;
                bool exitWhile = false;
                byte[] buffer = new byte[BUFFER_SIZE];
                FileStream fs = File.OpenRead(filename);
                int nbBlocks = (int)(fs.Length / (long)BUFFER_SIZE);
                int min = 0, max = nbBlocks;
                int idx = -1;
                DateTime currentDate = DateTime.Now;
     
                while (true)
                {
                    if (found)
                    {
                        idx++;
                    }
                    else
                    {
                        idx = min + ((max - min) / 2);
                    }
                    fs.Seek(idx * BUFFER_SIZE, SeekOrigin.Begin);
                    count = fs.Read(buffer, 0, BUFFER_SIZE);
                    if (count == 0)
                    {
                        break;
                    }
                    if (found && !rewind && data.Length > 0)
                    {
                        data = string.Concat(data.Substring(data.LastIndexOf('\n') + 1), ASCIIEncoding.ASCII.GetString(buffer, 0, count).Replace("\r", ""));
                        currentDate = DateTime.Parse(data.Substring(0, 10));
                    }
                    else
                    {
                        data = ASCIIEncoding.ASCII.GetString(buffer, 0, count).Replace("\r", "");
                        currentDate = DateTime.Parse(data.Substring(data.IndexOf('\n') + 1, 10));
                    }
                    if (currentDate > dte)
                    {
                        max = idx;
                    }
                    else if (currentDate < dte)
                    {
                        rewind = false;
                        string[] lines = data.Substring(0, data.LastIndexOf('\n')).Split('\n');
                        for (int i = 1, cpt = lines.Length; i < cpt - 1; i++)
                        {
                            currentDate = DateTime.Parse(lines[i].Split(';')[0]);
                            if (currentDate == dte)
                            {
                                //Console.WriteLine(lines[i]);
                                nbFound++;
                                if (!found && !rewind)
                                {
                                    found = true;
                                    rewind = true;
                                }
                            }
                            else if (currentDate > dte)
                            {
                                exitWhile = true;
                                break;
                            }
                        }
                        min = idx + 1;
                    }
                    else if (currentDate == dte && (!found || rewind) && idx > 0)
                    {
                        idx -= 2;
                        found = true;
                        rewind = true;
                    }
                    else if (currentDate == dte && found)
                    {
                        string[] lines = data.Split('\n');
                        for (int i = 0, cpt = lines.Length; i < cpt - 1; i++)
                        {
                            currentDate = DateTime.Parse(lines[i].Split(';')[0]);
                            if (currentDate == dte)
                            {
                                //Console.WriteLine(lines[i]);
                                nbFound++;
                            }
                            else if (currentDate > dte)
                            {
                                exitWhile = true;
                                break;
                            }
                        }
                    }
                    else if (currentDate == dte && (!found || rewind) && idx == 0)
                    {
                        found = true;
                        data = string.Empty;
                        idx--;
                    }
                    if (exitWhile)
                    {
                        break;
                    }
                }
                fs.Close();
                Console.WriteLine("Trouvé : {0}", nbFound);
            }
     
            public static void SimpleRecordReader(string filename, DateTime dte)
    		{
                int nbFound = 0;
                FileStream fs = File.OpenRead(filename);
                byte[] buffer = new byte[RECORD_SIZE * RECORD_PER_BUFFER];
                int count = 0;
                bool dowork = true;
                string data = string.Empty;
                DateTime currentDate;
                while (dowork)
                {
                    count = fs.Read(buffer, 0, RECORD_SIZE * RECORD_PER_BUFFER);
                    if (count == 0)
                    {
                        break;
                    }
                    for (int i = 0, cpt = count / RECORD_SIZE; i < cpt; i++)
                    {
                        data = ASCIIEncoding.ASCII.GetString(buffer).Substring(0, count);
                        currentDate = DateTime.Parse(data.Substring(i * RECORD_SIZE, 10));
                        if (dte == currentDate)
                        {
                            //Console.WriteLine(data.Substring(i * RECORD_SIZE, RECORD_SIZE));
                            nbFound++;
                        }
                        else if (dte < currentDate)
                        {
                            dowork = false;
                            break;
                        }
                    }
                }
                fs.Close();
                Console.WriteLine("Trouvé : {0}", nbFound);
    		}
     
            public static void DichotomyRecordReader(string filename, DateTime dte)
    		{
                int nbFound = 0;
                FileStream fs = File.OpenRead(filename);
                byte[] buffer = new byte[RECORD_SIZE * RECORD_PER_BUFFER];
                int count = 0;
                bool dowork = true;
                int min = 0;
                int max = (int)(fs.Length / (long)RECORD_SIZE);
                bool rewind = false;
                bool found = false;
                int idx = 0;
                string data = string.Empty;
                DateTime currentDate;
                while (dowork)
                {
                    if (rewind)
                    {
                        idx -= RECORD_PER_BUFFER;
                        if (idx < min)
                        {
                            idx = min;
                        }
                    }
                    else if (found)
                    {
                        idx += RECORD_PER_BUFFER;
                        if (idx > max)
                        {
                            idx = max;
                        }
                    }
                    else
                    {
                        idx = min + ((max - min) / 2);
                    }
                    fs.Seek(idx * RECORD_SIZE, SeekOrigin.Begin);
                    count = fs.Read(buffer, 0, RECORD_SIZE * RECORD_PER_BUFFER);
                    if (count == 0)
                    {
                        break;
                    }
                    for (int i = 0, cpt = count / RECORD_SIZE; i < cpt; i++)
                    {
                        data = ASCIIEncoding.ASCII.GetString(buffer).Substring(0, count);
                        currentDate = DateTime.Parse(data.Substring(i * RECORD_SIZE, 10));
                        if (dte == currentDate)
                        {
                            if (i == 0 && idx > 0 && !found)
                            {
                                rewind = true;
                                break;
                            }
                            else
                            {
                                //Console.WriteLine(data.Substring(i * RECORD_SIZE, RECORD_SIZE));
                                nbFound++;
                                found = true;
                            }
                        }
                        else if (currentDate < dte)
                        {
                            rewind = false;
                            if (i == cpt - 1 && !found)
                            {
                                min = idx;
                            }
                        }
                        else if (currentDate > dte)
                        {
                            if (found == true)
                            {
                                dowork = false;
                                break;
                            }
                            else
                            {
                                max = idx;
                                break;
                            }
                        }
                    }
                }
                fs.Close();
                Console.WriteLine("Trouvé : {0}", nbFound);
    		}
     
            public static void CreateFiles()
            {
                    List<Record> list = new List<Record>();
                    Random rnd = new Random();
                    Console.WriteLine("Generate data in memory...");
                    for (int i = 0; i < 30000000; i++)
                    {
                        list.Add(new Record(DateTime.Today.AddDays(rnd.Next(0, 36500)), i));
                    }
                    Console.WriteLine("Sort data in memory...");
                    list.Sort();
                    Console.WriteLine("Write data to disk...");
                    using (StreamWriter csv = File.CreateText("csv.txt"), rec = File.CreateText("rec.txt"))
                    {
                        for (int i = 0; i < 30000000; i++)
                        {
                            csv.WriteLine(list[i].ToCsv());
                            rec.Write(list[i].ToRec());
                        }
                        csv.Close();
                        rec.Close();
                    }
                    Console.WriteLine("Done");
            }
    	}
     
        public class Record : IEquatable<Record>, IComparable<Record>
        {
            public DateTime date;
            public int value;
     
            public Record(DateTime date, int value)
            {
                this.date = date;
                this.value = value;
            }
     
            public string ToCsv()
            {
                return string.Format("{0:dd/MM/yyyy};{1}", date, value);
            }
     
            public string ToRec()
            {
                return string.Format("{0:dd/MM/yyyy}{1,8}", date, value);
            }
     
            public override bool Equals(object obj)
            {
                if (obj == null)
                {
                    return false;
                }
                Record objAsRecord = obj as Record;
                if (objAsRecord == null)
                {
                    return false;
                }
                else
                {
                    return Equals(objAsRecord);
                }
            }
     
            public bool Equals(Record other)
            {
                if (other == null)
                {
                    return false;
                }
                else
                {
                    if (this.date.Equals(other.date))
                    {
                        return this.value.Equals(other.value);
                    }
                    else
                    {
                        return false;
                    }
                }
            }
     
            public override int GetHashCode()
            {
                return value;
            }
     
            public int CompareTo(Record compareRecord)
            {
                if (compareRecord == null)
                {
                    return 1;
                }
                else
                {
                    if (compareRecord.date == this.date)
                    {
                        return this.value.CompareTo(compareRecord.value);
                    }
                    else
                    {
                        return this.date.CompareTo(compareRecord.date);
                    }
                }
            }
        }
    }

    Quelques remarques :
    - J'ai volontairement forcé un cast de string vers DateTime à la lecture, plutôt que de stocker les dates dans un format ordonnable. Ceci pour que les algos prennent comptent le temps perdu par d'éventuels traitements des records interprétés.
    - J'ai tenté d'optimiser un peu. Je suis très friand d'astuces pour améliorer les choses.
    - Les GC.Collect() sont certes inutiles, mais sont là pour avoir une idée précise de la consommation réelle du programme à chaque étape (sinon à chaque étape le programme aurait bouffé 2.5 Go...)
    - Les fichiers générés font respectivement 604 Mo et 527 Mo : Le fichier de type record est donc fidèle aux attentes, puisqu'il est clairement plus petit, ce qui est un plus supplémentaire pour les performances.
    - Mon PC dispose de 8 Go de RAM, et n'a pas été à court de RAM durant mes tests.

    Tout commentaire ou remarque sont les bienvenus.

    En tout cas, si on ne peut pas faire de fichier de type record, l'utilisation d'un algo à base de lecture de blocs reste tout à fait valable, autant d'un point de vue charge mémoire que temps d'exécution.
    On divise quand même par 100 le temps d'exécution...

    Et arriver à moins de 1/10 de seconde pour trouver 900 lignes dans un fichier non indexé (mais ordonné) de 600 Mo en fonction d'un critère de recherche, je trouve ça pas mal
    On ne jouit bien que de ce qu’on partage.

  14. #14
    Membre du Club
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mai 2013
    Messages
    80
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Mai 2013
    Messages : 80
    Points : 54
    Points
    54
    Par défaut
    Oh c'est génial ça !
    Bon on est samedi, et je ne pourrais tester tout ça que lundi, mais je te tiens au courant de mes tests dès que possible ! Merci beaucoup !!

  15. #15
    Expert éminent
    Avatar de StringBuilder
    Homme Profil pro
    Chef de projets
    Inscrit en
    Février 2010
    Messages
    4 149
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Chef de projets
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2010
    Messages : 4 149
    Points : 7 392
    Points
    7 392
    Billets dans le blog
    1
    Par défaut
    Le truc con, c'est que celui qui devrait t'intéresser le plus (DichotomyBlockReader) est... le seul qui est buggé
    Donc tu pourras pas reprendre le code tel quel, car y'a des cas où le résultat n'est pas tout à fait le même que pour les autres. Faut trouver le bug et le corriger
    On ne jouit bien que de ce qu’on partage.

  16. #16
    Expert éminent
    Avatar de StringBuilder
    Homme Profil pro
    Chef de projets
    Inscrit en
    Février 2010
    Messages
    4 149
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Chef de projets
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2010
    Messages : 4 149
    Points : 7 392
    Points
    7 392
    Billets dans le blog
    1
    Par défaut
    Ah ben je me disais aussi que les fonctions "record" étaient décevantes d'un point de vues performances...

    Forcément, j'avais laissé une grosse boulette niveau optimisation... (construction de data à chaque itération plutôt qu'avant le for)

    Corrigé. Maintenant la fonction avec dichotomie ne fait plus que 3 centièmes de secondes.

    Code csharp : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
     
    public static void SimpleRecordReader(string filename, DateTime dte)
    		{
                int nbFound = 0;
                FileStream fs = File.OpenRead(filename);
                byte[] buffer = new byte[RECORD_SIZE * RECORD_PER_BUFFER];
                int count = 0;
                bool dowork = true;
                string data = string.Empty;
                DateTime currentDate;
                while (dowork)
                {
                    count = fs.Read(buffer, 0, RECORD_SIZE * RECORD_PER_BUFFER);
                    if (count == 0)
                    {
                        break;
                    }
                    data = ASCIIEncoding.ASCII.GetString(buffer).Substring(0, count);
                    for (int i = 0, cpt = count / RECORD_SIZE; i < cpt; i++)
                    {
                        currentDate = DateTime.Parse(data.Substring(i * RECORD_SIZE, 10));
                        if (dte == currentDate)
                        {
                            //Console.WriteLine(data.Substring(i * RECORD_SIZE, RECORD_SIZE));
                            nbFound++;
                        }
                        else if (dte < currentDate)
                        {
                            dowork = false;
                            break;
                        }
                    }
                }
                fs.Close();
                Console.WriteLine("Found : {0}", nbFound);
    		}
     
            public static void DichotomyRecordReader(string filename, DateTime dte)
    		{
                int nbFound = 0;
                FileStream fs = File.OpenRead(filename);
                byte[] buffer = new byte[RECORD_SIZE * RECORD_PER_BUFFER];
                int count = 0;
                bool dowork = true;
                int min = 0;
                int max = (int)(fs.Length / (long)RECORD_SIZE);
                bool rewind = false;
                bool found = false;
                int idx = 0;
                string data = string.Empty;
                DateTime currentDate;
                while (dowork)
                {
                    if (rewind)
                    {
                        idx -= RECORD_PER_BUFFER;
                        if (idx < min)
                        {
                            idx = min;
                        }
                    }
                    else if (found)
                    {
                        idx += RECORD_PER_BUFFER;
                        if (idx > max)
                        {
                            idx = max;
                        }
                    }
                    else
                    {
                        idx = min + ((max - min) / 2);
                    }
                    fs.Seek(idx * RECORD_SIZE, SeekOrigin.Begin);
                    count = fs.Read(buffer, 0, RECORD_SIZE * RECORD_PER_BUFFER);
                    if (count == 0)
                    {
                        break;
                    }
                    data = ASCIIEncoding.ASCII.GetString(buffer).Substring(0, count);
                    for (int i = 0, cpt = count / RECORD_SIZE; i < cpt; i++)
                    {
                        currentDate = DateTime.Parse(data.Substring(i * RECORD_SIZE, 10));
                        if (dte == currentDate)
                        {
                            if (i == 0 && idx > 0 && !found)
                            {
                                rewind = true;
                                break;
                            }
                            else
                            {
                                //Console.WriteLine(data.Substring(i * RECORD_SIZE, RECORD_SIZE));
                                nbFound++;
                                found = true;
                            }
                        }
                        else if (currentDate < dte)
                        {
                            rewind = false;
                            if (i == cpt - 1 && !found)
                            {
                                min = idx;
                            }
                        }
                        else if (currentDate > dte)
                        {
                            if (found == true)
                            {
                                dowork = false;
                                break;
                            }
                            else
                            {
                                max = idx;
                                break;
                            }
                        }
                    }
                }
                fs.Close();
                Console.WriteLine("Found : {0}", nbFound);
    		}
    On ne jouit bien que de ce qu’on partage.

  17. #17
    Membre du Club
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mai 2013
    Messages
    80
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Mai 2013
    Messages : 80
    Points : 54
    Points
    54
    Par défaut
    Bon, j'ai essayé le DichotomyBlockReader, mais étrangement je n'ai réussi qu'à l'éxécuter une fois, ou il a été plutôt rapide, j'ai voulu relancer mais ça a pris des plombes et des plombes. J'ai essayé de débuggué le tout mais force est de constater que je ne comprends pas tout ton code. ( En même temps j'ai jamais fait de dichotomie en C# ... )

    A quoi correspondent par exemple "BUFFER_SIZE", idx, .. ? Est-il possible d'avoir une explication sur l’algorithme que tu as écris ?

    Merci

  18. #18
    Expert éminent
    Avatar de StringBuilder
    Homme Profil pro
    Chef de projets
    Inscrit en
    Février 2010
    Messages
    4 149
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Chef de projets
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2010
    Messages : 4 149
    Points : 7 392
    Points
    7 392
    Billets dans le blog
    1
    Par défaut
    Hmmm, ça va être dur, faut déjà que je comprenne ce que j'ai écris

    En tout cas, c'est très certainement optimisable/débordelisable. J'ai pondu ça un peu comme c'est sorti de mon cerveau embrumé, à mon avis y'a de quoi faire mieux :

    Code csharp : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
     
            public static int DichotomyBlockReader(string filename, DateTime dte)
    		{
                int nbFound = 0; // Nom d'occurences trouvées
                int count = 0;    // Nombe de bytes lus par le StreamReader
                bool found = false;  // A-t-on trouvé la date ?
                bool rewind = false;  // Est-on en train de chercher la première occurence ?
                string data = string.Empty; // chaîne extraite du buffer
                bool exitWhile = false;  // doit-on sortir de la boucle
                byte[] buffer = new byte[BUFFER_SIZE];
                FileStream fs = File.OpenRead(filename);
                int nbBlocks = (int)(fs.Length / (long)BUFFER_SIZE);  // BUFFER_SIZE : combien de bytes doit-on lire à chaque accès au fichier ? On prendra, pour de meilleures performances, un buffer de la même taille que la taille d'un cluster sur le disque (habituellement entre 4 et 32 Ko selon la taille du disque) nbBlocks est le nombre de blocs qui constituent le fichier.
                int min = 0, max = nbBlocks; // Bornes pour la dichotomie
                int idx = -1; // Index du bloc en cours de traitement
                DateTime currentDate = DateTime.Now; // Date lue dans la ligne analysée
     
                while (true)
                {
                    if (found) // Si on a trouvé la première occurence de la date, on se contente d'avancer
                    {
                        idx++;
                    }
                    else // Sinon, on continue la dichotomie
                    {
                        idx = min + ((max - min) / 2);
                    }
                    fs.Seek(idx * BUFFER_SIZE, SeekOrigin.Begin); // On lit le bloc à la position idx dans le fichier
                    count = fs.Read(buffer, 0, BUFFER_SIZE);
                    if (count == 0) // Là, y'a un problème, on n'a rien lu. Ça veut dire qu'on est arrivé en fin de fichier.
                    {
                        break;
                    }
                    if (found && !rewind && data.Length > 0) // Alors, j'ai trouvé la première occurence, et je suis pas en mode recherche de la première occurence, et il me reste un morceau de bloc d'une lecture précédente (en gros, je suis en train de compter les occurences de ma recherche, et j'ai une ligne à cheval entre deux blocs, et je dois donc la coller à mon nouveau bloc lu
                    {
                        data = string.Concat(data.Substring(data.LastIndexOf('\n') + 1), ASCIIEncoding.ASCII.GetString(buffer, 0, count).Replace("\r", "")); // Je garde de mon ancien bloc que le dernier bout de ligne, et je le concatène à mon nouveau bloc lu auquel je retire les caractères '\r' (car sous Windows, un retour à la ligne c'est \r\n, ce qui est débile, \n suffit pour faire nos traitements, et c'est plus simple avec un seul caractère, ça évite d'avoir un \r sur le bloc n et \n sur le bloc n + 1
                        currentDate = DateTime.Parse(data.Substring(0, 10)); // Je lis la date qui se trouve en début de mon bloc
                    }
                    else // J'ai pas de données d'un ancien bloc, ou je suis en train de chercher la première occurence (à reculons), ou j'ai toujours pas trouvé du tout ma valeur
                    {
                        data = ASCIIEncoding.ASCII.GetString(buffer, 0, count).Replace("\r", ""); // je lis donc simplement mon bloc en virant les \r inutiles
                        currentDate = DateTime.Parse(data.Substring(data.IndexOf('\n') + 1, 10)); // Et je recherche la date de la première ligne entière (et je viens de trouver un premier bug : si idx = 0 OU dernier caractère du bloc précédent = \n, alors je dois lire ma date à l'index 0 et non après le premier \n)
                    }
                    if (currentDate > dte) // Ma date lue est plus grande que ma date recherchée :je suis allé trop loin dans le fichier
                    {
                        max = idx; // Donc l'index actuel devient la nouvelle borne
                    }
                    else if (currentDate < dte) // Ma date lue est plus petite que ma date recherchée
                    {
                        rewind = false;  // Si j'étais en train de rechercher à reclulon la première occurence, j'arrête, ça sert à rien d'aller plus loin
                        string[] lines = data.Substring(0, data.LastIndexOf('\n')).Split('\n'); // J'éclate mes lignes dans une tableau de string, c'est plus pratique à traiter
                        for (int i = 1, cpt = lines.Length; i < cpt - 1; i++) // Pour chaque ligne de mon tableau (sauf la première déjà traîtée et la dernière qu'il faudrait traiter si idx = nbBlocks second bug)
                        {
                            currentDate = DateTime.Parse(lines[i].Split(';')[0]); // Je parse la première valeur
                            if (currentDate == dte) // J'ai trouvé la date
                            {
                                nbFound++; // J'en ai trouvée une !
                                if (!found && !rewind) // Si j'avais encore rien trouvé et que j'étais pas à la recherche de la première ligne (j'aurais pas du incrémenter nbFound, troisième bug)
                                {
                                    found = true; // youhou, j'en a trouvé une !
                                    rewind = true; // je dois rechercher la première occurrence maintenant (et ça sert à rien si i > 1 !!! quatrième bug)
                                }
                            }
                            else if (currentDate > dte) // la date est plus grande : je suis allé trop loin
                            {
                                exitWhile = true; // j'ai fini de chercher
                                break;
                            }
                        }
                        min = idx + 1; // j'avance mon index d'un bloc
                    }
                    else if (currentDate == dte && (!found || rewind) && idx > 0) // la date est bonne, mais j'ai pas encore trouvé la première occurence, ou alors je suis en mode recherche de la première, et je suis pas en début de fichier
                    {
                        idx -= 2; // Je recule de deux (vu que j'avance de 1 en début de boucle... je recule de 1 en fait)
                        found = true; // j'ai trouvé la date
                        rewind = true; // je dois chercher la première occurence
                    }
                    else if (currentDate == dte && found) // la date est binne et j'ai trouvé une date, et je suis pas en mode recule : donc je suis en train de trouver toutes les occurrences après la première
                    {
                        string[] lines = data.Split('\n'); // j'éclate mon bloc en lignes
                        for (int i = 0, cpt = lines.Length; i < cpt - 1; i++) // je traite chaque ligne sauf la dernière (sauf si idx = nbBlocks)
                        {
                            currentDate = DateTime.Parse(lines[i].Split(';')[0]);
                            if (currentDate == dte) // la date est bonne
                            {
                                nbFound++; // j'ai donc trouvé une occurrence
                            }
                            else if (currentDate > dte) // la date est plus grande
                            {
                                exitWhile = true; // donc j'ai terminé
                                break;
                            }
                        }
                    }
                    else if (currentDate == dte && (!found || rewind) && idx == 0) // y'en a encore long ? j'ai la bonne date, pas trouvé, ou alors en mode recule, et en début de fichier
                    {
                        found = true; // j'ai trouvé
                        data = string.Empty; // j'ai pas de donnée à passer à l'ittération suivante
                        idx--; // je recule de 1 (pour mieux avancer en début de boucle) afin de traiter le bloc en mode "j'ai trouvé la première occurence"
                    }
                    if (exitWhile)
                    {
                        break;
                    }
                }
                fs.Close();
                return nbFound;
            }

    Grmpf !

    Franchement, y'a de la simplification qui se perd :o
    On ne jouit bien que de ce qu’on partage.

  19. #19
    Expert éminent
    Avatar de StringBuilder
    Homme Profil pro
    Chef de projets
    Inscrit en
    Février 2010
    Messages
    4 149
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Chef de projets
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2010
    Messages : 4 149
    Points : 7 392
    Points
    7 392
    Billets dans le blog
    1
    Par défaut
    Pas certain que ça t'aide des masses, mais voici la fonction DichotomyRecordReader réécrite et optimisée (y'a toujours de quoi faire mieux, mais c'est franchement mieux que la première - elle dure jamais plus de 1/100 seconde pour trouver 800 lignes dans un fichier de 600 Mo)

    Le méthode "Block" est certainement plus complexe, car on a cette histoire de lignes à cheval entre deux blocs, qu'on n'a pas avec les Records. C'est ce qui a rendu le code aussi bordelique...

    Code csharp : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
     
            public static int OptimizedDichotomyRecordReader(string filename, DateTime dte)
            {
                FileStream fs = File.OpenRead(filename);
                byte[] buffer = new byte[RECORD_SIZE * RECORD_PER_BUFFER];
                long min = 0;
                long max = fs.Length;
                int min_i = 0;
                int max_i = 0;
                int idx_i = 0;
                long idx = -1;
                int count = 0;
                string data = string.Empty;
                DateTime currentDate;
                string record = string.Empty;
                int nbFound = 0;
                bool finished = false;
     
                while (true)
                {
                    idx = (max - min) / 2 + min;
                    idx = idx - idx % RECORD_SIZE;
                    fs.Seek(idx, SeekOrigin.Begin);
                    count = fs.Read(buffer, 0, RECORD_SIZE * RECORD_PER_BUFFER);
                    data = ASCIIEncoding.ASCII.GetString(buffer).Substring(0, count);
                    currentDate = DateTime.Parse(data.Substring(0, 10));
                    if (currentDate >= dte)
                    {
                        max = idx;
                        continue;
                    }
                    else
                    {
                        currentDate = DateTime.Parse(data.Substring(count - RECORD_SIZE, 10));
                        if (currentDate < dte)
                        {
                            min = idx + RECORD_SIZE * RECORD_PER_BUFFER;
                            continue;
                        }
                        else
                        {
                            min_i = 0;
                            max_i = count;
                            while (true)
                            {
                                idx_i = (max_i - min_i) / 2 + min_i;
                                idx_i -= idx_i % RECORD_SIZE;
                                currentDate = DateTime.Parse(data.Substring(idx_i, 10));
                                if (currentDate > dte)
                                {
                                    max_i = idx_i;
                                }
                                else if (currentDate < dte)
                                {
                                    min_i = idx_i + RECORD_SIZE;
                                }
                                else
                                {
                                    while (true)
                                    {
                                        idx_i -= RECORD_SIZE;
                                        currentDate = DateTime.Parse(data.Substring(idx_i, 10));
                                        if (currentDate != dte)
                                        {
                                            idx_i += RECORD_SIZE;
                                            break;
                                        }
                                    }
                                    break;
                                }
                            }
                            break;
                        }
                    }
                }
                while (!finished)
                {
                    while (idx_i < RECORD_PER_BUFFER * RECORD_SIZE)
                    {
                        currentDate = DateTime.Parse(data.Substring(idx_i, 10));
                        if (currentDate == dte)
                        {
                            nbFound++;
                        }
                        else
                        {
                            finished = true;
                        }
                        idx_i += RECORD_SIZE;
                    }
                    if (!finished)
                    {
                        idx_i = 0;
                        idx += RECORD_SIZE * RECORD_PER_BUFFER;
                        fs.Seek(idx, SeekOrigin.Begin);
                        count = fs.Read(buffer, 0, RECORD_SIZE * RECORD_PER_BUFFER);
                        data = ASCIIEncoding.ASCII.GetString(buffer).Substring(0, count);
                    }
                }
                fs.Close();
                return nbFound;
            }

    Parmi les choses à retenir :
    - Quand on a un bloc, on lit la première date : si elle est plus grande ou égale à la date cherchée, alors on est allé trop loin : max = idx;
    - Ensuite, on lit la dernière date : si elle est plus petite, alors on est pas allé assez loin : min = idx + 1;
    - Quand on arrive là : c'est que la date recherchée est à l'intérieur du bloc : on n'a donc qu'à faire une recherche dichotomique à l'intérieur (au lieu de lire séquentiellement les 32Ko du bloc...)
    - Une fois qu'on a trouvé l'index de la première occurrence, on a juste à recommencer la lecture à partir de là, jusqu'à ce qu'on trouve une date supérieure
    On ne jouit bien que de ce qu’on partage.

  20. #20
    Membre du Club
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mai 2013
    Messages
    80
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Mai 2013
    Messages : 80
    Points : 54
    Points
    54
    Par défaut
    Ah ! Donc le BUFFER_SIZE, je le définis à 4 ou à 32 ?

    Bon je vais aller essayer de mettre tout ça en place !

Discussions similaires

  1. [Debutant] Lecture de fichier txt
    Par vbbarent dans le forum Débuter
    Réponses: 11
    Dernier message: 06/05/2008, 11h13
  2. Problème de lecture de fichier .txt
    Par Lenaick dans le forum WinDev
    Réponses: 4
    Dernier message: 16/04/2008, 11h49
  3. [PC] [Visual Object Cobol] Lecture de fichier .txt
    Par vince3132 dans le forum Cobol
    Réponses: 7
    Dernier message: 14/03/2008, 13h43
  4. [Excel - VBA] lecture de fichier txt
    Par simstef dans le forum Macros et VBA Excel
    Réponses: 10
    Dernier message: 15/06/2007, 16h00
  5. PL/SQL lecture/ecriture fichier txt
    Par stos dans le forum PL/SQL
    Réponses: 2
    Dernier message: 19/05/2006, 12h19

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo