2010-10-05 16 views
6

Mam program, który łączy pętlę zagnieżdżania (link text). Zasadniczo to, co robi, czyta zawartość z pliku (powiedzmy plik 10GB) do bufora1 (powiedzmy 400 MB), umieszcza go w tabeli mieszającej. Teraz odczytaj zawartość drugiego pliku (powiedzmy plik 10GB) do bufora 2 (powiedzmy 100 MB) i zobacz, czy elementy w buforze2 są obecne w mieszaniu. Wyprowadzanie wyniku nie ma znaczenia. Po prostu interesuje mnie teraz wydajność programu. W tym programie muszę czytać 8 bajtów na raz z obu plików, więc używam długiego int. Problem polega na tym, że mój program jest bardzo nieefektywny. Jak mogę sprawić, by był wydajny?Dlaczego mój program jest wolny? Jak mogę poprawić jego efektywność?

// skompilować za pomocą 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); 
} 

Aktualizacja:

Czy można bezpośrednio odczytać fragment (powiedzmy 400 MB) z pliku i bezpośrednio umieścić go w tabeli mieszania za pomocą C++ czytać strumień? Myślę, że może to jeszcze bardziej zmniejszyć obciążenie ogólne.

+0

jak szybko powinien wyglądać program rozdrabniający 20G danych? – Timothy

+3

Nie żartowałeś, gdy oznaczyłeś to jako C i C++. – Kevin

+0

Kup szybszy dysk. Dyski SSD są miłe. –

Odpowiedz

2

Czas działania dla programu oznacza (x bs xl x bs) (gdzie L to liczba linii w pierwszym pliku i bs jest rozmiarem bloku do pierwszego bufora, a L to liczba linii w drugiej pliku i bs jest rozmiarem bloku do drugiego bufora), ponieważ trzeba cztery zagnieżdżonej pętli. Ponieważ twoje rozmiary bloków są stałe, możesz powiedzieć, że twoje zamówienie to O (nx 400 xmx 400) lub O (1600mn), lub w najgorszym przypadku O (1600n), który w zasadzie kończy się jako O (n ).

można mieć O (n) algorytm, jeśli coś jak to (Pseudokod poniżej):

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 

Teraz duplicate będzie zawierać listę duplikatów i unique będzie zawierać listę unikalnych przedmiotów.

W oryginalnym algorytmie niepotrzebnie przechodzisz przez drugi plik dla każdej linii w pierwszym pliku. Tak naprawdę kończy się utratą korzyści z hasha (który daje O (1) czas wyszukiwania). Kompromisem w tym przypadku jest oczywiście to, że musisz przechowywać całe 10 GB w pamięci, co prawdopodobnie nie jest pomocne. Zwykle w takich przypadkach kompromis jest między czasem wykonania a pamięcią.

Prawdopodobnie jest lepszy sposób na zrobienie tego. Muszę o tym jeszcze pomyśleć. Jeśli nie, jestem pewien, że ktoś wymyśli lepszy pomysł :).

UPDATE

Prawdopodobnie można zmniejszyć zużycie pamięci-jeśli można znaleźć dobrą drogę do Hash linię (zapoznanie z pierwszego pliku), aby uzyskać unikalną wartość (tj, mapowanie 1-do-1 między linią a wartością skrótu). Zasadniczo byłoby zrobić coś takiego:

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 

Tutaj funkcja hash jest ten, który wykonuje hashowania. W ten sposób nie musisz przechowywać wszystkich linii w pamięci. Musisz tylko przechowywać ich wartości mieszane. To może ci trochę pomóc. Nawet w najgorszym przypadku (gdy porównywane są dwa pliki, które są identyczne lub zupełnie inne), nadal można znaleźć w pamięci 10Gb dla listy duplicate lub unique. Możesz ominąć to z utratą pewnych informacji, jeśli po prostu przechowujesz liczbę unikatowych lub zduplikowanych przedmiotów zamiast samych przedmiotów.

+0

Dostaję twój punkt, ale wydaje się, że bardzo mało wydajna pamięć. –

+0

@Sunil yup, jest (chyba że przechowujesz zakodowane wartości, w którym to przypadku możesz zmniejszyć koszty pamięci). Jak już wspomniałem, to zwykle kompromis. Prędkość a pamięć. W twoim rozwiązaniu używasz bardzo mało pamięci kosztem prędkości. W moim (oryginalnym) rozwiązaniu moje środowisko uruchomieniowe jest niska, ale z większym wykorzystaniem pamięci. W przypadku dużych zestawów danych pętle zagnieżdżone mają zwykle bardzo wysoką wydajność. –

0

Jedynym sposobem, aby się dowiedzieć, jest profilowanie go, np. Z gprof. Utwórz benchmark aktualnej implementacji, a następnie eksperymentuj z innymi modyfikacjami metodycznie i ponownie uruchom test porównawczy.

1

long long int *ptr = mmap() Twoje pliki, a następnie porównaj je z memcmp() w porcjach. Po stwierdzeniu rozbieżności cofnij się o jedną porcję i porównaj je bardziej szczegółowo. (Więcej szczegółów oznacza długotrwałą int w tym przypadku.)

Jeśli spodziewasz się znaleźć rozbieżności często, nie przejmuj się memcmp(), po prostu napisz własną pętlę porównując długie długie ints do siebie.

0

Założę się, że jeśli czytasz większe fragmenty, uzyskasz lepszą wydajność. fread() i przetwarzaj wiele bloków na przejście.

+0

Oczywiście, ale chcę użyć tylko 8 bajtów. Czy nie byłoby szybciej, jeśli użyję ifstream() zamiast fread()? Główną kwestią, którą próbuję wykonać, są moje funkcje odczytu, a funkcje map są bardzo powolne i będę wdzięczny za sugestie, które można ulepszyć. Dzięki –

+0

Jeśli wywołasz fread mniej razy, to usuniesz obciążenie związane z ustawianiem i odrywaniem każdego z nich. zadzwoń do ciebie. Ponieważ robisz to wiele razy, będzie to miało znaczący wpływ. 10 gb/8 bajtów = usunięto obciążenie związane z 1,25 miliarda połączeń. – Jay

0

Problemem jest to, że czytasz drugi plik n-razy. Bardzo wolno.

Najlepszym sposobem na przyspieszenie jest wcześniejsze sortowanie plików, a następnie wykonanie Sort-merge join. Ten rodzaj prawie zawsze jest tego wart, z mojego doświadczenia.

+0

Wiem, ale to jest cały punkt algorytmu Block Nested Loop Join. –

+0

Domyślam się, że mówię, że nie należy używać sprzężenia Block Loop, chyba że nie możesz tego zrobić w żaden inny sposób. Łączenie zagnieżdżone jest algorytmem ostatniego typu. Nic nie wiem o twoich danych, ale zazwyczaj istnieje sposób sortowania danych, abyś mógł użyć rozsądniejszego algorytmu łączenia. –

+0

Widzę, o czym mówisz. Problem polega nie na znalezieniu innego wydajnego algorytmu, ale na użyciu funkcji Block Nested Loop Join i prawidłowego zakodowania tego programu, aby działał on wydajnie. –

3

Jeśli używasz fread, spróbuj użyć setvbuf(). Domyślne bufory używane przez standardowe wywołania we/wy pliku lib są małe (często rzędu 4kB). Przetwarzając duże ilości danych szybko, będziesz związany z I/O i narzut pobierania wielu małych buforów danych może stać się znaczącym wąskim gardłem. Ustaw to na większy rozmiar (na przykład 64kB lub 256kB) i możesz zmniejszyć to obciążenie, a może zobaczyć znaczące ulepszenia - wypróbuj kilka wartości, aby zobaczyć, gdzie uzyskasz najlepsze zyski, ponieważ uzyskasz malejące zyski.

+0

Wydaje się interesujące. Spróbuję i odesłać wyniki. –

Powiązane problemy