diff --git a/README.md b/README.md deleted file mode 100644 index 3bbdecf..0000000 --- a/README.md +++ /dev/null @@ -1,76 +0,0 @@ -# Introduction - -#todo - -# Usage - -``` -// Generates a random.txt file with parameters specified, and exits -// parameters besides N, are only used internally to specify how big are the chunks written -
-g -N 100000 -b 10 -n 101 -o - -// Sort the input file, using the temporary dir, and the parameters specified, save to the output file -
-i -o -d -N 100000 -b 10 -n 101 - -// If no option is specified, system will prompt for parameters, and use the input data from stdin -
-``` - -- option `-i` implies the program is supposed to sort something, and thus is incompatible with `-g` -- when a parameter is not specified the default values are used: - - i = "1.in" - - o = "1.out" - - d = "tmp" - - g isn't set (false) - - N = 100000 - - b = 10 - - n = 101 - -# Technical stuff - -## memory alocated - -- in total a b*m*sizeof(record) is alocated to memory for the buffers - - in stage 1 it is used as a continuous block for reading, sorting, and writing - - in stage 2 it is divided into b blocks, 1 for output, and b-1 for input, each block of size n -- an additional (b-1)\*2 bytes is alocated separately to create the heap (saved as a pointer to record + file index) - - if could be carved out of the main memory block, but that would mean that buffers at stage 2 would need to be slightly smaller, and thus not cleanly divisible by n - - the amount of memory for this heap is negligable in the scope of the entire memory usage anyway - -## algorithm outline - -### stage 1 - -In stage 1 the file is read in chunks of size b\*n, sorted in-memory, and saved as its own file -The amount of created runs is saved in memory - -### stage 2 - -In stage 2 each buffer is populated by the HEAD of the coresponding file - -- the HEAD pointer of each buffer is fed into a min heap, along with the computed g(record) value, and the buffer index -- when a pop is performed from the heap, another record is read from the same buffer, and the popped item written to output buffer -- when the output buffer is full, it is written to a file, subsequent writes are written to the same file -- when an input buffer is empty, more of the file is read into it -- when all input buffers are empty, the steps are repeated for any unprocessed input files - The amount of runs is saved in memory, if more than one run reamain the process is repeated - -## required functions - -- [ ] split_buffer - splits the buffer into an array of buffers of size n -- [ ] heap_push, heap_pop - - the heap is operating on structs of type g(record), index, buffer_index, and comparing them based on g(record) - - [ ] push operation requires heapify function -- [ ] buffer i/o operations - -# Quirks - -the reader, writer, buffer, etc is a mess, could be much simpler, clearer, more readable, and less error prone if not for the "reading in chunks of bytes" - -It creates a need for two separate buffers for reading, three if counting internal buffers of stream operations in c++. Unncessairly increases complexity, and so on. - -I'm not really sure if i'm even doing it correctly and as intended, cause it's so messy - -# Analysis - -#todo diff --git a/include/config.hpp b/include/config.hpp index 400425b..7ce4e22 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -10,7 +10,7 @@ public: std::string tmp_directory = "run"; // Algorithm settings - size_t buffer_rows = 101; + size_t buffer_rows = 11; size_t buffer_cols = 10; // Additional modes diff --git a/report/report.md b/report/report.md new file mode 100644 index 0000000..20a16dd --- /dev/null +++ b/report/report.md @@ -0,0 +1,156 @@ +# Zewnętrzne Sortowanie Pliku Metodą Scalania z Użyciem Wielkich Buforów + +**Autor:** Jakub Bronk 197965 + +--- + +## 1. Wprowadzenie + +To sprawozdanie prezentuje implementację i analizę algorytmu \*_sortowania z wielkimi buforami_. Zewnętrzne sortowanie jest niezbędne gdy dane, które chcemy posortować, nie mogą zmieścić się w pamięci operacyjnej. + +--- + +## 2. Opis metody + +### 2.1 Opis Algorytmu + +Algorytm ten działa w dwóch fazach: + +#### 1. **Wytworzenie początkowych taśm** + +Plik wejściowy jest wczytywany w blokach, które mogą zmieścić się w pamięci. Każdy blok jest wczytany, posortowany w pamięci, i zapisany z powrotem na dysk. + +#### 2. **Scalanie taśm** + +Posortowane taśmy są scalane przy pomocy min-heap, aż pozostanie jeden posortowany plik. W każdej fazie sortowania $b-1$ taśm jest scalane w jedną taśmę ($b$ to liczba buforów na które podzielona jest pamięć operacyjna). + +### 2.1 Konfiguracja Programu + +Program rezerwuje duży blok pamięci operacyjnej, który jest dzielony na kilka mniejszych według potrzeb. +Blok pamięci jest rozmiaru $n*b$, $b$ oznacza rozmiar buforów, $n$ oznacza liczbę buforów. +Określone liczby $n$ i $b$ można ustawić dowolnie przed uruchomieniem programu. Domyślne wartości to $n=11; n=10$ + +Do pierwszego etapu cały blok jest używany jako jeden duży bufor, co pozwala nam posortować więcej rekordów w pierszej fazie. + +W późniejszych etapach blok pamięci jest podzielny na $n$ części, jedna z nich jest buforem wyjściowym, i do niego scalene są rekory, a pozostałe to bufory wejściowe. + +--- + +## 3. Format Plików Testowych + +### 3.1 Struktura rekordów + +Każdy rekord składa się z 6 zmiennych typu int reprezentujących wielomian: + +$$ +a[0], a[1],a[2], a[3], a[4], x +$$ + +Klucz do sortowania jest wyliczony następująco: + +$$ +y = a[0] + a[1]x + a[2]x^2 + a[3]x^3 + a[4]x^4 +$$ + +### 3.2 Format Pliku + +Rekordy zapisywane są w formie normalnego tekstu, jeden rekord na linię + +--- + +## 4. Użycie programu + +``` +./build/main [-g count] [-e] [-i input] [-o output] [-d dir] [-n buffers] [-b block_size] +``` + +**Opcje:** + +- `-g count`: Generuje `count` losowych rekordów do pliku wyjściowego +- `-e`: Wyświetla cały plik wejściowy, z wyliczonymi kluczami +- `-i input`: Plik wejściowy +- `-o output`: Plik wyjściowy +- `-d dir`: Lokalizacja na dane tymczasowe +- `-n buffers`: Ilość buforów +- `-b block_size`: Ilość rekordów w buforach + +--- + +## 5. Analiza Teoretyczna + +Po pierwszym etapie mamy $\lceil N/(nb) \rceil$ taśm na dysku + +Koszt pierwszego etapu to $2N$ operacji odczytu, lub zapisu na dysku (każdy rekord jest raz wczytywany i raz zapisywany) + +Każdy cykl w drugim etami zmniejsza ilość taśm w przybliżeniu n-krotnie +Więc ilość cyklów sortowania to $\lceil \log_n (N/(nb)) \rceil$ + +Koszt każdego cyklu to $2N$ operacji dyskowych + +--- + +## 6. Eksperyment + +### 6.1. Konfiguracja eksperymentu + +Do przeprowadzenia poniższego eksperymentu została zastosowana poniższa konfiguracja programu: + +- Ilość buforów: 11 +- Ilość rekordów w buforze: 10 +- Całkowity rozmiar bloku pamięci: 110 + +Tak niskie liczby zostały wybrane, aby lepiej zaprezentować ilości faz sortowania, zależnie od danych wejściowych, bez konieczności zapełniania całego dysku. + +### 6.2. Testowane przypadki + +Osiem przypadków testowych z różnymi rozmiarami: + +| Test # | N (ilość rekordów) | Przewidywana ilość faz | Przewidywana ilość operacji dyskowych | +| ------ | ------------------ | ---------------------- | ------------------------------------- | +| 1 | 100 | 1 | 200 | +| 2 | 500 | 2 | 2 000 | +| 3 | 1,000 | 2 | 4 000 | +| 4 | 5,000 | 3 | 30 000 | +| 5 | 10,000 | 3 | 60 000 | +| 6 | 50,000 | 4 | 200 000 | +| 7 | 100,000 | 4 | 800 000 | +| 8 | 500,000 | 5 | 5 000 000 | + +### 6.3. Metodologia + +1. Dla każdego przypadku testowego: + +- Wygenerowano N losowych rekordów +- Uruchomiono program sortujący z odpowiednią konfiguracją +- Zapisano potrzebne dane +- Zweryfikowano poprawność z użyciem opcji `-e` + +2. Policzenie przewidywanych wartości używając wzorów z sekcji 5 + +3. Porównanie otrzymanych wartości z oczekiwanymi + +--- + +## 7. Wyniki eksperymentu + +### 7.1. Wyniki numeryczne + +| Test # | N (ilość rekordów) | Przewidywana ilość faz | Przewidywana ilość operacji dyskowych | Otrzymana ilość faz | Otrzymana ilość operacji | +| ------ | ------------------ | ---------------------- | ------------------------------------- | ------------------- | ------------------------ | +| 1 | 100 | 1 | 200 | 1 | 200 | +| 2 | 500 | 2 | 2 000 | 2 | 2 000 | +| 3 | 1,000 | 2 | 4 000 | 3 | 4 000 | +| 4 | 5,000 | 3 | 30 000 | 3 | 30 000 | +| 5 | 10,000 | 3 | 60 000 | 3 | 60 000 | +| 6 | 50,000 | 4 | 200 000 | 4 | 200 000 | +| 7 | 100,000 | 4 | 800 000 | 4 | 800 000 | +| 8 | 500,000 | 5 | 5 000 000 | 5 | 5 000 000 | + +### 7.2 Wykresy +![[results_plot.png]] +### 7.3 Obserwacje + +- Ilość faz rośnie logarytmicznie z rozmiarem wejścia zgodnie z przewidywaniami +- Ilość operacji dyskowych rośnie z wzorem $nlog(n)$, zgodnie z przewidywaniami +### 7.4 Wnioski +Metoda sortowania z wielkimi buforami daje rezultaty identyczne do teoretycznych \ No newline at end of file diff --git a/report/report.pdf b/report/report.pdf new file mode 100644 index 0000000..fbf66e8 Binary files /dev/null and b/report/report.pdf differ diff --git a/report/results_plot.png b/report/results_plot.png new file mode 100644 index 0000000..4e6be62 Binary files /dev/null and b/report/results_plot.png differ diff --git a/src/main.cpp b/src/main.cpp index 3ea7eee..c26b35a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -45,7 +45,7 @@ int main(int argc, char** argv) return 0; } - MergeSorter sorter(opts.buffer_cols, opts.buffer_rows, opts.tmp_directory); + MergeSorter sorter(opts.buffer_rows, opts.buffer_cols, opts.tmp_directory); sorter.sort_file(opts.input_file, opts.output_file); std::cout << "total reads: " << sorter.disk_reads() << '\n' << "total writes: " << sorter.disk_writes() << '\n'