2010-10-05 10 views
6

Tengo un programa que hace bloque Anidar unido (link text). Básicamente lo que hace es leer contenido de un archivo (digamos un archivo de 10GB) en el buffer1 (digamos 400MB), lo pone en una tabla hash. Ahora lea el contenido del segundo archivo (digamos archivo de 10GB) en el buffer 2 (digamos 100MB) y vea si los elementos en buffer2 están presentes en el hash. La salida del resultado no importa. Solo me preocupa la eficiencia del programa por ahora. En este programa, necesito leer 8 bytes a la vez desde ambos archivos, así que utilizo long int largo. El problema es que mi programa es muy ineficiente. ¿Cómo puedo hacerlo eficiente?¿Por qué mi programa es lento? ¿Cómo puedo mejorar su eficiencia?

// puedo compilar usando g++ -o hash hash.c -std=c++0x

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <unistd.h> 
#include <sys/time.h> 
#include <stdint.h> 
#include <math.h> 
#include <limits.h> 
#include <iostream> 
#include <algorithm> 
#include <vector> 
#include <unordered_map> 
using namespace std; 

typedef std::unordered_map<unsigned long long int, unsigned long long int> Mymap; 
int main() 
{ 

uint64_t block_size1 = (400*1024*1024)/sizeof(long long int); //block size of Table A - division operator used to make the block size 1 mb - refer line 26,27 malloc statements. 
uint64_t block_size2 = (100*1024*1024)/sizeof(long long int); //block size of table B 

int i=0,j=0, k=0; 
uint64_t x,z,l=0; 
unsigned long long int *buffer1 = (unsigned long long int *)malloc(block_size1 * sizeof(long long int)); 
unsigned long long int *buffer2 = (unsigned long long int *)malloc(block_size2 * sizeof(long long int)); 

Mymap c1 ;               // Hash table 
//Mymap::iterator it; 

FILE *file1 = fopen64("10G1.bin","rb"); // Input is a binary file of 10 GB 
FILE *file2 = fopen64("10G2.bin","rb"); 

printf("size of buffer1 : %llu \n", block_size1 * sizeof(long long int)); 
printf("size of buffer2 : %llu \n", block_size2 * sizeof(long long int)); 


while(!feof(file1)) 
     { 
     k++; 
     printf("Iterations completed : %d \n",k); 
     fread(buffer1, sizeof(long long int), block_size1, file1);       // Reading the contents into the memory block from first file 

     for (x=0;x< block_size1;x++) 
      c1.insert(Mymap::value_type(buffer1[x], x));         // inserting values into the hash table 

//  std::cout << "The size of the hash table is" << c1.size() * sizeof(Mymap::value_type) << "\n" << endl; 

/*  // display contents of the hash table 
      for (Mymap::const_iterator it = c1.begin();it != c1.end(); ++it) 
      std::cout << " [" << it->first << ", " << it->second << "]"; 
      std::cout << std::endl; 
*/ 

       while(!feof(file2)) 
       { 
        i++;                 // Counting the number of iterations  
//     printf("%d\n",i); 

        fread(buffer2, sizeof(long long int), block_size2, file2);    // Reading the contents into the memory block from second file 

        for (z=0;z< block_size2;z++) 
         c1.find(buffer2[z]);            // finding the element in hash table 

//      if((c1.find(buffer2[z]) != c1.end()) == true)      //To check the correctness of the code 
//       l++; 
//     printf("The number of elements equal are : %llu\n",l);     // If input files have exactly same contents "l" should print out the block_size2 
//     l=0;      
       } 
       rewind(file2); 
       c1.clear();           //clear the contents of the hash table 
    } 

    free(buffer1); 
    free(buffer2); 
    fclose(file1); 
    fclose(file2); 
} 

Actualización:

¿Es posible leer directamente un trozo (dicen 400 MB) de un archivo y directamente ponen en tabla hash usando C++ lectores de flujo? Creo que eso puede reducir aún más los gastos generales.

+0

¿Qué tan rápido debe ser un programa que resuena a través de 20G de datos? – Timothy

+3

no estabas bromeando cuando etiquetaste esto como C y C++. – Kevin

+0

Compre un disco más rápido. Las SSD son agradables. –

Respuesta

2

El tiempo de ejecución para su programa es (l x ancho xl x ancho) (donde l es el número de líneas en el primer archivo, y BS es el tamaño de bloque para la primera memoria intermedia, y l es el número de líneas en el segundo archivo, y BS es el tamaño de bloque para el segundo buffer) ya que tiene cuatro bucles anidados. Como los tamaños de bloque son constantes, puede decir que su orden es O (nx 400 xmx 400) u O (1600mn), o en el peor de los casos O (1600n) que termina siendo O (n )

Usted puede tener una junta algoritmo (n) si haces algo como esto (pseudocódigo siguiente):

map = new Map(); 
duplicate = new List(); 
unique = new List(); 

for each line in file1 
    map.put(line, true) 
end for 

for each line in file2 
    if(map.get(line)) 
     duplicate.add(line) 
    else 
     unique.add(line) 
    fi 
end for 

ahora duplicate contendrá una lista de elementos duplicados y unique contendrá una lista de artículos únicos.

En su algoritmo original, recorre innecesariamente el segundo archivo para cada línea en el primer archivo. Así que en realidad terminas perdiendo el beneficio del hash (que te da O (1) tiempo de búsqueda). La compensación en este caso, por supuesto, es que tiene que almacenar los 10GB completos en la memoria que probablemente no sea tan útil. Por lo general, en casos como estos, la compensación es entre el tiempo de ejecución y la memoria.

Probablemente haya una mejor manera de hacerlo. Necesito pensarlo un poco más. Si no, estoy bastante seguro de que alguien tendrá una mejor idea :).

ACTUALIZACIÓN

es probable que pueda reducir la memoria de uso si se puede encontrar una buena manera de hash de la línea (que se lee desde el primer archivo) de manera que se obtiene un valor único (es decir,, un mapeo de 1 a 1 entre la línea y el valor hash). Esencialmente, usted haría algo como esto:

for each line in file1 
    map.put(hash(line), true) 
end for 

for each line in file2 
    if(map.get(hash(line))) 
     duplicate.add(line) 
    else 
     unique.add(line) 
    fi 
end for 

Aquí la función hash es la que realiza el hash. De esta forma, no tiene que almacenar todas las líneas en la memoria. Solo debe almacenar sus valores hash. Esto podría ayudarte un poco. Aún así, en el peor de los casos (en el que comparas dos archivos que son idénticos o completamente diferentes), puedes terminar con 10 Gb en memoria para la lista duplicate o . Puede evitarlo con la pérdida de cierta información si simplemente almacena un recuento de artículos únicos o duplicados en lugar de los artículos mismos.

+0

Entiendo tu punto, pero parece que la memoria es muy ineficiente. –

+0

@Sunil yup, lo es (a menos que almacene los valores hash, en cuyo caso puede reducir los costos de memoria). Como mencioné, esa suele ser la compensación. Velocidad vs. memoria. En su solución, utiliza muy poca memoria a expensas de la velocidad. En mi solución (original), mi tiempo de ejecución es bajo pero con mayor uso de memoria. Para grandes conjuntos de datos, los bucles anidados generalmente tienen un tiempo de ejecución muy alto. –

0

La única forma de saberlo es perfilarlo, por ejemplo, con gprof. Cree un punto de referencia de su implementación actual y luego experimente con otras modificaciones metódicamente y vuelva a ejecutar el punto de referencia.

1

long long int *ptr = mmap() sus archivos, luego compárelos con memcmp() en trozos. Una vez que se encuentra una discrepancia, retroceda un trozo y compárelos con más detalle. (Más detalles significa int largo largo en este caso.)

Si espera encontrar discrepancias con frecuencia, no se moleste con memcmp(), simplemente escriba su propio bucle comparando las notas largas largas entre sí.

0

Apuesto a que si lee en trozos más grandes obtendría un mejor rendimiento. fread() y procesa múltiples bloques por pase.

+0

Por supuesto, pero quiero usar solo 8 bytes. ¿No sería más rápido si uso ifstream() en lugar de fread()? El punto principal que estoy tratando de hacer es que mis funciones de lectura y de mapas son muy lentas y agradecería sugerencias para mejorar. Gracias –

+0

Si llama a fread menos veces, entonces quita la sobrecarga de configuración y demora para cada llamarte a quitar Dado que lo estás haciendo MUCHAS veces, tendrá un impacto significativo. 10 gb/8 bytes = la sobrecarga de 1.25 billones de llamadas eliminadas. – Jay

0

El problema que veo es que está leyendo el segundo archivo n veces. Realmente lento.

La mejor manera de hacerlo más rápido es ordenar previamente los archivos y luego hacer un Sort-merge join. El tipo casi siempre vale la pena, en mi experiencia.

+0

Lo sé, pero ese es el objetivo del algoritmo de unión anidada en bloque. –

+0

Supongo que lo que estoy diciendo es que no se debe usar una combinación de bloque anidado en bucle, a menos que no pueda hacerlo de otra manera. La unión Nested Loop es un tipo de algoritmo de último recurso. No sé nada sobre sus datos, pero generalmente hay una forma de ordenar los datos, de modo que puede usar un algoritmo de combinación más razonable. –

+0

Veo de lo que estás hablando. El problema no es encontrar otro algoritmo eficiente, sino usar Block Nested Loop Join y codificar este programa correctamente para que funcione de manera eficiente. –

3

Si está utilizando fread, intente con setvbuf(). Los búferes predeterminados utilizados por las llamadas de E/S del archivo lib estándar son pequeños (a menudo del orden de 4 KB). Al procesar grandes cantidades de datos rápidamente, estará vinculado a E/S y la sobrecarga de recuperar muchos pequeños datos de buffer puede convertirse en un cuello de botella significativo. Establezca esto en un tamaño más grande (por ejemplo, 64kB o 256kB) y puede reducir esa sobrecarga y puede ver mejoras significativas: pruebe algunos valores para ver dónde obtiene las mejores ganancias ya que obtendrá rendimientos decrecientes.

+0

Parece interesante. Trataremos de publicar los resultados. –

Cuestiones relacionadas