Carlos Mora y la mala leche de la string concatenation

Carlos Mora y la mala leche de la string concatenation

Postby hmpaquito » Tue Apr 24, 2018 4:36 pm

Hola Carlos y compañía,

Recordaba que escribiste algo al respecto.
Finalmente lo he encontrado en https://groups.google.com/forum/?fromgr ... qenCOHAAAJ

Pero veo que tu clase TBuffer publicada es un prototipo

¿ La llegaste a terminar ?

Es que necesito bajar el numero de FWrite() sin fragmentar la memoria.

Saludos.
hmpaquito
 
Posts: 1482
Joined: Thu Oct 30, 2008 2:37 pm

Re: Carlos Mora y la mala leche de la string concatenation

Postby hmpaquito » Tue Apr 24, 2018 5:12 pm

Carlos,

¿ Se podría usar Stuff() para reemplazar a una posible InsertTheString() ?

¿ Fragmentará también la memoria Stuff() ?


Editado:

Pues sí, parece que viendo el código de Stuff() https://github.com/vszakats/harbour-cor ... tl/stuff.c, también daría problemas de fragmentación porque lo que hace Stuff() es crear una nueva cadena a partir del parámetro cadena.

Por tanto, viendo el (mal) funcionamiento de Stuff(), se podría decir que Harbour necesita una funcion que _verdaderamente_ reemplace en una cadena existente, y no que cree una cadena nueva, provocando así problemas de fragmentación de memoria.
hmpaquito
 
Posts: 1482
Joined: Thu Oct 30, 2008 2:37 pm

Re: Carlos Mora y la mala leche de la string concatenation

Postby Carlos Mora » Thu Apr 26, 2018 4:11 pm

Hola Paquito,

si, justamente como stuff crea una nueva cadena es peor el remedio que la enfermedad. De todas maneras, si lo que quieres es bajar la cantidad de escrituras, la clase te sirve, haciendo concatenaciones de las cadenas y dejando de lado el buffer.
Code: Select all  Expand view

    #include "hbclass.ch"
    CLASS TBuffer
        DATA nSize INIT 4096
        DATA cBuffer
        DATA nCurLen
        DATA bProcess
        METHOD New( nLen ) CONSTRUCTOR
        METHOD Out( cText )
        METHOD End() DESTRUCTOR
    ENDCLASS

    METHOD New( nLen, bProcess ) CLASS TClassA
        IF ValType( nLen ) == 'N'
           ::nSize:= nLen
        ENDIF
        ::cBuffer:=""
        ::nCurLen:= 0
        ::bProcess:= bProcess
    RETURN Self

    METHOD Out( cText )
        IF ::nCurLen + Len( cText ) > ::nSize
           ::bProcess:Eval( ::cBuffer, ::nCurLen )
           ::nCurLen:= 0
           ::cBuffer:= ""
        ENDIF
       
        ::cBuffer+= cText
        ::nCurLen+= Len(cText)

    RETURN Self
       
    METHOD End()
        IF ::nCurLen > 0
           ::bProcess:Eval( ::cBuffer, ::nCurLen )
        ENDIF

    RETURN NIL
 


Prueba con eso y me cuentas. Se podría poner que cada 'x' escrituras hiciese una llamada al garbage collector, por refinarlo un poco.
Saludos
Carlos Mora
http://harbouradvisor.blogspot.com/
StackOverflow http://stackoverflow.com/users/549761/carlos-mora
“If you think education is expensive, try ignorance"
Carlos Mora
 
Posts: 988
Joined: Thu Nov 24, 2005 3:01 pm
Location: Madrid, España

Re: Carlos Mora y la mala leche de la string concatenation

Postby hmpaquito » Thu Apr 26, 2018 6:10 pm

Hola Carlos,

Agradezco enormemente tu respuesta.
Lo monté concatenando en una variable, que viene a ser como la TBuffer.

Mi sorpresa fue cuando después de ponerle un buffer de 60.000 los tiempos de grabación con fwrite (menos) se mantienen practicamente iguales.
¿ No será muy costoso, en tiempo, casi como un fwrite() un bucle concatenario del estilo cLine+= cText ?

El proceso finalizó y no hubo un gpf de memoria y no tuve que recurrir al colector de memoria.

Saludos
hmpaquito
 
Posts: 1482
Joined: Thu Oct 30, 2008 2:37 pm

Re: Carlos Mora y la mala leche de la string concatenation

Postby Carlos Mora » Thu Apr 26, 2018 7:57 pm

hmpaquito wrote:Mi sorpresa fue cuando después de ponerle un buffer de 60.000 los tiempos de grabación con fwrite (menos) se mantienen practicamente iguales.
¿ No será muy costoso, en tiempo, casi como un fwrite() un bucle concatenario del estilo cLine+= cText ?

Si le pones un buffer tan grande, entonces la concatenación empieza a complicar el asunto.
Piensa en esta situación: tenemos un cBuffer que tiene 59000 caracteres, y le añadimos una cadena de 10 caracteres ¿Que va a suceder cuando se ejecute ::cBuffer += cText? Harbour calcula el largo de la string resultante (59010), aloca un trozo de memoria de ese tamaño, copia los 59000 bytes de la primera cadena y a continuacion los 10 de la segunda. Luego marca como disponible el trozo de 59000 que venia usando. Es un movimiento INFERNAL con cada concatenación, por lo que no es ventajoso tener bufferes tan grandes.
Prueba de hacerlo con una longitud de 4096, 8192, etc (potencias de 2, el tamaño del sector del disco para optimizar I/O)
a ver que resultados obtienes. Si tuviésemos la función por la que pregunté en su momento ahí si habría ventaja, porque no se hacen copias del buffer con cada concatenación.

Un saludo
Saludos
Carlos Mora
http://harbouradvisor.blogspot.com/
StackOverflow http://stackoverflow.com/users/549761/carlos-mora
“If you think education is expensive, try ignorance"
Carlos Mora
 
Posts: 988
Joined: Thu Nov 24, 2005 3:01 pm
Location: Madrid, España

Re: Carlos Mora y la mala leche de la string concatenation

Postby hmpaquito » Fri Apr 27, 2018 7:58 am

Carlos,

Gracias por la explicacion.

Pondré un buffer más pequeño para intentar guardar un equilibrio entre tamaño de buffer y numero de fwrites.
Volveré por aquí a informar del resultado.

Saludos

PD 1. ¿ No seria más rapido crear el fichero en memoria y tratarlo con fwrite ?
PD 2. El txt que creo, ahora mismo, tiene ~ 130 mb y tarda 4 h.
hmpaquito
 
Posts: 1482
Joined: Thu Oct 30, 2008 2:37 pm

Re: Carlos Mora y la mala leche de la string concatenation

Postby xmanuel » Sat Apr 28, 2018 3:46 pm

Paquito por curiosidad, qué es lo que quieres hacer?
De qué se trata?
Qué quieres decir con tus dos PD:
PD 1. ¿ No seria más rapido crear el fichero en memoria y tratarlo con fwrite ?
PD 2. El txt que creo, ahora mismo, tiene ~ 130 mb y tarda 4 h.

Segú lo que sea te podré ayudar :roll:
______________________________________________________________________________
Sevilla - Andalucía
xmanuel
 
Posts: 756
Joined: Sun Jun 15, 2008 7:47 pm
Location: Sevilla

Re: Carlos Mora y la mala leche de la string concatenation

Postby Carlos Mora » Sun Apr 29, 2018 7:12 am

Hola Manuel,
xmanuel wrote:Segú lo que sea te podré ayudar :roll:

Eres la persona ideal :)
Necesitamos una función similar al Stuff(), pero por referencia, es decir que no me haga copias de las strings sino que altere la original.
Si miras el post que puse hace ya mucho tiempo, la idea es crear una clase Buffer genérica, que permita hacer cosas como las que hace Paquito sin provocar fragmentaciones ni hacer tantas copias en la memoria.
La idea es tener una función InsertTheString( @cBuffer, nPos, cText[, nLen] ), que inserta en la posición nPos la cadena cText, opcionalmente limitando la longitud a insertar. Cuando creo el buffer, prealoco una cadena de la longitud que quiero, y la voy 'llenando' hasta que ya no puedo más, entonces proceso el contenido hasta ese momento.
Tal como está la clase, es muy genérica y valdría para muchas cosas. Principalmente sería la generaciónde ficheros de texto que se generan a trocitos, como los XMLs, HTML y cosas así, y que la salida puede ir escribiendose a un fichero, un socket, etc... Con eso disminuímos la cantidad de escrituras, pudiendo elegir el tamaño del buffer para afinar el comportameinto según las necesidades del caso.

Saludos
Saludos
Carlos Mora
http://harbouradvisor.blogspot.com/
StackOverflow http://stackoverflow.com/users/549761/carlos-mora
“If you think education is expensive, try ignorance"
Carlos Mora
 
Posts: 988
Joined: Thu Nov 24, 2005 3:01 pm
Location: Madrid, España

Re: Carlos Mora y la mala leche de la string concatenation

Postby hmpaquito » Mon Apr 30, 2018 7:12 am

Hola Manuel,

Perfectamente explicado por Carlos.

Gracias por tu interés.


EDITADO:

Un ejemplo seria este:
Code: Select all  Expand view
#Define nBUFFER_SIZE 4096

nH:= FCreate("Clientes.Txt")
cLin:= ""

SELECT Clientes
GO TOP
DO WHILE !Eof()

   // Aqui es donde se deteriora la memoria, especialmente con buffer de gran tamaño.
   cLin+= FIELD-> Codigo+ ";"+ FIELD-> Nombre+ ";"+ FIELD-> Direccion+ ";"+ FIELD-> Poblacion+ ";"+ FIELD-> Provincia+ ";"+ FIELD-> Observaciones+ CRLF

   If Len(cLin) >= nBUFFER_SIZE
      FWrite(nH, cLin)
      cLin:= ""
   ENDIF
   SKIP
ENDDO
FWrite(nH, cLin)
FClose(nH)
hmpaquito
 
Posts: 1482
Joined: Thu Oct 30, 2008 2:37 pm

Re: Carlos Mora y la mala leche de la string concatenation

Postby xmanuel » Tue May 01, 2018 7:15 pm

Os doy una idea de momento...
Más adelante si no podéis implementarla lo hago yo...

La idea es crea un buffer de memoria fijo que escriba en el fichero de destino cuando se llene.
Y de eso se tiene que encargar el propio gestor del buffer.
El ancho del buffer yo lo pondría lo más grande posible (lo que permita FWrite())

Code: Select all  Expand view

mibuffer := creaBuffer()

    SELECT Clientes
    GO TOP
    DO WHILE !Eof()
       escribeEnBuffer( mibuffer,  FIELD-> Codigo )
       escribeEnBuffer( mibuffer,   ";" )
       escribeEnBuffer( mibuffer,  FIELD-> Nombre)
       escribeEnBuffer( mibuffer,   ";" )
       escribeEnBuffer( mibuffer,  FIELD-> Direccion)
       escribeEnBuffer( mibuffer,   ";" )
       escribeEnBuffer( mibuffer,  FIELD-> Poblacion)
       escribeEnBuffer( mibuffer,   ";" )
       escribeEnBuffer( mibuffer,  FIELD-> Provincia)
       escribeEnBuffer( mibuffer,   ";" )
       escribeEnBuffer( mibuffer,  FIELD-> Observaciones)
       escribeEnBuffer( mibuffer,   CRLF )

       SKIP
    ENDDO
freeBuffer( mibuffer )
 


No sé si captais la idea?

Si me dejais hasta el fin de semana lo hago :roll:
______________________________________________________________________________
Sevilla - Andalucía
xmanuel
 
Posts: 756
Joined: Sun Jun 15, 2008 7:47 pm
Location: Sevilla

Re: Carlos Mora y la mala leche de la string concatenation

Postby hmpaquito » Wed May 02, 2018 7:37 am

Buenos días Manuel,

Me encantaría si pudieras dar una solución a este problema.
Al menos yo, necesitaría que funcionara con xHarbour.

Agradecido.

Saludos
hmpaquito
 
Posts: 1482
Joined: Thu Oct 30, 2008 2:37 pm

Re: Carlos Mora y la mala leche de la string concatenation

Postby Carlos Mora » Wed May 02, 2018 10:15 am

Manu,
xmanuel wrote:La idea es crea un buffer de memoria fijo que escriba en el fichero de destino cuando se llene.
Y de eso se tiene que encargar el propio gestor del buffer.
El ancho del buffer yo lo pondría lo más grande posible (lo que permita FWrite())

Eso ya está hecho, mira el primer post. Lo que tu has hecho con funciones lo escribí hace mucho con una clase, solo falta la función C que haga lo del Stuff por referenciay sale funcionando.
Respecto de poner un buffer grande (sin la función Stuff por referencia), tiene un inconveniente que ya ha demostrado Paquito en la práctica: la concatenación sucesiva destroza literalmente la memoria, fragmentándola. Hay una explicación mía en el hilo de porque sucede.
xmanuel wrote:Si me dejais hasta el fin de semana lo hago :roll:


En el foro de Harbour Users me sugieren que use MemIO, pero sería meter una dependencia donde se puede resolver con algo más sencillo. Si uso MemIO directamente lo deja en memoria y lo escribo al cerrar. Y no se bien como se comporta con el tamaño creciente. demasiado rollo que no justifico, al menos de momento con la idea que sugieren.
La solución con la clase me parece más elegante porque es genérica, hace de Buffer sin importar como vas a consumir el buffer, eso lo haces pasándole el codeblock 'de consumo'. Se puede usar para más cosas (de hecho lo estoy necesitando, aunque no tan urgentemente).
SOLID: Single Concern (Es un Buffer), Open/Close(Suficientemete configurable para no requerir cambios, y se puede extender si hace falta), Liskov(Es extensible y se pueden usar las subclases con la misma interfaz), Interface segregada (Una interfaz con pocos métodos, que solo hace una cosa), inyección de Dependencias (Es el codeblock el que decide que se hace con la cadena resultante).

Usos de la clase Buffer: Logs de la aplicación, Volcado de SQL (por ejemplo migraciones de DBF a SQL), Salidas a la response en un servidor http, etc.
Saludos
Carlos Mora
http://harbouradvisor.blogspot.com/
StackOverflow http://stackoverflow.com/users/549761/carlos-mora
“If you think education is expensive, try ignorance"
Carlos Mora
 
Posts: 988
Joined: Thu Nov 24, 2005 3:01 pm
Location: Madrid, España

Re: Carlos Mora y la mala leche de la string concatenation

Postby hmpaquito » Wed May 02, 2018 6:22 pm

hmpaquito wrote:Pondré un buffer más pequeño para intentar guardar un equilibrio entre tamaño de buffer y numero de fwrites.
Volveré por aquí a informar del resultado.


Pues nada, que no ha funcionado. Puse un buffer de 8192 y el tiempo es el mismo: ~ 4 h.
hmpaquito
 
Posts: 1482
Joined: Thu Oct 30, 2008 2:37 pm

Re: Carlos Mora y la mala leche de la string concatenation

Postby xmanuel » Fri May 04, 2018 1:27 am

Jajaja...

Este fin de semana veremos si eso que hace Paquito en 4 horas pasa a minutos... como mucho :D
Atentos a las pantallas...

PD: Si no doy señales de vida es que se me ha olvidado, solo tenéis que recordármelo, ok?
______________________________________________________________________________
Sevilla - Andalucía
xmanuel
 
Posts: 756
Joined: Sun Jun 15, 2008 7:47 pm
Location: Sevilla

Re: Carlos Mora y la mala leche de la string concatenation

Postby nageswaragunupudi » Fri May 04, 2018 2:08 pm

Very interesting discussion among experts about optimization of string operations, avoiding memory fragmentation, etc. Though this topic interests me too, at the moment, I am not discussing that topic.

The discussion appears to have started with the requirement of Mr. Paquito to reduce the time to export dbf to a delimited text file.
The txt that I believe, right now, has ~ 130 mb and takes 4 h.

Do you really mean 130 MB ( = 130,000,000 bytes ), not 130GB?
If it is only 130 MB, it should not take more than 2 or 3 minutes without any optimizations, even with standard COPY TO .. DELIMITED WITH ... command.

Instead of COPY TO command, we can even try the (x)Harbour function
Code: Select all  Expand view

DBF2TEXT( bWhile, bFor, aFields, cDelim, hFile, cSep, nCount, cdp )
 


The above example is a very simple case where all the fields are character fields. There can be cases wehre some fields can be numeric, dates, etc, requirng conversion to strings.

For testing I exported \fwh\samples\customer.dbf 2200 times to customer.txt. This took 2 minutes and produced a text file of size 142.527,000 bytes, i.e., 142.527 MB.
Code: Select all  Expand view

04-05-2018  18:46       142,527,000 customer.txt
               1 File(s)    142,527,000 bytes
 


My test code:
Code: Select all  Expand view

   USE CUSTOMER
   hFile    := FCreate( "customer.txt" )
   ? "start"
   nSecs    := SECONDS()
   for n := 1 to 2200
      CUSTOMER->( DBGOTOP() )
      CUSTOMER->( DBF2TEXT( nil, nil, nil, nil, hFile, ";", -1 ) )
   next
   FClose( hFile )
   ? SECONDS() - nSecs
 


Did I wrongly understand the requirement of Mr. Paquito?
Regards

G. N. Rao.
Hyderabad, India
User avatar
nageswaragunupudi
 
Posts: 10248
Joined: Sun Nov 19, 2006 5:22 am
Location: India

Next

Return to FiveWin para Harbour/xHarbour

Who is online

Users browsing this forum: richard-service and 69 guests