This commit is contained in:
bronku 2025-11-23 20:58:43 +01:00
parent 722c085817
commit fddaf6b9d0
6 changed files with 158 additions and 78 deletions

View file

@ -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
<main> -g -N 100000 -b 10 -n 101 -o <filename>
// Sort the input file, using the temporary dir, and the parameters specified, save to the output file
<main> -i <input-file> -o <output-file> -d <tmp dir> -N 100000 -b 10 -n 101
// If no option is specified, system will prompt for parameters, and use the input data from stdin
<main>
```
- 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

View file

@ -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

156
report/report.md Normal file
View file

@ -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

BIN
report/report.pdf Normal file

Binary file not shown.

BIN
report/results_plot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

View file

@ -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'