TFBuffer, una clase para acelerar la escritura en ficheros

Post Reply
xmanuel
Posts: 768
Joined: Sun Jun 15, 2008 7:47 pm
Location: Sevilla
Been thanked: 5 times
Contact:

TFBuffer, una clase para acelerar la escritura en ficheros

Post by xmanuel »

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


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


#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
User avatar
nageswaragunupudi
Posts: 10721
Joined: Sun Nov 19, 2006 5:22 am
Location: India
Been thanked: 8 times
Contact:

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

Post by nageswaragunupudi »

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
cnavarro
Posts: 6557
Joined: Wed Feb 15, 2012 8:25 pm
Location: España
Been thanked: 3 times

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

Post by cnavarro »

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
Massimo Linossi
Posts: 508
Joined: Mon Oct 17, 2005 10:38 am
Location: Italy

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

Post by Massimo Linossi »

Great !!!
Can it be used for buffering the write on DBF files too ?
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

Post by Carlos Mora »

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"
hmpaquito
Posts: 1482
Joined: Thu Oct 30, 2008 2:37 pm

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

Post by hmpaquito »

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

   :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.
xmanuel
Posts: 768
Joined: Sun Jun 15, 2008 7:47 pm
Location: Sevilla
Been thanked: 5 times
Contact:

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

Post by xmanuel »

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: 768
Joined: Sun Jun 15, 2008 7:47 pm
Location: Sevilla
Been thanked: 5 times
Contact:

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

Post by xmanuel »

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

Ejemplo:

Code: Select all | Expand


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


#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
Post Reply