Parcourir la source

Copy report to the repo also (no code changes only comments)

master
Christos Choutouridis il y a 10 heures
Parent
révision
9041833c42
8 fichiers modifiés avec 221 ajouts et 5 suppressions
  1. BIN
      homework_1/homework_1_report.pdf
  2. +9
    -0
      homework_1/inc/v0.hpp
  3. +30
    -4
      homework_1/inc/v1.hpp
  4. BIN
      homework_1/out.hdf5
  5. BIN
      homework_1/report/homework_1_report.pdf
  6. +162
    -0
      homework_1/report/homework_1_report.tex
  7. +15
    -0
      homework_1/src/main.cpp
  8. +5
    -1
      homework_1/src/v1.cpp

BIN
homework_1/homework_1_report.pdf Voir le fichier


+ 9
- 0
homework_1/inc/v0.hpp Voir le fichier

@@ -61,6 +61,14 @@ void pdist2(const Matrix& X, const Matrix& Y, Matrix& D2) {
M++;
}

/*!
* Quick select implementation
* \fn void quickselect(std::vector<std::pair<DataType,IndexType>>&, int)
* \tparam DataType
* \tparam IndexType
* \param vec Vector of paire(distance, index) to partially sort over distance
* \param k The number of elements to sort-select
*/
template<typename DataType, typename IndexType>
void quickselect(std::vector<std::pair<DataType, IndexType>>& vec, int k) {
std::nth_element(
@@ -76,6 +84,7 @@ void quickselect(std::vector<std::pair<DataType, IndexType>>& vec, int k) {
/*!
* \param C Is a MxD matrix (Corpus)
* \param Q Is a NxD matrix (Query)
* \param idx_offset The offset of the indexes for output (to match with the actual Corpus indexes)
* \param k The number of nearest neighbors needed
* \param idx Is the Nxk matrix with the k indexes of the C points, that are
* neighbors of the nth point of Q


+ 30
- 4
homework_1/inc/v1.hpp Voir le fichier

@@ -38,6 +38,22 @@ void init_workers();

namespace v1 {


/*!
*
* Merge knnsearch results and select the closest neighbors
*
* \tparam DataType
* \tparam IndexType
* \param N1 Neighbors results from one knnsearch
* \param D1 Distances results from one knnsearcs
* \param N2 Neighbors results from second knnsearch
* \param D2 Distances results from second knnsearch
* \param k How many
* \param m How accurate
* \param N Output for Neighbors
* \param D Output for Distances
*/
template <typename DataType, typename IndexType>
void mergeResultsWithM(mtx::Matrix<IndexType>& N1, mtx::Matrix<DataType>& D1,
mtx::Matrix<IndexType>& N2, mtx::Matrix<DataType>& D2,
@@ -77,6 +93,9 @@ void mergeResultsWithM(mtx::Matrix<IndexType>& N1, mtx::Matrix<DataType>& D1,
}
}

/*!
* The main parallelizable body
*/
template<typename MatrixD, typename MatrixI>
void worker_body (std::vector<MatrixD>& corpus_slices,
std::vector<MatrixD>& query_slices,
@@ -109,6 +128,17 @@ void worker_body (std::vector<MatrixD>& corpus_slices,
}
}

/*!
* \param C Is a MxD matrix (Corpus)
* \param Q Is a NxD matrix (Query)
* \param num_slices How many slices to Corpus-Query
* \param k The number of nearest neighbors needed
* \param m accuracy
* \param idx Is the Nxk matrix with the k indexes of the C points, that are
* neighbors of the nth point of Q
* \param dst Is the Nxk matrix with the k distances to the C points of the nth
* point of Q
*/
template<typename MatrixD, typename MatrixI>
void knnsearch(MatrixD& C, MatrixD& Q, size_t num_slices, size_t k, size_t m, MatrixI& idx, MatrixD& dst) {
using DstType = typename MatrixD::dataType;
@@ -147,15 +177,11 @@ void knnsearch(MatrixD& C, MatrixD& Q, size_t num_slices, size_t k, size_t m, Ma
#if defined OMP
#pragma omp parallel for
for (size_t qi = 0; qi < num_slices; ++qi) {
for (size_t qi = 0; qi < num_slices; ++qi) {
worker_body (corpus_slices, query_slices, idx, dst, qi, num_slices, corpus_slice_size, query_slice_size, k, m);
}
}
#elif defined CILK
cilk_for (size_t qi = 0; qi < num_slices; ++qi) {
for (size_t qi = 0; qi < num_slices; ++qi) {
worker_body (corpus_slices, query_slices, idx, dst, qi, num_slices, corpus_slice_size, query_slice_size, k, m);
}
}
#elif defined PTHREADS
std::vector<std::thread> workers;


BIN
homework_1/out.hdf5 Voir le fichier


BIN
homework_1/report/homework_1_report.pdf Voir le fichier


+ 162
- 0
homework_1/report/homework_1_report.tex Voir le fichier

@@ -46,5 +46,167 @@

\section{Εισαγωγή}

Η παρούσα εργασία αφορά τον παραλληλισμό συστημάτων με κοινή μνήμη.
Το αντικείμενό είναι ο παραλληλισμός τους αλγορίθμου εύρεσης κοντινότερων γειτόνων \textbf{knnseach}.
Στην εργασία αυτή υλοποιούμε τόσο μια σειριακή προσέγγιση όσο και μια που παραλληλοποιείται με \textbf{pthreads}, με \textbf{open-cilk} και με \textbf{open-mp}.
Στην παραλληλοποιήσιμη έκδοση δεν μας ενδιαφέρει η ακρίβεια του αλγορίθμου, κάτι που μας δίνει την δυνατότητα να βελτιώσουμε τον χρόνο εκτέλεσης.


\section{Παραδοτέα}
Τα παραδοτέα της εργασίας αποτελούνται από:
\begin{itemize}
\item Την παρούσα αναφορά.
\item Το \href{https://git.hoo2.net/hoo2/PDS/src/branch/master/homework_1}{σύνδεσμο με το αποθετήριο} που περιέχει τον κώδικα για την παραγωγή των εκτελέσιμων, της αναφοράς και τις μετρήσεις.
\item Τον \href{https://hub.docker.com/r/hoo2/hpcimage}{σύνδεσμο} με το image με το οποίο μεταγλωττίσαμε και εκτελέσαμε τις μετρήσεις.
\end{itemize}

\section {Υλοποίηση}
Πριν ξεκινήσουμε να αναλύουμε τον παραλληλισμό και τις μετρήσεις καλό θα ήταν να αναφερθούμε στην υλοποίηση.
Για την παρούσα εργασία χρησιμοποιήσαμε τη γλώσσα \textbf{C++} και πέρα από τις βιβλιοθήκες που ζητούνται από την εκφώνηση (cilk/omp) έγινε και χρήση της \textbf{openblas}.
Βοηθητικά έγινε επίσης χρήση του \textbf{bash} και της \textbf{matlab} κυρίως για την λήψη και οπτικοποίηση των αποτελεσμάτων.
Στην παρούσα εργασία γίνεται ευρεία χρήση πινάκων τόσο για εισαγωγή και εξαγωγή των δεδομένων, όσο και για τους υπολογισμούς.
Για το λόγο αυτό θεωρήσαμε ότι έπρεπε πρώτα να υλοποιήσουμε κάποιες αφαιρέσεις που θα μας επέτρεπαν να χειριστούμε ευκολότερα το πρόβλημα

\subsection{Ο τύπος Matrix}
Η πιο βασική αφαίρεση που υλοποιήσαμε είναι ο τύπος \textbf{Matrix}.
Πρόκειται για μια αναπαράσταση πινάκων, είτε μονοδιάστατων είτε δισδιάστατων.
Η υλοποίησή του έγινε με χρήση templates και έτσι μπορούμε να κατασκευάζουμε πίνακες οποιουδήποτε τύπου.
Η αφαίρεση εσωτερικά κάνει χρίση μονοδιάστατης αποθήκευσης των δεδομένων με αποτέλεσμα να έχουμε την δυνατότητα να διαλέγουμε εμείς το ordering του πίνακα.
Αν είναι δηλαδή column-major ή row-major.
Στην αφαίρεση δημιουργούμε πίνακες στους οποίους τον έλεγχο της μνήμης τον έχουμε εσωτερικά στο αντικείμενο.
Για την αποθήκευση χρησιμοποιούμε \textbf{std::vector} κάτι που μας δίνει ευελιξία στο μέγεθος αλλά και στη χρήση.
\par
Η κύρια όμως λειτουργικότητα που καθιστά την αφαίρεση βολική για την συγκεκριμένη εργασία είναι η δυνατότητα να κατασκευάσουμε αντικείμενα των οποίων η μνήμη είναι και εξωτερικά του αντικειμένου.
Να λειτουργήσει δηλαδή ο ίδιος ο τύπος ώς viewer, όπως για παράδειγμα το \href{https://en.cppreference.com/w/cpp/header/string_view}{string view}, χωρίς όμως να χρειάζεται να έχουμε διαφορετικό τύπο για αυτή τη δουλειά.
Σε αυτή την περίπτωση στο εσωτερικό storage δεν αποθηκεύουμε παρά μόνο μια διεύθυνση στα δεδομένα του άλλου πίνακα.
Μπορούμε όμως να χρησιμοποιήσουμε όλη την λειτουργικότητα του πίνακα.
Αυτό μας δίνει τη δυνατότητα να δημιουργούμε αντικείμενα που “βλέπουν” σε ολόκληρους ή και σε \textbf{slices }από πίνακες.

Για παράδειγμα μπορούμε να γράψουμε:
\begin{verbatim}
Matrix<double> A(4,5); // Πίνακας 4x5
Matrix<double> vA(A, 0, 2, 5); // View στις 2 πρώτες γραμμές του Α
for (int i=0 ; i<vA.rows() ; ++i)
for (int j=0 ; j<vA.columns() ; ++j)
std::cout << A(i, j);
\end{verbatim}

Στον παραπάνω παράδειγμα ο πίνακας με τα δεδομένα είναι ο \textbf{Α}.
o \textbf{vA}, είναι ένα \textit{slice} του A, που δείχνει μόνο στις 2 πρώτες γραμμές του Α.

\subsection{knnsearch version 0}
Για το πρώτο σκέλος της άσκησης είχαμε να παρουσιάσουμε την knnsearch με σειριακό τρόπο.
Για το σκοπό αυτό υλοποιήσαμε την \textbf{v0::knnsearch}.
Η συνάρτηση αυτή κάνει χρήση του παραπάνω τύπου (Matrix) και επομένως είναι και ή ίδια template.
Η λειτουργία της χωρίζεται σε 3 βασικά μέρη:
\begin{itemize}
\item Τον υπολογισμό του \textbf{πίνακα αποστάσεων}.
Εδώ βρίσκεται ο κύριος υπολογιστικός όγκος της συνάρτησης, λόγω του πολλαπλασιασμού πινάκων που απαιτείται.
Για το λόγο αυτό κάνουμε χρήση της openblas.
\item Την \textbf{αντιστοίχηση των αποστάσεων} των σημείων \textbf{με τους δείκτες (indexes)} αυτών των αποστάσεων στον αρχικό πίνακα Corpus.
Εδώ ουσιαστικά \textit{“ζευγαρώνουμε”} τον κάθε δείκτη με τη απόσταση που του αναλογεί σε μια δομή \textbf{std::pair<>}.
Αυτό το κάνουμε ώστε ταξινομώντας ένα διάνυσμα με τέτοια ζευγάρια ώς προς τη μικρότερη απόσταση, να ταξινομούνται αυτόματα και οι δείκτες.
Γιαυτό το σκοπό κατασκευάζουμε για κάθε σημείο του Query, ένα διάνυσμα τέτοιων ζευγαριών.
\item Την \textbf{μερική ταξινόμηση} του παραπάνω διανύσματος.
Εδώ κάνουμε χρήση του αλγορίθμου quick-select.
Ουσιαστικά ταξινομούμε μόνο ένα κομμάτι του διανύσματος όσα τα στοιχεία που θέλουμε (τον αριθμό των γειτόνων που ψάχνουμε).
Ο αλγόριθμος αυτός είναι γραμμικός, περιορίζοντας έτσι λιγάκι τη “χασούρα”.
Για το σκοπό αυτό χρησιμοποιήσαμε την std::nth\_element().
\end{itemize}
Στην υλοποίησή μας τα Corpus-Query είναι διαφορετικά, κάτι που δεν μας περιορίζει να λύσουμε το πρόβλημα όπου το Corpus == Query φυσικά.

\subsection{knnsearch version 1}
Για το δεύτερο σκέλος της εργασίας είχαμε να παρουσιάσουμε την knnsearch την οποία θα παραλληλοποιήσουμε.
Για το σκοπό αυτό υλοποιήσαμε την \textbf{v1::knnsearch}.
Η αρχική μας προσέγγιση ήταν \textbf{αναδρομική}.
Ουσιαστικά χωρίζαμε τα Corpus-Queries σε slices και για καθένα από αυτά καλούσαμε ξανά τον εαυτό μας.
Η αναδρομή τερματιζόταν όταν τα slices ήταν αρκετά μικρά ώστε να τρέξει ο σειριακός αλγόριθμος.
Για να μην χάνονται όμως γείτονες η knnsearch έπρεπε να τρέχει για όλους τους συνδυασμούς.
Το κάθε Corpus με τα δύο διαφορετικά Queries και το κάθε Query αντίστοιχα.
Έπειτα συνενώναμε τα αποτελέσματα.
Αν και αυτή η προσέγγιση ήταν η ακριβείς μας έδινε όμως τη δυνατότητα να “φτηνύνουμε” τον τρόπο με τον οποίο ενώναμε τα αποτελέσματα.
Επίσης για την περίπτωση που το Queryήταν ίδιο με το Corpus, μας προσφέρεται η δυνατότητα να αποφύγουμε έναν από τους 4 συνδυασμούς.
\par
Η προσέγγιση αυτή, αν και απλή, δεν βολεύει για παραλληλοποίηση, καθώς τα νήματα που πρέπει να δημιουργηθούν βρίσκονται μέσα στην αναδρομή, καθιστώντας δύσχρηστη(όχι αδύνατη) τη χρήση των εργαλείων της cilk και της openMP.
Ακόμα και το “μέρος” στο οποίο θα έμπαινε η loop-α με τις join για την περίπτωση των pthreads ήταν περίπλοκο.
Για το σκοπό αυτό \textbf{“ανοίξαμε” την αναδρομή} σε βρόχο επανάληψης.
Σε αυτή την υλοποίηση τα Corpus-Queries, χωρίζονται σε ένα αριθμό από slices που μας δίνεται από την γραμμή εντολών.
Για όλα αυτά σε ένα διπλό βρόχο καλούμε την σειριακή knnsearch.
Ο διπλός βρόχος ουσιαστικά δημιουργεί όλους τους συνδυασμούς των slice από το Corpus με τα slices από το Query.
Έτσι πάλι δεν χάνουμε γείτονες.
Τα αποτελέσματα συνενώνονται όπου και κρατούνται οι κοντινότεροι γείτονες.
Η διαδικασία επιλογής είναι ίδια με τη σειριακή.
Γίνεται δηλαδή πάλι χρήση της quick-select.
Για να μειώσουμε την ακρίβεια και εδώ μπορούμε να “φτωχύνουμε” το merge.
Για το σκοπό αυτό έχουμε ένα όρισμα στη συνάρτηση το οποίο μας κόβει τον αριθμό των κοντινότερων γειτόνων.

\subsection{knnsearch version 1}

Για την παραγωγή των εκτελέσιμων χρησιμοποιήσαμε το προσωπικό μας(μου) laptop.
Στον κατάλογο με τον πηγαίο κώδικα υπάρχει ότι είναι απαραίτητο για την μεταγλώττιση και εκτέλεση του κώδικα.
Για τη μεταγλώττιση κάναμε χρήση του image hpcimage που βρίσκεται \href{https://hub.docker.com/r/hoo2/hpcimage}{εδώ}.

\section{Αποτελέσματα}

Παρακάτω παραθέτουμε τα αποτελέσματα των μετρήσεων.
Για αυτές:
\begin{itemize}
\item Εκτελέσαμε την κάθε έκδοση του προγράμματος της v1 για\textbf{ διαφορετικό αριθμό από threads} για δύο διαφορετικούς πίνακες(all-to-all) ώστε να δούμε την συμπεριφορά.
Τα threads μπορούν να περαστούν από τη γραμμή εντολών.
\item Εκτελέσαμε την κάθε έκδοση του προγράμματος για δύο διαφορετικούς πίνακες(all-to-all) για \textbf{διαφορετική επιθυμητή ακρίβεια}.
\end{itemize}

Για αντιπαραβολή παραθέτουμε ότι ότι στο ίδιο μηχάνημα το οποίο πήραμε τις μετρήσεις η knnsearch έκδοση του matlab κάνει:
\begin{itemize}
\item sift: \textbf{3.223628 [sec]}
\item mnist: \textbf{20.634987 [sec]}
\end{itemize}

Για την έκδοση με την openMP έχουμε:
{
\centering
\includegraphics[width=\textwidth]{../measurements/OMP_over_threads.png}
\captionof{figure}{Βελτιστοποίηση από την παραλληλοποίηση [openMP].}
\label{fig:openMP_th}


Για την έκδοση με την Cilk έχουμε:
\includegraphics[width=\textwidth]{../measurements/CILK_over_threads.png}
\captionof{figure}{Βελτιστοποίηση από την παραλληλοποίηση [open-cilk].}
\label{fig:cilk_th}

Για την έκδοση με τα pthreads έχουμε:
\includegraphics[width=\textwidth]{../measurements/Pthreads_over_threads.png}
\captionof{figure}{Βελτιστοποίηση από την παραλληλοποίηση [pthreads].}
\label{fig:pthreads_th}


Για την έκδοση με την openMP όσο αλλάζουμε την ακρίβεια έχουμε:
\includegraphics[width=\textwidth]{../measurements/OMP_over_accuracy.png}
\captionof{figure}{Βελτιστοποίηση λόγο μειωμένης ακρίβειας [openMP].}
\label{fig:openMP_acc}


Για την έκδοση με την Cilk όσο αλλάζουμε την ακρίβεια έχουμε:
\includegraphics[width=\textwidth]{../measurements/CILK_over_accuracy.png}
\captionof{figure}{Βελτιστοποίηση λόγο μειωμένης ακρίβειας [open-cilk].}
\label{fig:cilk_acc}

Για την έκδοση με τα pthreads όσο αλλάζουμε την ακρίβεια έχουμε:
\includegraphics[width=\textwidth]{../measurements/Pthreads_over_accuracy.png}
\captionof{figure}{Βελτιστοποίηση λόγο μειωμένης ακρίβειας [pthreads].}
\label{fig:pthreads_acc}
}

\section{Συμπεράσματα}

%\justifying

Απ' ότι βλέπουμε παραπάνω, ενώ ο χρόνος βελτιώνεται καθώς χρησιμοποιούμε παραπάνω threads, αυτό δεν συμβαίνει όταν μειώνουμε την ακρίβεια.
Τουλάχιστον όχι όσο θα θέλαμε.
Φυσικά αυτό έχει να κάνει με την υλοποίηση μας, καθώς δεν επιλέξαμε καλή τεχνική συνένωσης.
Ακόμα φαίνεται ότι ενώ η μέθοδος με τα pthreads αποδίδει καλά, αυτό δεν συμβαίνει και με την cilk και την openmp.
Αυτό γιατί στην παρούσα υλοποίηση δεν γίνεται σωστή διαχείριση των threads.


\end{document}

+ 15
- 0
homework_1/src/main.cpp Voir le fichier

@@ -120,6 +120,13 @@ bool get_options(int argc, char* argv[]){
return status;
}

/*!
* Matrix load
*
* \fn void loadMtx(MatrixDst&, MatrixDst&)
* \param Corpus matrix to load to
* \param Query matrix to load to
*/
void loadMtx(MatrixDst& Corpus, MatrixDst& Query) {
if (access(session.outMtxFile.c_str(), F_OK) == 0)
std::remove(session.outMtxFile.c_str());
@@ -132,6 +139,14 @@ void loadMtx(MatrixDst& Corpus, MatrixDst& Query) {
// timer.print_dt("Load hdf5 files");
}


/*!
* Matrix store
*
* \fn void storeMtx(MatrixIdx&, MatrixDst&)
* \param Idx Index part(neighbors) of the matrix to store
* \param Dst Distances part of the matrix to store
*/
void storeMtx(MatrixIdx& Idx, MatrixDst& Dst) {
// timer.start();
Mtx::store<MatrixIdx, IdxHDF5Type>(session.outMtxFile, session.outMtxIdxDataSet, Idx);


+ 5
- 1
homework_1/src/v1.cpp Voir le fichier

@@ -9,7 +9,11 @@

#include "v1.hpp"


/*!
* \fn void init_workers()
*
* Initialize worker settings
*/
void init_workers() {
#if defined CILK
size_t cilk_w = __cilkrts_get_nworkers();


Chargement…
Annuler
Enregistrer