Carlos Mora y la mala leche de la string concatenation
Carlos Mora y la mala leche de la string concatenation
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.
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.
Re: Carlos Mora y la mala leche de la string concatenation
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.
¿ 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.
-
- Posts: 989
- Joined: Thu Nov 24, 2005 3:01 pm
- Location: Madrid, España
Re: Carlos Mora y la mala leche de la string concatenation
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.
Prueba con eso y me cuentas. Se podría poner que cada 'x' escrituras hiciese una llamada al garbage collector, por refinarlo un poco.
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
#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
http://harbouradvisor.blogspot.com/
StackOverflow http://stackoverflow.com/users/549761/carlos-mora
“If you think education is expensive, try ignorance"
Re: Carlos Mora y la mala leche de la string concatenation
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
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
-
- Posts: 989
- Joined: Thu Nov 24, 2005 3:01 pm
- Location: Madrid, España
Re: Carlos Mora y la mala leche de la string concatenation
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
http://harbouradvisor.blogspot.com/
StackOverflow http://stackoverflow.com/users/549761/carlos-mora
“If you think education is expensive, try ignorance"
Re: Carlos Mora y la mala leche de la string concatenation
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.
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.
-
- Posts: 768
- Joined: Sun Jun 15, 2008 7:47 pm
- Location: Sevilla
- Been thanked: 5 times
- Contact:
Re: Carlos Mora y la mala leche de la string concatenation
Paquito por curiosidad, qué es lo que quieres hacer?
De qué se trata?
Qué quieres decir con tus dos PD:
Segú lo que sea te podré ayudar![Rolling Eyes :roll:](./images/smilies/icon_rolleyes.gif)
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
![Rolling Eyes :roll:](./images/smilies/icon_rolleyes.gif)
______________________________________________________________________________
Sevilla - Andalucía
Sevilla - Andalucía
-
- Posts: 989
- Joined: Thu Nov 24, 2005 3:01 pm
- Location: Madrid, España
Re: Carlos Mora y la mala leche de la string concatenation
Hola Manuel,
Eres la persona ideal![Smile :)](./images/smilies/icon_smile.gif)
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
xmanuel wrote:Segú lo que sea te podré ayudar
Eres la persona ideal
![Smile :)](./images/smilies/icon_smile.gif)
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
http://harbouradvisor.blogspot.com/
StackOverflow http://stackoverflow.com/users/549761/carlos-mora
“If you think education is expensive, try ignorance"
Re: Carlos Mora y la mala leche de la string concatenation
Hola Manuel,
Perfectamente explicado por Carlos.
Gracias por tu interés.
EDITADO:
Un ejemplo seria este:
Perfectamente explicado por Carlos.
Gracias por tu interés.
EDITADO:
Un ejemplo seria este:
Code: Select all | Expand
#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)
-
- Posts: 768
- Joined: Sun Jun 15, 2008 7:47 pm
- Location: Sevilla
- Been thanked: 5 times
- Contact:
Re: Carlos Mora y la mala leche de la string concatenation
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())
No sé si captais la idea?
Si me dejais hasta el fin de semana lo hago![Rolling Eyes :roll:](./images/smilies/icon_rolleyes.gif)
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
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
![Rolling Eyes :roll:](./images/smilies/icon_rolleyes.gif)
______________________________________________________________________________
Sevilla - Andalucía
Sevilla - Andalucía
Re: Carlos Mora y la mala leche de la string concatenation
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
Me encantaría si pudieras dar una solución a este problema.
Al menos yo, necesitaría que funcionara con xHarbour.
Agradecido.
Saludos
-
- Posts: 989
- Joined: Thu Nov 24, 2005 3:01 pm
- Location: Madrid, España
Re: Carlos Mora y la mala leche de la string concatenation
Manu,
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.
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.
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
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
http://harbouradvisor.blogspot.com/
StackOverflow http://stackoverflow.com/users/549761/carlos-mora
“If you think education is expensive, try ignorance"
Re: Carlos Mora y la mala leche de la string concatenation
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.
-
- Posts: 768
- Joined: Sun Jun 15, 2008 7:47 pm
- Location: Sevilla
- Been thanked: 5 times
- Contact:
Re: Carlos Mora y la mala leche de la string concatenation
Jajaja...
Este fin de semana veremos si eso que hace Paquito en 4 horas pasa a minutos... como mucho
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?
Este fin de semana veremos si eso que hace Paquito en 4 horas pasa a minutos... como mucho
![Very Happy :D](./images/smilies/icon_biggrin.gif)
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
Sevilla - Andalucía
- nageswaragunupudi
- Posts: 10721
- Joined: Sun Nov 19, 2006 5:22 am
- Location: India
- Been thanked: 8 times
- Contact:
Re: Carlos Mora y la mala leche de la string concatenation
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.
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
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.
My test code:
Did I wrongly understand the requirement of Mr. Paquito?
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
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
04-05-2018 18:46 142,527,000 customer.txt
1 File(s) 142,527,000 bytes
My test code:
Code: Select all | Expand
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
G. N. Rao.
Hyderabad, India