TFBuffer, una clase para acelerar la escritura en ficheros

TFBuffer, una clase para acelerar la escritura en ficheros

Postby xmanuel » Sun May 06, 2018 7:51 am

Aquí os dejo una clase para manejar un buffer de escritura a ficheros.
Acelera muchísimo el proceso.
La clase es muy simple de usar y con pocos métodos.
La dejo aquí para que todo el que la mejore la vaya dejando en este hilo.

Ejemplo:
Code: Select all  Expand view  RUN

procedure main

    local hFile := FCreate( "prueba.txt" )
    local o := TFBuffer():new( hFile )
    local i, t

    cls
   
    @ 10, 10 SAY "Espere, estoy procesando datos..."
   
    t := Seconds()
   
    for i := 1 to 10000000
        o:addString( "Esta es la cadena numero: " )
        o:addString( Str( i ) )
        o:addString( " -> " )
        o:addString( Time() )
        o:addString( ";" )
        o:addString( hb_eol() )
    next
   
    t := Seconds() - t
   
    o:free()

    FClose( hFile )

    // Para calcular el tamaño en kB
    hFile := FOpen( "prueba.txt" )
    i := FSeek( hFile, 0, 2 ) / 1024
    FClose( hFile )
   
    Alert( "Ha tardado: " + AllTrim( Str( t ) ) + " segundos con un fichero de: " + AllTrim( Str( i ) ) + " kB")
   
return
 


En mi ordenador genera un fichero de 550.000.000 en solo casi 10 segundos (9.88).

Y esta es la clase:
Code: Select all  Expand view  RUN

#include "hbclass.ch"

#define FB_MAX_SIZE             65535

CLASS TFBuffer
   
    DATA hFB  // Uso interno
   
    METHOD new( hFile, nLen ) CONSTRUCTOR
    METHOD free() // Libera memoria
    METHOD creaBuffer( hFile, nLen )  // Uso interno
    METHOD addString( cString ) // añade cadena al buffer, hace el ::flush() automaticamente
    METHOD flush() // Por si se quiere salvar a disco manualmente
   
END CLASS

//------------------------------------------------------------------------------

METHOD new( hFile, nLen ) CLASS TFBuffer

    if ValType( hFile ) == 'N'     
        if !( ValType( nLen ) == 'N' .and. nLen > 0 .and. nLen <= FB_MAX_SIZE )
            nLen := FB_MAX_SIZE
        endif
        ::creaBuffer( hFile, nLen )
    else
        Alert( "Al menos deberías meter el manejador de un fichero abierto..." )
    endif      
   
return self

//------------------------------------------------------------------------------

#pragma BEGINDUMP

#include "hbapifs.h"
#include "hbapiitm.h"
#include "hbstack.h"

/* Estructura del bufer */
typedef struct
{
    unsigned char *pBuffer;
    unsigned int uiLen;
    unsigned int uiPos;
    HB_FHANDLE hFile;
} FBUFFER, *PFBUFFER;

static void _flushFB( PFBUFFER pFB );

//------------------------------------------------------------------------------
// Metodo para crear la estructura  buffer

HB_FUNC_STATIC( TFBUFFER_CREABUFFER )
{
    PHB_ITEM Self = hb_stackSelfItem();
    PFBUFFER pFB = ( PFBUFFER ) hb_xgrab( sizeof( FBUFFER ) );

    pFB->uiLen = hb_parnint( 2 );
    pFB->pBuffer = ( unsigned char * ) hb_xgrab( pFB->uiLen );
    pFB->uiPos = 0;
    pFB->hFile = ( HB_FHANDLE ) hb_parnint( 1 );

    hb_arraySetPtr( Self, 1, pFB );
}

//------------------------------------------------------------------------------
// Metodo para liberar el buffer

HB_FUNC_STATIC( TFBUFFER_FREE )
{
    PHB_ITEM Self = hb_stackSelfItem();
    PFBUFFER pFB = hb_arrayGetPtr( Self, 1 );

    if( pFB )   
    {
        _flushFB( pFB );

        if( pFB->pBuffer )
        {
            hb_xfree( pFB->pBuffer );
        }

        hb_xfree( pFB );
    }
}

//------------------------------------------------------------------------------
// Metodo para añadir cadenas al buffer

HB_FUNC_STATIC( TFBUFFER_ADDSTRING )
{
    PHB_ITEM Self = hb_stackSelfItem();
    PFBUFFER pFB = hb_arrayGetPtr( Self, 1 );
    PHB_ITEM pString = hb_param( 1, HB_IT_STRING  );
   
    if( pString )
    {
        unsigned int uiPos = 0;
        const char *szString = hb_itemGetCPtr( pString );
        unsigned int uiLen = hb_itemGetCLen( pString );

        while( uiPos < uiLen )
        {
            if( pFB->uiPos == pFB->uiLen )
            {
                _flushFB( pFB );
            }
           
            pFB->pBuffer[ pFB->uiPos++ ] = ( unsigned char ) szString[ uiPos++ ];
        }
    }
}

//------------------------------------------------------------------------------
// Metodo para forzar escritura en disco del buffer e inicia la posicion

HB_FUNC_STATIC( TFBUFFER_FLUSH )
{
    PHB_ITEM Self = hb_stackSelfItem();
    PFBUFFER pFB = hb_arrayGetPtr( Self, 1 );

    _flushFB( pFB );
}

//------------------------------------------------------------------------------
// Funcion de uso interno. Escribe en disco el buffer e inicia la posicion

static void _flushFB( PFBUFFER pFB )
{
    if( pFB->uiPos > 0 )
    {
        hb_fsWrite( pFB->hFile, pFB->pBuffer, pFB->uiPos );
        pFB->uiPos = 0;
    }
}

//------------------------------------------------------------------------------

#pragma ENDDUMP
 


Como veis está escrita casi toda en lenguaje C
Los métodos son auto descriptivos.

Si la mejoráis, ponerla aquí para que todos podamos usarla.
______________________________________________________________________________
Sevilla - Andalucía
xmanuel
 
Posts: 763
Joined: Sun Jun 15, 2008 7:47 pm
Location: Sevilla

Re: TFBuffer, una clase para acelerar la escritura en ficheros

Postby nageswaragunupudi » Sun May 06, 2018 5:06 pm

Excellent.
Also, a very good example showing how to write methods in C language.
I have a few requests if you don't mind implementing.

Now, we need to convert numbers and dates to string values before calling addstring(). These conversions at Harbour level are a bit less efficient than in C level. Can you handle these conversions inside the method automatically?
Also to reduce the number of calls, can we call
:AddString( c1, ",", c2, ",", c3, .... )
Regards

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

Re: TFBuffer, una clase para acelerar la escritura en ficheros

Postby cnavarro » Sun May 06, 2018 6:11 pm

Manu, muy buena, gracias, y una lección de la que todos aprenderemos como se programa en Harbour/C
Oye, para calcular el tamaño del fichero, por qué no utilizas Hb_FSize?
Last edited by cnavarro on Sun May 06, 2018 7:38 pm, edited 1 time in total.
Cristobal Navarro
Hay dos tipos de personas: las que te hacen perder el tiempo y las que te hacen perder la noción del tiempo
El secreto de la felicidad no está en hacer lo que te gusta, sino en que te guste lo que haces
User avatar
cnavarro
 
Posts: 6552
Joined: Wed Feb 15, 2012 8:25 pm
Location: España

Re: TFBuffer, una clase para acelerar la escritura en ficheros

Postby Massimo Linossi » Sun May 06, 2018 6:41 pm

Great !!!
Can it be used for buffering the write on DBF files too ?
User avatar
Massimo Linossi
 
Posts: 505
Joined: Mon Oct 17, 2005 10:38 am
Location: Italy

Re: TFBuffer, una clase para acelerar la escritura en ficheros

Postby Carlos Mora » Sun May 06, 2018 6:46 pm

nageswaragunupudi wrote:Now, we need to convert numbers and dates to string values before calling addstring(). These conversions at Harbour level are a bit less efficient than in C level. Can you handle these conversions inside the method automatically?

I would prefer to keep it as simple as posible, because all the winnings will be lost in the logic applied to EVERY call. It will be clearer for the class user, otherwise the complex or 'tricky' behavoir should be very well documented, so my vote to KISS. :D
An alternative would be to have extra methods to every type, like AddDate, AddNumber, etc.

I've run my own test in an more than 10 years laptop, and it is able to run the test in 44 secs, what is really amazing.

Adhiero a la opinión de Cristobal, es una verdadera lección de como escribir métodos en C.
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: 989
Joined: Thu Nov 24, 2005 3:01 pm
Location: Madrid, España

Re: TFBuffer, una clase para acelerar la escritura en ficheros

Postby hmpaquito » Mon May 07, 2018 7:40 am

Buenos días a todos y todas,

Vayamos por partes, como dijo el de York.

1º Muchas gracias Manuel por tu trabajo. Aún no lo he probado, tengo que terminar primero mi issue y luego sigo probando la TBuffer que tan amablemente has hecho.

2º Sería bueno que la TBuffer pudiera valer para más cosas aparte de crear ficheros .txt (o .csv.). Por ejemplo, yo uso mucho la concatenación para generar ficheros excel: primero acumulo en la variable String y luego pego su contenido (paste) en la hoja de cálculo; ahora mismo, tengo limitado el tamaño de la variable (copy) a 25.000 bytes porque con un buffer de 60.000 a veces genera GPF (errores de alloc), con lo que para este caso vendría de perlas tu clase.

Yo propongo que o bien haya dos clases, o bien haya dos comportamientos o bien se vacie el buffer en una llamada a un codeblock que se use como parametro.

Si haces dos clases, propongo que se llamen TBufferString, para tratamiento manual del buffer y TBufferFile, para volcado automático a ficheros de texto.
Si haces dos comportamientos dentro de la misma clase, el comportamiento String, el vaciado del buffer se haría externamente llamando al metodo flush.
Si lo haces con un codeblock, como lo tenía prototipado Carlos Mora, bien se le podría llamar bFlush, recibiría el parámetro de la cadena, a ser posible por referencia, para ganar en velocidad.


3º Estoy con Carlos Mora, en que habría que mantener la simplicidad del :AddString(), no sobrecargarlo ni con multiples parametros ni con conversiones. Si podrían haber, como indica Carlos, más metodos:
Code: Select all  Expand view  RUN
   :addStringMultiple()
   :addDate()
   :addNumber()
 

Saludos cordiales



PD 1. Tengo una duda. Veo que la funcion Stuff() utiliza hb_xmemcpy(), que a su vez llama a la primitiva memcpy(). En cambio tu TBuffer copia byte a byte. ¿ Será suficientemente rápido el copiado caracter a caracter ? Es que en mi (gran) ignorancia no sé si existe alguna forma más rápida de copiar.
PD 2. Agradecería si mantuvieras la clase compatible con xHarbour.
hmpaquito
 
Posts: 1482
Joined: Thu Oct 30, 2008 2:37 pm

Re: TFBuffer, una clase para acelerar la escritura en ficheros

Postby xmanuel » Mon May 07, 2018 5:48 pm

Paquito, en C todas las funciones de tratamiento de cadenas es carácter a carácter.
Seguro que el tratamiento de lo que haces tardará menos de 4 horas. Pero eso mejor pruébalo y salimos de dudas :D

Esto es un StringBuffer para escribir en ficheros.

La idea de Carlos me parce muy buena, me refiero a hacer una clase buffer desacoplada de ficheros.
Para eso sólo habría que cambiar dos cosas:
1.- El parámetro hFile
2. Y convertir el metodo ::flush() en Virtual y desarrollarlo en las clases heredadas y especializadas.

Con esto conseguiríamos una clase TStringBuffer() independiente de donde se va a escribir y de esta se podría derivar otras clase especializadas como la TFBuffer() (TFileBuffer()) que es la que yo he hecho.

Para los que quieran hacer que TFBuffer pueda funcionar con cualquier tipo de dato quiero deciros que existe una función que provee harbour llamada hb_itemString() que os puede servir.

PD: gracias por vuestros comentarios :lol:
______________________________________________________________________________
Sevilla - Andalucía
xmanuel
 
Posts: 763
Joined: Sun Jun 15, 2008 7:47 pm
Location: Sevilla

Re: TFBuffer, una clase para acelerar la escritura en ficheros

Postby xmanuel » Mon May 07, 2018 7:41 pm

Al final he hecho la clase TFBuffer para que se le pueda pasar cualquier tipo de dato:

Ejemplo:
Code: Select all  Expand view  RUN

procedure main

    local hFile := FCreate( "prueba.txt" )
    local o := TFBuffer():new( hFile )
    local i, t

    set date format to "yyyy-mm-dd"
   
    cls
   
    @ 10, 10 SAY "Espere, estoy procesando datos..."
   
    t := Seconds()
/* 
    for i := 1 to 10000000
        o:addString( "Esta es la cadena numero: " )
        o:addString( Str( i ) )
        o:addString( " -> " )
        o:addString( Time() )
        o:addString( ";" )
        o:addString( hb_eol() )
    next
*/

    for i := 1 to 1000000
        o:addValue( ( i % 2 ) == 0 )     // Logical
        o:addValue( i )                  // Numeric
        o:addValue( " <> " )             // String
        o:addValue( date() )             // Date
        o:addValue( " " )                // String
        o:addValue( Time() )             // String
        o:addValue( hb_eol() )           // String
    next
   
    t := Seconds() - t
   
    o:free()

    FClose( hFile )

    // Para calcular el tamaño en kB
    i := Hb_FSize( "prueba.txt" ) / 1024
   
    Alert( "Ha tardado: " + AllTrim( Str( t ) ) + " segundos con un fichero de: " + AllTrim( Str( i ) ) + " kB")
   
return
 


El nuevo método se llama addValue( uValue )

Esta es la clase:
Code: Select all  Expand view  RUN

#include "hbclass.ch"

#define FB_MAX_SIZE             65535

CLASS TFBuffer
   
    DATA hFB  // Uso interno
   
    METHOD new( hFile, nLen ) CONSTRUCTOR
    METHOD free() // Libera memoria
    METHOD creaBuffer( hFile, nLen )  // Uso interno
    METHOD addString( cString ) // añade cadena al buffer, hace el ::flush() automaticamente
    METHOD addValue( value ) // convierte el valor en cadena y lo añade al buffer
    METHOD flush() // Por si se quiere salvar a disco manualmente
   
END CLASS

//------------------------------------------------------------------------------

METHOD new( hFile, nLen ) CLASS TFBuffer

    if ValType( hFile ) == 'N'     
        if !( ValType( nLen ) == 'N' .and. nLen > 0 .and. nLen <= FB_MAX_SIZE )
            nLen := FB_MAX_SIZE
        endif
        ::creaBuffer( hFile, nLen )
    else
        Alert( "Al menos deberías meter el manejador de un fichero abierto..." )
    endif      
   
return self

//------------------------------------------------------------------------------

#pragma BEGINDUMP

#include "hbapifs.h"
#include "hbapiitm.h"
#include "hbstack.h"

/* Estructura del bufer */
typedef struct
{
    unsigned char *pBuffer;
    unsigned int uiLen;
    unsigned int uiPos;
    HB_FHANDLE hFile;
} FBUFFER, *PFBUFFER;

static void _flushFB( PFBUFFER pFB );

//------------------------------------------------------------------------------
// Metodo para crear la estructura  buffer

HB_FUNC_STATIC( TFBUFFER_CREABUFFER )
{
    PHB_ITEM Self = hb_stackSelfItem();
    PFBUFFER pFB = ( PFBUFFER ) hb_xgrab( sizeof( FBUFFER ) );

    pFB->uiLen = hb_parnint( 2 );
    pFB->pBuffer = ( unsigned char * ) hb_xgrab( pFB->uiLen );
    pFB->uiPos = 0;
    pFB->hFile = ( HB_FHANDLE ) hb_parnint( 1 );

    hb_arraySetPtr( Self, 1, pFB );
}

//------------------------------------------------------------------------------
// Metodo para liberar el buffer

HB_FUNC_STATIC( TFBUFFER_FREE )
{
    PHB_ITEM Self = hb_stackSelfItem();
    PFBUFFER pFB = hb_arrayGetPtr( Self, 1 );

    if( pFB )   
    {
        _flushFB( pFB );

        if( pFB->pBuffer )
        {
            hb_xfree( pFB->pBuffer );
        }

        hb_xfree( pFB );
    }
}

//------------------------------------------------------------------------------
// Metodo para añadir cadenas al buffer

HB_FUNC_STATIC( TFBUFFER_ADDSTRING )
{
    PHB_ITEM Self = hb_stackSelfItem();
    PFBUFFER pFB = hb_arrayGetPtr( Self, 1 );
    PHB_ITEM pString = hb_param( 1, HB_IT_STRING  );
   
    if( pString )
    {
        unsigned int uiPos = 0;
        const char *szString = hb_itemGetCPtr( pString );
        unsigned int uiLen = hb_itemGetCLen( pString );

        while( uiPos < uiLen )
        {
            if( pFB->uiPos == pFB->uiLen )
            {
                _flushFB( pFB );
            }
           
            pFB->pBuffer[ pFB->uiPos++ ] = ( unsigned char ) szString[ uiPos++ ];
        }
    }
}

//------------------------------------------------------------------------------
// Metodo para añadir cadenas al buffer

HB_FUNC_STATIC( TFBUFFER_ADDVALUE )
{
    PHB_ITEM Self = hb_stackSelfItem();
    PFBUFFER pFB = hb_arrayGetPtr( Self, 1 );
    PHB_ITEM pValue = hb_param( 1, HB_IT_ANY  );
   
    if( pValue )
    {
        unsigned int uiPos = 0;
        HB_SIZE uiLen;
        HB_BOOL fFree;
        char *szString = hb_itemString( pValue, &uiLen, &fFree );

        while( uiPos < uiLen )
        {
            if( pFB->uiPos == pFB->uiLen )
            {
                _flushFB( pFB );
            }
           
            pFB->pBuffer[ pFB->uiPos++ ] = ( unsigned char ) szString[ uiPos++ ];
        }
       
        if( fFree )
        {
            hb_xfree( szString );
        }
    }
}

//------------------------------------------------------------------------------
// Metodo para forzar escritura en disco del buffer e inicia la posicion

HB_FUNC_STATIC( TFBUFFER_FLUSH )
{
    PHB_ITEM Self = hb_stackSelfItem();
    PFBUFFER pFB = hb_arrayGetPtr( Self, 1 );

    _flushFB( pFB );
}

//------------------------------------------------------------------------------
// Funcion de uso interno. Escribe en disco el buffer e inicia la posicion

static void _flushFB( PFBUFFER pFB )
{
    if( pFB->uiPos > 0 )
    {
        hb_fsWrite( pFB->hFile, pFB->pBuffer, pFB->uiPos );
        pFB->uiPos = 0;
    }
}

//------------------------------------------------------------------------------

#pragma ENDDUMP
 


Espero que te valga Paquito.
Mr. Rao creo que esto es lo que quería...

Un saludo :D
Manu Exposito
______________________________________________________________________________
Sevilla - Andalucía
xmanuel
 
Posts: 763
Joined: Sun Jun 15, 2008 7:47 pm
Location: Sevilla


Return to FiveWin para Harbour/xHarbour

Who is online

Users browsing this forum: Google [Bot] and 15 guests