Leyendo archivo en la estructura -- performance campo con c campo con parsing campo con io camp codereview Relacionados El problema

Reading file into structure


4
vote

problema

Español

En el momento en que estoy tratando de leer un archivo bastante grande en un programa C para el usuario posterior. El tamaño del archivo está en el rango de 800 megabytes que contienen alrededor de 20 millones de líneas de datos del siguiente formato:

yyyymmdd hhmmssmmm, x.xxxxx0, x, xxxxx0,0

  • y: año
  • M: Mes
  • D: día
  • H: hora
  • M: Minuto
  • s: segundo
  • M: milisegundos
  • x: número de punto flotante

Aquí hay algunos ejemplos:

  20150101 130021493,1.209650,1.210070,0  20150101 130044493,1.209720,1.210140,0 20150101 130044743,1.209650,1.210070,0 20150101 130045493,1.209720,1.210140,0  20150101 130045743,1.209670,1.210090,0   

Quiero leer los datos de una sola línea en la siguiente estructura:

  struct forexData {   struct tm timestamp;    uint32_t bidQuote;   uint32_t askQuote; };   

Como ya puede imaginar, estos datos representan las cotizaciones de oferta / pide en un punto específico en el tiempo. Las cotizaciones de oferta / pregunta se almacenan en uint32_t , ya que prefiero un valor entero sobre los flotadores para su uso posterior. Las estructuras de cada línea se colocan en otra estructura, que contendrán todos los datos.

  struct forexDataSet {   struct forexData **data;    uint32_t capacity;   uint32_t cardinality; };   

El data Puntos de propiedad a una matriz de punteros a forexData Estructuras. El capacity -propty describe la cantidad de punteros que puede contener la matriz y 9988776655544338 se mantiene, cuántos punteros se utilizan actualmente.

Aquí están las rutinas para leer en el archivo:

  #include <time.h> #include <stdio.h> #include <fcntl.h> #include <string.h> #include <unistd.h> #include <stdint.h> #include <stdlib.h>  #define INITIAL_SET_SIZE  10000000 #define SET_INCREASE_STEP 1000000  // fileDescriptor returned by open(fileName, O_RDONLY, 0); struct forexDataSet *createForexDataSetFromFile(int fileDescriptor) {   char     buf[39000];   ssize_t nBytesRead;    struct forexDataSet *set = calloc(1, sizeof(struct forexDataSet));   set->data = malloc(INITIAL_SET_SIZE * sizeof(struct forexData *));    if (!set->data) {     return NULL;   }    set->capacity = INITIAL_SET_SIZE;    do {     nBytesRead = read(fileDescriptor, buf, sizeof(buf));      size_t lineStart;     for (lineStart = 0; lineStart < nBytesRead; lineStart += 39) {       struct forexData *tmp = createForexDataFromString(buf + lineStart);         if (!tmp) {         freeForexDataSet(set);         return NULL;       } else {         if (set->cardinality == set->capacity) {           struct forexData **increasedData = realloc(set->data, sizeof(struct forexData *) * (set->capacity + SET_INCREASE_STEP));            if (increasedData == NULL) {             freeForexDataSet(set);             free(tmp);             return NULL;           }            set->data = increasedData;           set->capacity += SET_INCREASE_STEP;         }          set->data[set->cardinality++] = tmp;       }     }    } while(nBytesRead == sizeof(buf));    return set; }  struct forexData *createForexDataFromString(const char *str) {   struct forexData *tmp = calloc(1, sizeof(struct forexData));    if (!tmp) {     return NULL;   }    strptime(str, "%Y%m%d %H%M%S", &tmp->timestamp);    tmp->bidQuote = (uint32_t) (atof(str + 19) * 1000000);   tmp->askQuote = (uint32_t) (atof(str + 28) * 1000000);    return tmp; }  void freeForexDataSet(struct forexDataSet *set) {   size_t dataIndex;    for (dataIndex = 0; dataIndex < set->cardinality; dataIndex++) {     free(set->data[dataIndex]);   }    free(set->data);   free(set); }   

El código compilado con las banderas de clang 20150101 130021493,1.209650,1.210070,0 20150101 130044493,1.209720,1.210140,0 20150101 130044743,1.209650,1.210070,0 20150101 130045493,1.209720,1.210140,0 20150101 130045743,1.209670,1.210090,0 0 Sin las advertencias, excepto una advertencia (comparando 99887766554443311 a 20150101 130021493,1.209650,1.210070,0 20150101 130044493,1.209720,1.210140,0 20150101 130044743,1.209650,1.210070,0 20150101 130045493,1.209720,1.210140,0 20150101 130045743,1.209670,1.210090,0 2 en un 20150101 130021493,1.209650,1.210070,0 20150101 130044493,1.209720,1.210140,0 20150101 130044743,1.209650,1.210070,0 20150101 130045493,1.209720,1.210140,0 20150101 130045743,1.209670,1.210090,0 3 -loop).

Qué me gustaría revisar / hacer una lluvia de ideas:

  • Performance. Actualmente, el proceso toma alrededor de 7 segundos para completarse y me preguntaba si hay algo importante, lo hice mal.
  • tampón tamaño . 39000 (exactamente 1000 líneas, ya que una línea contiene 39 caracteres). Solo leí en líneas enteras a la vez, de lo contrario había tenido un problema con las líneas superpuestas y el análisis, pero elegí 39000 sin una razón específica. Una vez incluso intenté asignar un tamaño de búfer tan grande como el archivo y funcionó bastante bien (tengo 16GB de RAM), pero preferiría no aspirar todo el RAM mientras analiza.
  • trozos de codificación dura . Como probablemente ya viste mucho de esto, es codificado duro 20150101 130021493,1.209650,1.210070,0 20150101 130044493,1.209720,1.210140,0 20150101 130044743,1.209650,1.210070,0 20150101 130045493,1.209720,1.210140,0 20150101 130045743,1.209670,1.210090,0 4 , 20150101 130021493,1.209650,1.210070,0 20150101 130044493,1.209720,1.210140,0 20150101 130044743,1.209650,1.210070,0 20150101 130045493,1.209720,1.210140,0 20150101 130045743,1.209670,1.210090,0 5 bytes talla de búfer, 99887766555443316 y me preguntaba cómo mejorar estos sin para implementar un analizador de cadena de formato bastante complejo. Tal vez haya una forma inteligente de determinar el tamaño del búfer de algo como este.
  • Error manejando y liberando . Todo está liberado correctamente, cuando algo no podía asignarse, etc.
Original en ingles

At the time I'm trying to read a quite big file into a C program for later user. The file size is in the range of 800 megabytes containing around 20 million lines of data of the following format:

YYYYMMDD HHMMSSMMM,X.XXXXX0,X,XXXXX0,0

  • Y: Year
  • M: Month
  • D: Day
  • H: Hour
  • M: Minute
  • S: Second
  • M: Milliseconds
  • X: floating point number

Here are a few examples:

20150101 130021493,1.209650,1.210070,0  20150101 130044493,1.209720,1.210140,0 20150101 130044743,1.209650,1.210070,0 20150101 130045493,1.209720,1.210140,0  20150101 130045743,1.209670,1.210090,0 

I want to read the data of a single line into the following structure:

struct forexData {   struct tm timestamp;    uint32_t bidQuote;   uint32_t askQuote; }; 

As you can already imagine, this data represents the bid/ask quotes at a specific point in time. The bid/ask quotes are stored in uint32_t, since I prefer an integer value over floats for later use. The structures of each line are placed in another structure, which will contain all data.

struct forexDataSet {   struct forexData **data;    uint32_t capacity;   uint32_t cardinality; }; 

The data-property points to an array of pointers to forexData structures. The capacity-property describes how many pointers the array can hold and cardinality holds, how many pointers are currently used.

Here are the routines to read in the file:

#include <time.h> #include <stdio.h> #include <fcntl.h> #include <string.h> #include <unistd.h> #include <stdint.h> #include <stdlib.h>  #define INITIAL_SET_SIZE  10000000 #define SET_INCREASE_STEP 1000000  // fileDescriptor returned by open(fileName, O_RDONLY, 0); struct forexDataSet *createForexDataSetFromFile(int fileDescriptor) {   char     buf[39000];   ssize_t nBytesRead;    struct forexDataSet *set = calloc(1, sizeof(struct forexDataSet));   set->data = malloc(INITIAL_SET_SIZE * sizeof(struct forexData *));    if (!set->data) {     return NULL;   }    set->capacity = INITIAL_SET_SIZE;    do {     nBytesRead = read(fileDescriptor, buf, sizeof(buf));      size_t lineStart;     for (lineStart = 0; lineStart < nBytesRead; lineStart += 39) {       struct forexData *tmp = createForexDataFromString(buf + lineStart);         if (!tmp) {         freeForexDataSet(set);         return NULL;       } else {         if (set->cardinality == set->capacity) {           struct forexData **increasedData = realloc(set->data, sizeof(struct forexData *) * (set->capacity + SET_INCREASE_STEP));            if (increasedData == NULL) {             freeForexDataSet(set);             free(tmp);             return NULL;           }            set->data = increasedData;           set->capacity += SET_INCREASE_STEP;         }          set->data[set->cardinality++] = tmp;       }     }    } while(nBytesRead == sizeof(buf));    return set; }  struct forexData *createForexDataFromString(const char *str) {   struct forexData *tmp = calloc(1, sizeof(struct forexData));    if (!tmp) {     return NULL;   }    strptime(str, "%Y%m%d %H%M%S", &tmp->timestamp);    tmp->bidQuote = (uint32_t) (atof(str + 19) * 1000000);   tmp->askQuote = (uint32_t) (atof(str + 28) * 1000000);    return tmp; }  void freeForexDataSet(struct forexDataSet *set) {   size_t dataIndex;    for (dataIndex = 0; dataIndex < set->cardinality; dataIndex++) {     free(set->data[dataIndex]);   }    free(set->data);   free(set); } 

The code compile with clang flags -Weverything -Wextra without warnings except one warning (comparing size_t to ssize_t in a for-loop).

What I'd like to get reviewed / brainstormed:

  • Performance. Currently the process takes around 7 seconds to complete and I was wondering if there is anything major I did wrong.
  • Buffer size. 39000 (exactly 1000 lines, since one line contains 39 characters). I only read in whole lines at a time, otherwise I'd had a problem with overlapping lines and parsing, but I chose 39000 without a specific reason. I once even tried to allocate a buffer sized as big as the file and it worked pretty well (I have 16GB of RAM), but I would prefer to not suck up all the RAM while parsing.
  • Hard-Coded-Pieces. As you probably already saw a lot of this is hard coded atof(str + 19), 39000 bytes buffer size, strptime(str, "%Y%m%d %H%M%S", &tmp->timestamp); and I was wondering how to improve these without to implement a fairly complex formatting string parser. Maybe there is a clever way to determine the buffer size of something like this.
  • Error handling and freeing. Is everything freed correctly, when something could not get allocated, etc.
           
 
 

Lista de respuestas

6
 
vote
vote
La mejor respuesta
 

No hay necesidad de RealLOC

Lo primero que noté es que su programa tiene una estrategia de reasignación ineficiente. En un archivo con 20 millones de entradas, su programa deberá reasignarse 10 veces. Pero ni siquiera necesitas reasignar en absoluto. Ya que sabe que el archivo contiene entradas de longitud 39, todo lo que necesita hacer es medir el tamaño del archivo y dividir en 39 para determinar el número de entradas. Aquí está el código que solía hacerlo:

  #define INPUT_LINE_LEN    39      // In createForexDataSetFromFile()     off_t    fsize;     ssize_t  numEntries;      fsize      = lseek(fileDescriptor, 0, SEEK_END);     numEntries = (fsize + INPUT_LINE_LEN - 1) / INPUT_LINE_LEN;     lseek(fileDescriptor, 0, SEEK_SET);   

Este cambio no terminó acelerando el programa, sino que simplificó las cosas eliminando toda la parte de reasignación del código. También redujo el uso total de la memoria de alrededor de 1.7 GB a aproximadamente 1,3 GB para un archivo de entrada de 20 millones de líneas.

Una matriz de estructuras

En este momento, asigne una matriz de punteros y luego asignar una estructura de datos por línea en el archivo. Usted termina llamando malloc 20 millones de veces más de lo que necesita. En lugar de una matriz de punteros, debe asignar una matriz de estructuras. El código se vería así:

  struct forexDataSet {     struct forexData *data;     uint32_t numEntries; };  // fileDescriptor returned by open(fileName, O_RDONLY, 0); struct forexDataSet *createForexDataSetFromFile(int fileDescriptor) {     char     buf[INPUT_LINE_LEN * 1000];     ssize_t nBytesRead;     off_t    fsize;     ssize_t  numEntries;     ssize_t  entryNum = 0;      fsize      = lseek(fileDescriptor, 0, SEEK_END);     numEntries = (fsize + INPUT_LINE_LEN - 1) / INPUT_LINE_LEN;     lseek(fileDescriptor, 0, SEEK_SET);      struct forexDataSet *set = calloc(1, sizeof(struct forexDataSet));     set->data = malloc(numEntries * sizeof(struct forexData));      if (!set->data) {         free(set);         return NULL;     }      set->numEntries = numEntries;     do {         nBytesRead = read(fileDescriptor, buf, sizeof(buf));          size_t lineStart;         for (lineStart = 0; lineStart < nBytesRead; lineStart += 39) {             createForexDataFromString(buf + lineStart, &set->data[entryNum++]);         }     } while(nBytesRead == sizeof(buf));     return set; }  void createForexDataFromString(const char *str, struct forexData *tmp) {     strptime(str, "%Y%m%d %H%M%S", &tmp->timestamp);      tmp->bidQuote = (uint32_t) (atof(str + 19) * 1000000);     tmp->askQuote = (uint32_t) (atof(str + 28) * 1000000); }   

Este cambio aceleró el programa de 21 segundos a 17.2 segundos en mi caso de prueba. También redujo el uso de la memoria de 1,3 GB a 1,0 GB.

Tiempo de lectura personalizado

Una de las partes más lentas del programa es la llamada a strptime() . Dado que el tiempo está en un formato fijo, podría hacerlo mejor escribiendo su propio analizador de tiempo personalizado, como este:

  static inline int getInt4(const char *str) {     return (str[0] - '0') * 1000 + (str[1] - '0') * 100 +            (str[2] - '0') * 10 + (str[3] - '0'); }  static inline int getInt2(const char *str) {     return (str[0] - '0') * 10 + (str[1] - '0'); }  void createForexDataFromString(const char *str, struct forexData *tmp) {     tmp->timestamp.tm_year = getInt4(str) - 1900;     tmp->timestamp.tm_mon  = getInt2(str+4) - 1;     tmp->timestamp.tm_mday = getInt2(str+6);     tmp->timestamp.tm_hour = getInt2(str+9);     tmp->timestamp.tm_min  = getInt2(str+11);     tmp->timestamp.tm_sec  = getInt2(str+13);      tmp->bidQuote = (uint32_t) (atof(str + 19) * 1000000);     tmp->askQuote = (uint32_t) (atof(str + 28) * 1000000); }   

Este cambio aceleró el programa desde 17.2 segundos a 7.8 seg.

Formato de tiempo más compacto

No estoy seguro de si necesita el tiempo en formato 9988776665544335 , pero ese formato desperdicia mucho espacio. Se define como una estructura que contiene 9 campos INT, que normalmente va a ser de 36 bytes. Multiplique que por 20 millones de entradas y usted está utilizando 720 MB solo para mantener las marcas de tiempo. Cambié la marca de tiempo a una estructura personalizada que usa solo 8 bytes, como este:

  struct forexTimestamp {     uint8_t sec;     uint8_t min;     uint8_t hour;     uint8_t day;     uint8_t mon;     uint16_t year; };  struct forexData {     struct forexTimestamp timestamp;      uint32_t bidQuote;     uint32_t askQuote; };   

Este cambio solo aceleró el programa de 7,8 a 7,6 segundos, pero redujo el uso de la memoria de más de 1 GB a aproximadamente 320 MB.

Lectura personalizada FLOAT

La otra parte lenta del programa está leyendo el flotador y conversión a un entero. También puede hacer su propia función de lectura de flotación personalizada también:

  // Assumes float is in the format X.XXXXXX and returns the float // multiplied by 1000000. static inline uint32_t getFloat_1_6(const char *str) {     return (str[0] - '0') * 1000000 + (str[2] - '0') * 100000 +            (str[3] - '0') * 10000   + (str[4] - '0') * 1000   +            (str[4] - '0') * 100     + (str[5] - '0') * 10    +            (str[6] - '0'); }  void createForexDataFromString(const char *str, struct forexData *tmp) {     tmp->timestamp.year = getInt4(str);     tmp->timestamp.mon  = getInt2(str+4);     tmp->timestamp.day  = getInt2(str+6);     tmp->timestamp.hour = getInt2(str+9);     tmp->timestamp.min  = getInt2(str+11);     tmp->timestamp.sec  = getInt2(str+13);      tmp->bidQuote = getFloat_1_6(str + 19);     tmp->askQuote = getFloat_1_6(str + 28); }   

Este cambio redujo el tiempo de ejecución del programa de 7.6 segundos a 0.79 seg.

Resumen

En total, el tiempo de ejecución del programa se redujo de 21 segundos a 0,79 segundos, o 26.6 veces más rápido que el original. El uso de la memoria también se redujo de 1.7 GB a 320 MB. Las ganancias más grandes fueron de las posiciones personalizadas de dos partes de la línea de entrada. El programa final fue esto:

  #include <time.h> #include <stdio.h> #include <fcntl.h> #include <string.h> #include <unistd.h> #include <stdint.h> #include <stdlib.h>  struct forexTimestamp {     uint8_t sec;     uint8_t min;     uint8_t hour;     uint8_t day;     uint8_t mon;     uint16_t year; };  struct forexData {     struct forexTimestamp timestamp;      uint32_t bidQuote;     uint32_t askQuote; };  struct forexDataSet {     struct forexData *data;     uint32_t numEntries; };  #define INPUT_LINE_LEN    39  static void createForexDataFromString(const char *str, struct forexData *tmp);  // fileDescriptor returned by open(fileName, O_RDONLY, 0); struct forexDataSet *createForexDataSetFromFile(int fileDescriptor) {     char     buf[INPUT_LINE_LEN * 1000];     ssize_t  nBytesRead;     off_t    fsize;     ssize_t  numEntries;     ssize_t  entryNum = 0;      fsize      = lseek(fileDescriptor, 0, SEEK_END);     numEntries = (fsize + INPUT_LINE_LEN - 1) / INPUT_LINE_LEN;     lseek(fileDescriptor, 0, SEEK_SET);      struct forexDataSet *set = calloc(1, sizeof(struct forexDataSet));     set->data = malloc(numEntries * sizeof(struct forexData));     set->numEntries = numEntries;      if (!set->data) {         free(set);         return NULL;     }      do {         nBytesRead = read(fileDescriptor, buf, sizeof(buf));          size_t lineStart;         for (lineStart = 0; lineStart < nBytesRead; lineStart += 39)             createForexDataFromString(buf + lineStart, &set->data[entryNum++]);     } while(nBytesRead == sizeof(buf));      return set; }  static inline int getInt4(const char *str) {     return (str[0] - '0') * 1000 + (str[1] - '0') * 100 +            (str[2] - '0') * 10   + (str[3] - '0'); }  static inline int getInt2(const char *str) {     return (str[0] - '0') * 10 + (str[1] - '0'); }  // Assumes float is in the format X.XXXXXX and returns the float // multiplied by 1000000.    static inline uint32_t getFloat_1_6(const char *str) {     return (str[0] - '0') * 1000000 + (str[2] - '0') * 100000 +            (str[3] - '0') * 10000   + (str[4] - '0') * 1000   +            (str[4] - '0') * 100     + (str[5] - '0') * 10    +            (str[6] - '0'); }  static void createForexDataFromString(const char *str, struct forexData *tmp) {     tmp->timestamp.year = getInt4(str);     tmp->timestamp.mon  = getInt2(str+4);     tmp->timestamp.day  = getInt2(str+6);     tmp->timestamp.hour = getInt2(str+9);     tmp->timestamp.min  = getInt2(str+11);     tmp->timestamp.sec  = getInt2(str+13);      tmp->bidQuote = getFloat_1_6(str + 19);     tmp->askQuote = getFloat_1_6(str + 28); }  void freeForexDataSet(struct forexDataSet *set) {     free(set->data);     free(set); }  int main(int argc, char *argv[]) {     struct forexDataSet *set;     int fd;      if (argc < 2) {         return 0;     }     fd = open(argv[1], O_RDONLY, 0);     set = createForexDataSetFromFile(fd);     return 0; }   
 

No need for realloc

The first thing I noticed is that your program has an inefficient reallocation strategy. On a file with 20 million entries, your program will need to reallocate 10 times. But you don't even need to reallocate at all. Since you know that the file contains entries of length 39, all you need to do is measure the file size and divide by 39 to determine the number of entries. Here is the code I used to do it:

#define INPUT_LINE_LEN    39      // In createForexDataSetFromFile()     off_t    fsize;     ssize_t  numEntries;      fsize      = lseek(fileDescriptor, 0, SEEK_END);     numEntries = (fsize + INPUT_LINE_LEN - 1) / INPUT_LINE_LEN;     lseek(fileDescriptor, 0, SEEK_SET); 

This change didn't end up speeding up the program, but it did simplify things by removing the whole reallocation portion of the code. It also reduced the total memory usage from around 1.7 GB to about 1.3 GB for a 20 million line input file.

One array of structs

Right now, you allocate an array of pointers, and then allocate one data structure per line in the file. You end up calling malloc 20 million times more than you need to. Instead of an array of pointers, you should just allocate one array of structs. The code would look like this:

struct forexDataSet {     struct forexData *data;     uint32_t numEntries; };  // fileDescriptor returned by open(fileName, O_RDONLY, 0); struct forexDataSet *createForexDataSetFromFile(int fileDescriptor) {     char     buf[INPUT_LINE_LEN * 1000];     ssize_t nBytesRead;     off_t    fsize;     ssize_t  numEntries;     ssize_t  entryNum = 0;      fsize      = lseek(fileDescriptor, 0, SEEK_END);     numEntries = (fsize + INPUT_LINE_LEN - 1) / INPUT_LINE_LEN;     lseek(fileDescriptor, 0, SEEK_SET);      struct forexDataSet *set = calloc(1, sizeof(struct forexDataSet));     set->data = malloc(numEntries * sizeof(struct forexData));      if (!set->data) {         free(set);         return NULL;     }      set->numEntries = numEntries;     do {         nBytesRead = read(fileDescriptor, buf, sizeof(buf));          size_t lineStart;         for (lineStart = 0; lineStart < nBytesRead; lineStart += 39) {             createForexDataFromString(buf + lineStart, &set->data[entryNum++]);         }     } while(nBytesRead == sizeof(buf));     return set; }  void createForexDataFromString(const char *str, struct forexData *tmp) {     strptime(str, "%Y%m%d %H%M%S", &tmp->timestamp);      tmp->bidQuote = (uint32_t) (atof(str + 19) * 1000000);     tmp->askQuote = (uint32_t) (atof(str + 28) * 1000000); } 

This change sped up the program from 21 sec to 17.2 sec on my test case. It also reduced the memory usage from 1.3 GB to 1.0 GB.

Custom read time

One of the slower parts of the program is the call to strptime(). Since the time is in a fixed format, you could do better by writing your own custom time parser, like this:

static inline int getInt4(const char *str) {     return (str[0] - '0') * 1000 + (str[1] - '0') * 100 +            (str[2] - '0') * 10 + (str[3] - '0'); }  static inline int getInt2(const char *str) {     return (str[0] - '0') * 10 + (str[1] - '0'); }  void createForexDataFromString(const char *str, struct forexData *tmp) {     tmp->timestamp.tm_year = getInt4(str) - 1900;     tmp->timestamp.tm_mon  = getInt2(str+4) - 1;     tmp->timestamp.tm_mday = getInt2(str+6);     tmp->timestamp.tm_hour = getInt2(str+9);     tmp->timestamp.tm_min  = getInt2(str+11);     tmp->timestamp.tm_sec  = getInt2(str+13);      tmp->bidQuote = (uint32_t) (atof(str + 19) * 1000000);     tmp->askQuote = (uint32_t) (atof(str + 28) * 1000000); } 

This change sped up the program from 17.2 sec to 7.8 sec.

More compact time format

I'm not sure if you need the time in a struct tm format, but that format wastes a lot of space. It is defined as a struct containing 9 int fields, which is typically going to be 36 bytes. Multiply that by 20 million entries and you are using 720 MB just to hold the timestamps. I changed the timestamp to a custom struct which uses only 8 bytes, like this:

struct forexTimestamp {     uint8_t sec;     uint8_t min;     uint8_t hour;     uint8_t day;     uint8_t mon;     uint16_t year; };  struct forexData {     struct forexTimestamp timestamp;      uint32_t bidQuote;     uint32_t askQuote; }; 

This change only sped up the program from 7.8 to 7.6 sec, but it reduced the memory usage from over 1 GB to about 320 MB.

Custom read float

The other slow part of the program is reading the float and converting to an integer. You can make your own custom float reading function as well:

// Assumes float is in the format X.XXXXXX and returns the float // multiplied by 1000000. static inline uint32_t getFloat_1_6(const char *str) {     return (str[0] - '0') * 1000000 + (str[2] - '0') * 100000 +            (str[3] - '0') * 10000   + (str[4] - '0') * 1000   +            (str[4] - '0') * 100     + (str[5] - '0') * 10    +            (str[6] - '0'); }  void createForexDataFromString(const char *str, struct forexData *tmp) {     tmp->timestamp.year = getInt4(str);     tmp->timestamp.mon  = getInt2(str+4);     tmp->timestamp.day  = getInt2(str+6);     tmp->timestamp.hour = getInt2(str+9);     tmp->timestamp.min  = getInt2(str+11);     tmp->timestamp.sec  = getInt2(str+13);      tmp->bidQuote = getFloat_1_6(str + 19);     tmp->askQuote = getFloat_1_6(str + 28); } 

This change reduced the runtime of the program from 7.6 sec to 0.79 sec.

Summary

In total, the program runtime was reduced from 21 sec to 0.79 sec, or 26.6 times faster than the original. The memory usage was also decreased from 1.7 GB to 320 MB. The biggest gains were from the custom parsings of two parts of the input line. The final program was this:

#include <time.h> #include <stdio.h> #include <fcntl.h> #include <string.h> #include <unistd.h> #include <stdint.h> #include <stdlib.h>  struct forexTimestamp {     uint8_t sec;     uint8_t min;     uint8_t hour;     uint8_t day;     uint8_t mon;     uint16_t year; };  struct forexData {     struct forexTimestamp timestamp;      uint32_t bidQuote;     uint32_t askQuote; };  struct forexDataSet {     struct forexData *data;     uint32_t numEntries; };  #define INPUT_LINE_LEN    39  static void createForexDataFromString(const char *str, struct forexData *tmp);  // fileDescriptor returned by open(fileName, O_RDONLY, 0); struct forexDataSet *createForexDataSetFromFile(int fileDescriptor) {     char     buf[INPUT_LINE_LEN * 1000];     ssize_t  nBytesRead;     off_t    fsize;     ssize_t  numEntries;     ssize_t  entryNum = 0;      fsize      = lseek(fileDescriptor, 0, SEEK_END);     numEntries = (fsize + INPUT_LINE_LEN - 1) / INPUT_LINE_LEN;     lseek(fileDescriptor, 0, SEEK_SET);      struct forexDataSet *set = calloc(1, sizeof(struct forexDataSet));     set->data = malloc(numEntries * sizeof(struct forexData));     set->numEntries = numEntries;      if (!set->data) {         free(set);         return NULL;     }      do {         nBytesRead = read(fileDescriptor, buf, sizeof(buf));          size_t lineStart;         for (lineStart = 0; lineStart < nBytesRead; lineStart += 39)             createForexDataFromString(buf + lineStart, &set->data[entryNum++]);     } while(nBytesRead == sizeof(buf));      return set; }  static inline int getInt4(const char *str) {     return (str[0] - '0') * 1000 + (str[1] - '0') * 100 +            (str[2] - '0') * 10   + (str[3] - '0'); }  static inline int getInt2(const char *str) {     return (str[0] - '0') * 10 + (str[1] - '0'); }  // Assumes float is in the format X.XXXXXX and returns the float // multiplied by 1000000.    static inline uint32_t getFloat_1_6(const char *str) {     return (str[0] - '0') * 1000000 + (str[2] - '0') * 100000 +            (str[3] - '0') * 10000   + (str[4] - '0') * 1000   +            (str[4] - '0') * 100     + (str[5] - '0') * 10    +            (str[6] - '0'); }  static void createForexDataFromString(const char *str, struct forexData *tmp) {     tmp->timestamp.year = getInt4(str);     tmp->timestamp.mon  = getInt2(str+4);     tmp->timestamp.day  = getInt2(str+6);     tmp->timestamp.hour = getInt2(str+9);     tmp->timestamp.min  = getInt2(str+11);     tmp->timestamp.sec  = getInt2(str+13);      tmp->bidQuote = getFloat_1_6(str + 19);     tmp->askQuote = getFloat_1_6(str + 28); }  void freeForexDataSet(struct forexDataSet *set) {     free(set->data);     free(set); }  int main(int argc, char *argv[]) {     struct forexDataSet *set;     int fd;      if (argc < 2) {         return 0;     }     fd = open(argv[1], O_RDONLY, 0);     set = createForexDataSetFromFile(fd);     return 0; } 
 
 
     
     
2
 
vote

Consideraciones:

Si el formato de archivo como se indica, ocupa 800 MB en el disco, ocupará menos memoria cuando se cargará en forma binaria si la estructura es una, dos o de cuatro bytes) envasadas (alineadas). Si la alineación es superior a las cuatro bytes, el rendimiento de acceso debido a las características del autobús puede mejorar al costo del aumento de la ocupación de la memoria. Sería mejor probar varias alineaciones diferentes para medir la diferencia.

Debe considerar la carga en una instancia de SQLite. SQLite admite índices, y probablemente debería estar indexando por la marca de tiempo al menos.

Finalmente, podría disminuir el tiempo de carga a través de un enfoque multi-roscado. Difundir las compensaciones de manera uniforme a lo largo del archivo, y cargar desde esas compensaciones. Necesitará un mango de archivo por hilo.

 

Considerations:

If the file format as stated occupies 800MB on disc, it will occupy less memory when loaded in binary form IF the structure is one, two, or four-byte-packed (aligned). If the alignment is higher than four bytes, access performance due to bus characteristics might improve at the cost of increased memory occupation. You would be best to try several different alignments to measure the difference.

You should consider loading into an SQLite instance. SQLite supports indexes, and you should probably be indexing by the timestamp at the least.

Finally, you could decrease the load time through a multi-threaded approach. Spread offsets evenly throughout the file, and load from those offsets. You'll need one file handle per thread.

 
 

Relacionados problema

1  Buscando y reemplazando el texto  ( Searching and replacing text ) 
Modificador de listas de reproducción simple Este programa es un programa de búsqueda y reemplazo para archivos basados ​​en texto. El objetivo principal ...

2  Implementación de SCPI para el control de instrumentos de prueba  ( Implementation of scpi for control of test instruments ) 
trabajo con equipos de prueba electrónicos. Me gusta poder automatizar las pruebas utilizando sus interfaces de control remoto. He construido un patrón, algun...

2  Lectura de datos de Red Stream  ( Reading data from network stream ) 
Tengo una función para solicitar una actualización del servidor. Proporciono la consulta y también indica la longitud esperada de la respuesta. public ...

3  Leyendo un archivo XML grande y analizando los elementos necesarios en MySQLDB  ( Reading a large xml file and parsing necessary elements into mysqldb ) 
Tengo concepto justo en una programación (aprendiz), pero no un código experto para refactorarse al más alto nivel. Estoy tratando de leer un archivo XML enor...

2  Saltando espacios en blanco al leer el archivo  ( Skipping whitespaces when reading file ) 
En la lectura de un archivo, encontré líneas en blanco (incluso en la parte inferior) se bloquean el programa. Para solucionar esto, he agregado el siguiente ...

5  Convertir una mezcla de Latín 1 y UTF-8 a UTF-8 adecuado  ( Convert a mix of latin 1 and utf 8 to proper utf 8 ) 
El siguiente programa toma una secuencia byte arbitraria como entrada y salidas UTF-8 bien formadas. Todas las secuencias UTF-8 de la entrada se copian sin mo...

8  Contando novedades en un archivo  ( Counting newlines in a file ) 
He comenzado a pasar por tutoriales de nodos en nodoschool.io , y la segunda asignación fue escribir un programa que contará El número de nuevas líneas en un...

11  Escáner en Scala  ( Scanner in scala ) 
que quería implementar 'código> 99887766555443333 / Código> en Scala. El objetivo de esta clase es: implementar una interfaz de colección SCALA (probable...

7  Programa de Dados-Rolling  ( Dice rolling program ) 
Soy algo nuevo en la programación. Me gustaría comprender las áreas de código que son basura y podrían estar mejor escritas para mantener el comportamiento ex...

6  Bloqueo y comunicación UDP de ASYNC con una cámara IR  ( Blocking and async udp communication with an ir camera ) 
Estoy escribiendo una aplicación Java para controlar una cámara de investigación IR personalizada que se instalará en un avión. La cámara se conecta a la comp...




© 2022 respuesta.top Reservados todos los derechos. Centro de preguntas y respuestas reservados todos los derechos