Chaines longues et fonctions d'analyse des chaines longues

Partagez votre expérience de PureBasic avec les autres utilisateurs.
fweil
Messages : 505
Inscription : dim. 16/mai/2004 17:50
Localisation : Bayonne (64)
Contact :

Chaines longues et fonctions d'analyse des chaines longues

Message par fweil »

Voilà un début ... surtout un petit 'comment on fait, comment ça marche'

Manipulation de chaines de plus de 64K et une fonction FindLongString étudiée. Le seul manque qu'il reste au FindLongString ici décrit est le dernier paramètre proposé dans FindString standard de PureBasic, qui permet de démarrer la recherche à une position différente du début de chaine.

A noter un truc intéressant, dans le source, une façon d'utiliser la fonction à partir d'une chaine pure, et une autre façon de charger une chaine longue à partir d'un fichier. Ce qui veut dire que de telles fonctions pourraient être généralisées pour traiter indifférement des chaines et des fichiers.

... et on oublie pas de fermer le débogueur pour avoir les tests de performances avec les vrais résultats.

Code : Tout sélectionner

;
; Chaines longues et analyse de chaines.
;
; F.Weil 20040521
;
; Petit tut pour le traitement de chaines longues (>64K). Purebasic autorise de créer des chaines longues, en modifiant la valeur d'allocation mémoire
; par des instructions assembleur.
;
; Toutefois il n'est alors plus possible d'utiliser le jeu de commandes de la familles des chaines pour analyser une chaine longue, car ces commandes
; sont écrites pour traiter des chaines d'une longueur maximale de 64K.
;
; Le présent source montre la manière de modifier l'allocation de longueur de chaine, de remettre cette valeur à la valeur par défaut, puis propose
; d'étudier une fonction équivalente à FindString() mais capable d'analyser des chaines longues.
;
; A des fins pédagogiques, une première fonction FindString est proposée, puis différentes versions d'optimisation sont listées.
;
; L'un des problèmes posés est particulièrement le fait que le traitement de chaines le plus optimisé ne peut se faire bien
; qu'en descendant au niveau assembleur. Ici nous n'aborderons pas ce niveau, mais nous resterons dans une écriture PureBasic.
;
; Toutefois nous allons manipuler les chaines dans des tableaux d'octets pour utiliser ce que nous avons déjà vu précédement, et avoir
; des temps de réponse assez courts.
;
Structure StringBytes
 Bytes.b[$1000000]
EndStructure

;
; Ici on utilise un boucle pour scanner les caractères et vérifier pour chaque position pointée si la sous-chaine de longueur égale à la sous-chaine cherchée
; est égale à celle-ci.
;
; La comparaison se fait, pour commencer par quelque chose de simple, en appelant la sous-chaine avec un PekkS(), ce qui n'est pas optimum mais
; cela permet déjà d'étudier la question.
;
; A la première occurence de deux sous-chaines égales, on peut soritr de la procédure avec la valeur courante du ponteur comme valeur retour.
;
; Si à la fin du scan on a rien trouvé, on retourne 0
;
Procedure FindLongString(*String.StringBytes, LenString.l, *StringToFind.StringBytes, LengthStringToFind.l)
  For i = 0 To LenString - LengthStringToFind + 1
    If *String\Bytes[i] = *StringToFind\Bytes[0]
        If PeekS(*String + i, LengthStringToFind) = PeekS(*StringToFind, LengthStringToFind)
            ProcedureReturn i + 1
        EndIf
    EndIf
  Next
  ProcedureReturn 0
EndProcedure

;
; Cette deuxième version permet l'évitement d'un coûteux PeekS en gérant deux boucles imbriquées.
; Une fois trouvé un premier caractère correspondant entre la chaine et la sous-chaine, on vérifie chaque caractère suivant pour retenir
; l'index si la totalité des caractères correspondent. Si c'est le cas on sort de la procédure en retournant la valeur de l'index.
; Si ce n'est pas le cas on incrémente l'index.
;
Procedure FindLongString1(*String.StringBytes, LenString.l, *StringToFind.StringBytes, LengthStringToFind.l)
  For i = 0 To LenString - LengthStringToFind + 1
    For j = 0 To LengthStringToFind - 1
      If *String\Bytes[i + j] <> *StringToFind\Bytes[j]
          Break
      EndIf
      If j = LengthStringToFind - 1
          ProcedureReturn i + 1
      EndIf
    Next
  Next
  ProcedureReturn 0
EndProcedure

;
; Pour gagner encore un peu de temps, il faut casser le principe des boucles For / Next pour passer à des boucles While / Wend.
; De cette manière on va pouvoir gérer les pointeurs au plus près.
;
Procedure FindLongString2(*String.StringBytes, LenString.l, *StringToFind.StringBytes, LengthStringToFind.l)
  i = 0
  While i < LenString - LengthStringToFind + 1
    If *String\Bytes[i] = *StringToFind\Bytes[0]
        j =0
        While j < LengthStringToFind - 1
          j + 1
          If *String\Bytes[i + j] <> *StringToFind\Bytes[j]
              Break
          EndIf
        Wend
        If j = LengthStringToFind - 1
            ProcedureReturn i + 1
        EndIf
    EndIf
    i + 1
  Wend
  ProcedureReturn 0
EndProcedure

;
; Et oui celle-là pour demain ou plus tard !
;  
; Procedure ReplaceLongString(*String.StringBytes, *StringToFind.StringBytes, *StringToReplace.StringBytes)
;  
; EndProcedure

Procedure ManipulationBufferSize(Bytes)
  PBStringBase.l = 0
  PBMemoryBase.l = 0
  !MOV Eax, dword [PB_StringBase]
  !MOV [Esp+4],Eax
  !MOV Eax, dword [PB_MemoryBase]
  !MOV [Esp+8],Eax
  HeapReAlloc_(PBMemoryBase, #GMEM_ZEROINIT, PBStringBase, Bytes)
  !MOV dword [PB_StringBase],Eax
  Debug "ManipulationBufferSize : max length of string set to : " + Str(Bytes)
  ProcedureReturn Bytes
EndProcedure

;
;
;
  OpenConsole()
  Count.l = 1000000
  ;
  ; Utilisation sur des chaines normales
  ;
  Chaine.s = "Voyez le brick géant que j'examine près du wharf"
  Cherche.s = "brick"
  tz = ElapsedMilliseconds() : For i = 1 To Count : iu = FindString(Chaine, Cherche, 1) : Next : PrintN(Str(ElapsedMilliseconds() - tz) + " " + Str(iu))
  tz = ElapsedMilliseconds() : For i = 1 To Count : iu = FindLongString(@Chaine, Len(Chaine), @Cherche, Len(Cherche)) : Next : PrintN(Str(ElapsedMilliseconds() - tz) + " " + Str(iu))
  tz = ElapsedMilliseconds() : For i = 1 To Count : iu = FindLongString1(@Chaine, Len(Chaine), @Cherche, Len(Cherche)) : Next : PrintN(Str(ElapsedMilliseconds() - tz) + " " + Str(iu))
  tz = ElapsedMilliseconds() : For i = 1 To Count : iu = FindLongString2(@Chaine, Len(Chaine), @Cherche, Len(Cherche)) : Next : PrintN(Str(ElapsedMilliseconds() - tz) + " " + Str(iu))
  ;
  ; Création d'un buffer long et chargement d'un fichier en conséquence
  ;
  LongStringLength.l = $1000000 ; 16 MO
  ManipulationBufferSize(LongStringLength)
  ;
  ; Là chez moi j'ai la bible complète qui fait un peu moins de 5MO.
  ;
  If ReadFile(0, "C:\Recup\Frw\Bible\BibleJNDdoc-Bible.txt")
      LengthOfFile = Lof()
      Buffer = AllocateMemory(LengthOfFile)
      ReadData(Buffer, LengthOfFile)
      CloseFile(0)
  EndIf
  ManipulationBufferSize($10000)
  Cherche.s = "oméga"
  ;
  ; Lancement de la recherche d'un sous-chaine (celle-ci n'aparaît qu'à la fin du fichier)
  ;
  Count.l = 10
  tz = ElapsedMilliseconds() : For i = 1 To Count
    iu = FindLongString2(Buffer, LengthOfFile, @Cherche, Len(Cherche))
  Next : PrintN(Str(ElapsedMilliseconds() - tz) + " " + Str(iu))
  PrintN("Trouvé " + Mid(PeekS(Buffer + iu - 1, Len(Cherche)), 1, Len(Cherche)) + " = " + Cherche + " à la position " + Str(iu))

  Cherche.s = "Bible"
  ;
  ; Celle là au début
  ;
  tz = ElapsedMilliseconds() : For i = 1 To Count
    iu = FindLongString2(Buffer, LengthOfFile, @Cherche, Len(Cherche))
  Next : PrintN(Str(ElapsedMilliseconds() - tz) + " " + Str(iu))
  PrintN("Trouvé " + Mid(PeekS(Buffer + iu - 1, Len(Cherche)), 1, Len(Cherche)) + " = " + Cherche + " à la position " + Str(iu))

  While Inkey() = "" : Wend
  CloseConsole()
  
End
Mon avatar reproduit l'image de 4x1.8m présentée au 'Salon international du meuble de Paris' en janvier 2004, dans l'exposition 'Shades' réunisant 22 créateurs autour de Matt Sindall. L'original est un stratifié en 150 dpi.
nico
Messages : 3702
Inscription : ven. 13/févr./2004 0:57

Message par nico »

Tu obtient quoi en temps avec ton fichier!
Avatar de l’utilisateur
cederavic
Messages : 1338
Inscription : lun. 09/févr./2004 23:38
Localisation : Bordeaux

Message par cederavic »

voilas la source de ma lib si ça interesse :

Code : Tout sélectionner

ProcedureDLL ReplaceLargeString(*s1, k.l, len.l, s2.s, s3.s)
  If k > len + k
    ProcedureReturn -1
  EndIf
  tk = k
  k = 0
  res = -1
  ll = Len(s2)  
  Repeat
    te.s = Mid(PeekS(*s1+tk), k, ll)
    If te = s2
      
      *s1 + tk + k + ll - 1
      If k = 0
        PokeS(*s1 + tk - tk - ll + 1, s3 + PeekS(*s1 + 1))
      Else
        PokeS(*s1 + tk - 1 - tk - ll + 1, s3 + PeekS(*s1)) 
      EndIf
      *s1 - tk - k - ll + 1
      res = 1
    EndIf
    k + 1
  Until k > len
  ProcedureReturn res
EndProcedure

ProcedureDLL FindLargeString(*s1, k.l, len.l, s2.s)
  If k > len + k
    ProcedureReturn -1
  EndIf
  tk = k
  k = 0
  ll = Len(s2)  
  Repeat
    te.s = Mid(PeekS(*s1+tk), k, ll)
    If te = s2
      ProcedureReturn tk + k
    EndIf
    k + 1
  Until k > len
  ProcedureReturn -1
EndProcedure

ProcedureDLL CountLargeString(*s1, k.l, len.l, s2.s)
  If k > len + k
    ProcedureReturn -1
  EndIf
  tk = k
  k = 0
  ll = Len(s2)  
  Repeat
    te.s = Mid(PeekS(*s1+tk), k, ll)
    If te = s2
      nb + 1
    EndIf
    k + 1
  Until k > len
  ProcedureReturn nb - 1
EndProcedure
fweil
Messages : 505
Inscription : dim. 16/mai/2004 17:50
Localisation : Bayonne (64)
Contact :

Message par fweil »

Comme résultat du test de perf je trouve la première occurence d'un mot situé à la position 4,6M en 100 millisecondes.
Mon avatar reproduit l'image de 4x1.8m présentée au 'Salon international du meuble de Paris' en janvier 2004, dans l'exposition 'Shades' réunisant 22 créateurs autour de Matt Sindall. L'original est un stratifié en 150 dpi.
nico
Messages : 3702
Inscription : ven. 13/févr./2004 0:57

Message par nico »

Tu peut être un peu plus précis:

test avec les différentes procédures et processeur utilisé.
fweil
Messages : 505
Inscription : dim. 16/mai/2004 17:50
Localisation : Bayonne (64)
Contact :

Message par fweil »

nico,

Le test donne les résultats suivants sur mon 1,2GHz

Phase 1

Tests sur chaine courte 1.000.000 d'itérations :

FindString : 150ms
FindLongString : 490ms
FindLongString1 : 460ms
FindLongString2 : 360ms

Phase 2

Tests sur chaine longue 10 itérations fichier "bible" mot "oméga"

FindLongString2 : 1030ms

Tests sur chaine longue 1000000 itérations fichier "bible" mot "Bible"

FindLongString2 : 520ms

Au passage la phase 1 du test avec FindLargeString donne 5900ms et la phase 2 plante.
Mon avatar reproduit l'image de 4x1.8m présentée au 'Salon international du meuble de Paris' en janvier 2004, dans l'exposition 'Shades' réunisant 22 créateurs autour de Matt Sindall. L'original est un stratifié en 150 dpi.
Avatar de l’utilisateur
ZapMan
Messages : 460
Inscription : ven. 13/févr./2004 23:14
Localisation : France
Contact :

Message par ZapMan »

Code : Tout sélectionner

;************************************************************
;* Le truc de Fred pour augmenter la taille du buffer texte *
;*   et éviter les plantage quand on manipule des grandes   *
;*    chaines de caractères.                                *
;************************************************************

Procedure SetStringManipulationBufferSize(Bytes)
  PBStringBase.l = 0
  PBMemoryBase.l = 0
  !MOV eax, dword [PB_StringBase]
  !MOV [esp+4],eax
  !MOV eax, dword [PB_MemoryBase]
  !MOV [esp+8],eax
  HeapReAlloc_(PBMemoryBase, #GMEM_ZEROINIT, PBStringBase, Bytes)
  !MOV dword [_PB_StringBase],eax
EndProcedure

; Set the buffer size for all strings to 1 MB.

SetStringManipulationBufferSize(1000000)
Cette bidouille semble fonctionner pour toutes les fonctions concernant les textes, SAUF avec ReplaceString.
Dernière modification par ZapMan le lun. 24/mai/2004 23:51, modifié 1 fois.
Tout obstacle est un point d'appui potentiel.

Bibliothèques PureBasic et autres codes à télécharger :https://www.editions-humanis.com/downlo ... ads_FR.htm
fweil
Messages : 505
Inscription : dim. 16/mai/2004 17:50
Localisation : Bayonne (64)
Contact :

Message par fweil »

Mon avatar reproduit l'image de 4x1.8m présentée au 'Salon international du meuble de Paris' en janvier 2004, dans l'exposition 'Shades' réunisant 22 créateurs autour de Matt Sindall. L'original est un stratifié en 150 dpi.
Répondre