%
% PDS exercise 1 report
%
% authors:
%   Χρήστος Χουτουρίδης ΑΕΜ 8997
%   cchoutou@ece.auth.gr
%

% AuthReportConfig requirements
% =============================
\newcommand{\AuthorName}{Χρήστος Χουτουρίδης}
\newcommand{\AuthorMail}{cchoutou@ece.auth.gr}
\newcommand{\AuthorAEM}{8997}

% Comment out to add co-author
%\newcommand{\CoAuthorName}{Co-author's name}
%\newcommand{\CoAuthorMail}{Co-author'smail}
%\newcommand{\CoAuthorAEM}{Co-author's AEM}

\newcommand{\DocTitle}{Εργασία 1: \eng{vertexwise triangle counting}}
\newcommand{\Department}{Τμημα ΗΜΜΥ. Τομεάς Ηλεκτρονικής}
\newcommand{\ClassName}{Παράλληλα και Διανεμημένα Συστήματα}

\newcommand{\InstructorName}{Πιτσιάνης Νικόλαος}
\newcommand{\InstructorMail}{nikos.pitsianis@eng.auth.gr}
\newcommand{\CurrentDate}{\today}

\input{config/AuthReportConfig.tex}

% Document fine-tuning
\setFancyHeadLR{\ClassName}{\DocTitle}
%\setFancyHeadLERO{\ClassName}{\DocTitle}
%\BottomTitleSpace{8em}

\usepackage{subcaption}
\usepackage{float}
\usepackage{appendix}


% Document
% =============================
\begin{document}

%\FirstPage
\TitleHeader

%\tableofcontents
%\listoffigures
%\listoftables

\section{Εισαγωγή} 
Μια δεξιότητα ενός μηχανικού λογισμικού, που πάντα αποτελεί πρόκληση, είναι ο παράλληλος προγραμματισμός.
Με αυτόν θα ασχοληθούμε στην παρούσα εργασία.
Για το σκοπό αυτό θα υλοποιήσουμε ένα πρόγραμμα που θα εντοπίζει τις ακμές που δημιουργούν τρίγωνα σε ένα μη κατευθυντικό γράφο.
Και αν αυτό είναι το τι, ο πραγματικός μας σκοπός είναι να καταφέρουμε να παραλληλοποιήσουμε την διαδικασία.
Να βρούμε δηλαδή τα κομμάτια που μπορούν να εκτελεστούν ταυτόχρονα και που δεν επηρεάζουν το ένα το άλλο.
\par
Στην παρούσα εργασία υλοποιούμε δύο αλγόριθμους, έναν που αναζητά μέσα στον πίνακα όλους τους δυνατούς συνδυασμούς και έναν που κάνει χρήση λίγης γραμμικής άλγεβρας.
Όπως θα δούμε παρακάτω ο δεύτερος μας δίνει καλύτερες δυνατότητες παραλληλοποίησης.
Για να ελέγξουμε την ορθότητα και να πάρουμε μετρήσεις τα εκτελέσιμα που παράξαμε τα τρέξαμε στη υπολογιστική συστοιχία του ΑΠΘ.

\section{Παραδοτέα}
Τα παραδοτέα της εργασίας αποτελούνται από:
\begin{itemize}
    \item Την παρούσα αναφορά.
    \item Το \href{https://git.hoo2.net/hoo2/PDS_TriangleCount}{σύνδεσμο με το αποθετήριο} που περιέχει τον κώδικα για την παραγωγή των εκτελέσιμων, της αναφοράς και τις μετρήσεις από τη συστοιχία του πανεπιστημίου.
\end{itemize}

\section {Υλοποίηση}
Πριν ξεκινήσουμε να αναλύουμε τον παραλληλισμό και τις μετρήσεις καλό θα ήταν να αναφερθούμε στην υλοποίηση.
Για την παρούσα εργασία χρησιμοποιήσαμε τη γλώσσα \eng{C++} και βοηθητικά έγινε χρήση του \eng{bash} και της \eng{matlab}.
Στην εργασία γίνεται χρήση αραιών πινάκων με το \eng{format \textbf{CSC}}, καθώς βολεύει πολύ για πολλαπλασιασμούς.

\subsection{\eng{SpMat - SpMatCol - SpMatRow}}
Πρώτα έπρεπε να δημιουργήσουμε τις κατάλληλες αφαιρέσεις ώστε ο κώδικάς μας να είναι πιο ευανάγνωστος και καλύτερα δομημένος.
Για τον πίνακα δημιουργήσαμε το \eng{template \textbf{SpMat}}.
Πρόκειται για μια δομή που περιέχει εργαλεία για ανάγνωση-εγγραφή στοιχείων, γραμμών, στηλών κτλ, δίνοντάς μας την ψευδαίσθηση ότι κινούμαστε σε πλήρη πίνακα.
Αντίθετα όμως εσωτερικά γίνεται εκτενής χρήση του \eng{format CSC}.
Το \eng{template} μας δίνει τη δυνατότητα επιλογής βελτιστοποίησης, όταν ο πίνακας είναι συμμετρικός, κάτι που συμβαίνει και στην περίπτωσή μας, ώστε όταν ζητάμε να προσπελάσουμε μια γραμμή, στην ουσία να προσπελνούμε την αντίστοιχη συμμετρική στήλη, καθώς αυτό σε ένα πίνακα \eng{CSC} είναι βελτιστοποιημένο.
\par
Οι δομές \eng{\textbf{SpMatCol}} και \eng{\textbf{SpMatRow}} αποτελούν κάτι που στην \eng{C++} ονομάζουμε \eng{views}\footnote{όπως πχ το: \href{https://en.cppreference.com/w/cpp/header/string_view}{\eng{string view}}} και αποτελούν \eng{non-owning objects} που δείχνουν στον πίνακα \eng{SpMat}.
Επίσης ταυτόχρονα αποτελούν ένα είδος \eng{iterator} που μας επιτρέπει να συμπεριφερόμαστε στη στήλη(ή στη γραμμή) σαν να ήταν \eng{range}.
Αντί όμως να χρησιμοποιούμε για δείκτες \eng{pointers}, χρησιμοποιούμε ακεραίους, καθώς έτσι μπορούμε να αναφερόμαστε στην αντίστοιχη θέση ενός άλλου πίνακα πολύ εύκολα.
Έτσι μπορούμε για παράδειγμα να γράψουμε:
\setEnglish
\begin{verbatim}
  for (int i=0 ; i<A.size() ; ++i)
    for (auto j = A.getRow(i); j.index() != j.end() ; ++j)
      C(i, j.index()) = A.getRow(i)*A.getCol(j.index());
\end{verbatim}
\setGreek
και να πάρουμε στον \eng{\textbf{SpMat C}} το $A\odot (A \cdot A)$ και μάλιστα αφού ο Α είναι συμμετρικός ο πολλαπλασιασμός να γίνει με \eng{CSC} στήλες μόνο.
Στην υλοποίησή μας εδώ κάνουμε μια ακόμα βελτιστοποίηση .

\section{Βελτιστοποίηση για το συνολικό αριθμό τριγώνων}
Η υλοποίηση του 2ου αλγόριθμου\eng{(V4)} βασίζεται στις ιδιότητες των συμμετρικών πινάκων και δημιουργεί ένα διάνυσμα $\vec{c_3}$ του οποίου το κάθε στοιχείο είναι ίσο με τον αριθμό των τριγώνων στους οποίους συμμετέχει ο κάθε κόμβος.
Το διάνυσμα δίνεται από την σχέση $\vec{c_3} = \dfrac{1}{2} A \odot (A \cdot A)\cdot \vec{1}$.
Γνωρίζοντας όμως ότι ο πίνακας γειτονίασης Α είναι συμμετρικός, κάποιος μπαίνει στη σκέψη ότι όλη η πληροφορία για τον αριθμό τριγώνων θα μπορούσε να βρίσκεται στο μισό του πίνακα.
Πράγματι, όπως αποδεικνύεται και στην παράγραφο \ref{proof}, για τον κάτω τριγωνικό ενός συμμετρικού γράφου $L=(l_{ij}) \neq 0 \quad \forall i > j$ έχουμε $\vec{c_1} = \sum_{j=0}^{N-1} \sum_{k=0}^{N-1} l_{ij} \cdot l_{ki} \cdot l_{kj} $, όπου το $\vec{c_1}$ είναι ένα διάνυσμα που σε κάθε θέση έχει τον αριθμό των \textit{μή διατεταγμένων τριγώνων} του κάθε κόμβου.
\par
Αυτό μας δίνει την δυνατότητα να παρακάμψουμε τη δημιουργία του συμμετρικού πίνακα και να δουλέψουμε με τον κάτω τριγωνικό, όπως ακριβώς μας έρχεται από το \eng{MatrixMarket format}.
Αν και αυτό μπορεί να εφαρμοστεί στον αλγόριθμο της έκδοσης \eng{V3}, αυτό δεν μπορεί να γίνει στην έκδοση \eng{V4}, στην οποία πρέπει να κάνουμε ολόκληρο τον πολλαπλασιασμό για το διάνυσμα $\vec{c_3}$.
Αν όμως επιθυμούσαμε μόνο το συνολικό αριθμό τριγώνων τότε θα μπορούσαμε και εδώ να κάνουμε χρήση της βελτιστοποίησης.
Τα αποτελέσματα αυτά είναι σημειωμένα στον πίνακα \ref{ar:timing} ως \eng{\textit{sum}}.

\section{Παραλληλισμός}
Η στρατηγική μας για τον παραλληλισμό, τόσο για την έκδοση \eng{V3}, όσο και για την \eng{V4}, αρχικά ήταν να χωρίσουμε όλες τις πράξεις που μπορούν να γίνουν ταυτόχρονα.
Αυτό οδηγεί σε πολύ μεγάλο αριθμό πιθανών ταυτόχρονων νημάτων τα οποία:
\begin{itemize}
    \item Στην περίπτωση του \eng{V3}: Για κάθε νήμα, αφού έχουμε σε Ο(1) τις δύο πρώτες ακμές του κάθε τριγώνου, κάνουμε αναζήτηση την τρίτη ακμή, πίσω στην στήλη αναφοράς.
    \item Στην περίπτωση του \eng{V4}: Για κάθε νήμα, αφού έχουμε σε O(1) τη κάθε μή μηδενική θέση του πίνακα, υπολογίζουμε το εσωτερικό γινόμενο της αντίστοιχης γραμμής και στήλης. 
\end{itemize}
Αυτή η προσέγγιση δυστυχώς έχει ένα μεγάλο μειονέκτημα, κάτι που επιβεβαιώσαμε και πειραματικά \footnote{Χρησιμοποιήσαμε τα \eng{valgrind} και \eng{gprof}}.
Κάθε χρονική στιγμή πολλά νήματα προσπαθούν να αναγνώσουν από τη μνήμη, μία ένα αντικείμενο από τη στήλη αναφοράς και μία από τη στήλη κάποιας άλλης ακμής.
Επίσης προσπαθούν να γράψουν στην θέση του διανύσματος εξόδου στη θέση που αντιστοιχεί στη στήλη αναφοράς.
Αυτό εκτός από το μεγάλο \eng{race}, αυξάνει δραματικά τις αποτυχίες της \eng{cache}, με αποτέλεσμα να χάνεται οποιαδήποτε επιτάχυνση μπορούσαμε να αποκτήσουμε.
\par
Για να υπερκεράσουμε αυτό το πρόβλημα αποφασίσαμε να \textbf{“δώσουμε” σε κάθε νήμα μία και μόνο στήλη}.
Αυτό μειώνει τον αριθμό των νημάτων πάρα πολύ και σχεδόν εξαλείφει τα κομμάτια που πρέπει να προστατευτούν για \eng{race}.
Σε αυτή την προσέγγιση το κάθε νήμα εκτελεί ότι περιγράψαμε παραπάνω, μόνο που τώρα το εκτελεί \textit{για κάθε μη μηδενικό αντικείμενο της κάθε στήλης}.
Με αυτό τον τρόπο εκμεταλλευόμαστε την \eng{cache} του κάθε πυρήνα και τα άλματα στη μνήμη γίνονται από μια(ίδια στήλη ανά πυρήνα), σε μια διαφορετική κάθε φορά.
Το μειονέκτημα σε αυτή την περίπτωση προκύπτει από τη σειρά με την οποία δημιουργούνται τα νήματα.
Σε ένα πίνακα που τα μη μηδενικά στοιχεία είναι ομοιόμορφα κατανεμημένα τα νήματα χρειάζονται περίπου τον ίδιο χρόνο για να εκτελεστούν.
Αν όμως κάποιες στήλες είναι πολύ \textit{“βαρύτερες”} από κάποιες άλλες τότε τα νήματα σε αυτές καθυστερούν περισσότερο και μαζί τους καθυστερούν όλη τη διαδικασία.
\par
Σε αυτό το σημείο έρχονται να βοηθήσουν τα εργαλεία διαχείρισης του χρονοπρογραμματισμού(\eng{scheduling}) των νημάτων.
Στην περίπτωση της \eng{cilk} δεν έχουμε κάποια πρόσβαση.
Από την άλλη όμως η \eng{cilk} έχει ένα αρκετά ευέλικτο μοντέλο που στηρίζεται στο \eng{work stealing}\footnote{\eng{\url{https://en.wikipedia.org/wiki/Work_stealing}}} με αποτέλεσμα να διαχειρίζεται αρκετά ομαλά τις ανομοιομορφίες. 
\par
Στην \eng{openMP} από την άλλη έχουμε τη δυνατότητα να ελέγξουμε το \eng{scheduling}, κάτι που για την υλοποίησή μας, μπορεί να γίνει με την παράμετρο “\eng{-{}-dynamic}” από τη γραμμή εντολών.
Έτσι για τους πίνακες που είναι αρκετά ανομοιόμορφοι μπορούμε να επιλέξουμε δυναμικό, ενώ για τους ποιο ομοιόμορφους στατικό.
Με το δυναμικό το αποτέλεσμα είναι τα νήματα που εκτελούνται κάθε χρονική στιγμή να έρχονται από “τυχαίες” στήλες του πίνακα.
Αυτή η τυχαιότητα έχει ως αποτέλεσμα να εξαλείφει τις ανομοιομορφίες επιβαρύνοντας λιγάκι την \eng{cache}.
Συνολικά όμως σε ένα ανομοιόμορφο πίνακα αξίζει τον κόπο.
Για παράδειγμα στον πίνακα \eng{com\_Youtube} στην έκδοση \eng{V4}, για 8 πυρήνες με στατικό \eng{scheduling} είχαμε 2780 \eng{msec} ενώ με δυναμικό 1598 \eng{msec}.
\par
Τέλος στην περίπτωση των \eng{pthreads}, δεν έχουμε κανένα αυτοματισμό έτοιμο.
Παρόλα αυτά προσπαθήσαμε να κάνουμε κάτι αντίστοιχο της υλοποίησης του \eng{openMP} για το \eng{static} και \eng{dynamic scheduling}.
Αντί να προσπελάσουμε τον πίνακα ανά στήλη με τη σειρά, τον προσπελνάμε με βάση τις τιμές μια ακολουθίας.
Όταν θέλουμε \eng{static scheduling} την ακολουθία αυτή τη έχουμε σε αύξουσα σειρά, όταν όμως θέλουμε “\eng{dynamic}”, τότε την ανακατεύουμε με τυχαίο τρόπο.
Έτσι πάλι έχουμε το κέρδος της εξάλειψης της ανομοιομορφίας όταν αυτό είναι απαραίτητο.
\par
Παρακάτω παραθέτουμε ένα δείγμα από τις μετρήσεις που έγιναν στη συστοιχία.
Σε αυτές η πρώτη μέτρηση (0 \eng{cores}) αφορά τη σειριακή υλοποίηση και η δεύτερη (1 \eng{core}) ένα και μόνο νήμα.
Από τα γραφήματα μπορούμε να δούμε και την αύξηση του χρόνου στο ένα νήμα, καθώς και την αργή πτώση των χρόνων στα λίγα νήματα.
Αυτά οφείλονται στον έξτρα φόρτο που προσθέτει στο πρόγραμμα μας η διαχείριση των νημάτων.
\par
%Οι μετρήσεις με το αλγόριθμο της έκδοσης \eng{(V3)} και \eng{(V4)} συνοψίζονται:\\
\setEnglish
\begin{tabular}{rrrrrr} \label{ar:timing}
    Algo/Matrix       & belgium\_osm & com\_Youtube &  dblp-2010 & mycielskian13 & NACA0015    \\
    Serial v3         &     19.5 [ms]&  2705.0 [ms] &  68.0 [ms] &   1056.0 [ms] & 175.5 [ms]  \\ 
    OpenMP v3 (x8)    &     11.5 [ms]&   704.0 [ms] &  32.0 [ms] &    276.0 [ms] & 102.0 [ms]  \\ 
    OpenMP v3 (x20)   &      8.9 [ms]&   301.0 [ms] &  15.5 [ms] &     76.0 [ms] &  34.5 [ms]  \\ 
    Cilk v3 (x8)      &      8.5 [ms]&   768.0 [ms] &  18.5 [ms] &    395.0 [ms] &  28.0 [ms]  \\
    Cilk v3 (x20)     &      6.3 [ms]&   678.5 [ms] &  11.0 [ms] &    171.5 [ms] &  16.5 [ms]  \\  
     ------------     & ------------ & ------------ &------------& ------------  &------------ \\
    Serial v4         &    48.5 [ms] &    6289 [ms] & 162.0 [ms] &     1743 [ms] &  633.5 [ms] \\ 
    OpenMP v4 (x8)    &    18.5 [ms] &    1598 [ms] &  53.5 [ms] &    517.0 [ms] &  235.0 [ms] \\ 
    OpenMP v4 (x20)   &    6932 [us] &    1010 [ms] &  21.5 [ms] &    128.5 [ms] &   62.5 [ms] \\ 
    Cilk v4 (x8)      &    11.5 [ms] &    1695 [ms] &  29.5 [ms] &    303.0 [ms] &  123.5 [ms] \\
    Cilk v4 (x20)     &    6521 [us] &    1516 [ms] &  16.0 [ms] &    137.5 [ms] &   51.0 [ms] \\ 
    pthreads v4 (x8)  &    19.0 [ms] &    1907 [ms] &  37.5 [ms] &    303.5 [ms] &  128.5 [ms] \\
    pthreads v4 (x20) &    10.0 [ms] &    1369 [ms] &  36.0 [ms] &    154.0 [ms] &   56.0 [ms] \\
    Serial v4 (sum)   &    28.3 [ms] &    2943 [ms] &  36.5 [ms] &    561.5 [ms] &  135.2 [ms] \\ 
    OpenMP v4 (sum)   &    3931 [us] &     883 [ms] &  7761 [us] &     43.0 [ms] &   23.9 [ms] \\ 
    Cilk v4 (sum)     &    3872 [us] &    1288 [ms] &  5710 [us] &     95.0 [ms] &   13.2 [ms] \\ 
    pthreads v4 (sum) &    8424 [us] &   980.6 [ms] &  9370 [us] &     89.9 [ms] &   25.9 [ms] \\ 
\end{tabular}
\setGreek

\begin{figure}[h!]
    \begin{subfigure}[h]{0.52\textwidth}
        \includegraphics[width=\textwidth]{source/belgium_osm.jpg}
        \caption{Πίνακας \eng{belgium\_osm}}
        \label{fig:belgium_osm}
    \end{subfigure}
    \begin{subfigure}[h]{0.52\textwidth}
        \includegraphics[width=\textwidth]{source/com_Youtube.jpg}
        \caption{Πίνακας \eng{com\_Youtube}}
        \label{fig:com_Youtube}
    \end{subfigure}
    \begin{subfigure}[h]{0.52\textwidth}
        \includegraphics[width=\textwidth]{source/dblp-2010.jpg}
        \caption{Πίνακας \eng{dblp-2010}}
        \label{fig:dblp-2010}
    \end{subfigure}
    \begin{subfigure}[h]{0.52\textwidth}
        \includegraphics[width=\textwidth]{source/mycielskian13.jpg}
        \caption{Πίνακας \eng{mycielskian13}}
        \label{fig:mycielskian13}
    \end{subfigure}
    \begin{subfigure}[h]{0.52\textwidth}
        \includegraphics[width=\textwidth]{source/NACA0015.jpg}
        \caption{Πίνακας \eng{NACA0015}}
        \label{fig:NACA0015}
    \end{subfigure}
\end{figure}


\appendix
\section{Παράρτημα}
\subsection{Μέτρηση τριγώνων με κάτω τριγωνικό πίνακα} \label{proof}
Έστω \eng{\textbf{L}} ο κάτω τριγωνικός πίνακας ενός συμμετρικού γράφου \eng{\textbf{G}}, όπου $L=(l_{ij}) \neq 0 \quad \forall i > j$.
Τότε:

\begin{equation}\label{eq:Lproduct}
    (L^T \cdot L)_{(i,j)} = \sum_{k=0}^{N-1}l_{ik}^T \cdot l_{kj} = \sum_{k=0}^{N-1}l_{ki} \cdot l_{kj} \quad \forall k>i, k>j 
\end{equation} 
Έτσι από την \eqref{eq:Lproduct} προκύπτει:
\begin{equation}
    (L^T \cdot L)_{(i,j)} =  \sum_{k=0}^{N-1}l_{ki} \cdot l_{kj} \Rightarrow \
    (L \odot (L^T \cdot L))_{(i,j)}  = l_{ij}\cdot \sum_{k=0}^{N-1} l_{ki} \cdot l_{kj} = \sum_{k=0}^{N-1} l_{ij}\cdot l_{ki} \cdot l_{kj}
\end{equation}
\begin{equation} \label{eq:Cmat}
    \Rightarrow C_{(i,j)} = \sum_{k=0}^{N-1} l_{ij}\cdot l_{ki} \cdot l_{kj} \quad \forall k>i>j 
\end{equation} 
Όπου φαίνεται πως ο \eng{\textbf{C}} είναι ένας κάτω τριγωνικός πίνακας με μη μηδενικά στοιχεία μόνο στις θέσεις \eng{(i,j)} για τις οποίες υπάρχουν στον \eng{\textbf{G}} τα τρίγωνα \eng{(i,j,k)} για κάθε \eng{i,j,k} τ.ω: $k>i>j$.
Δηλαδή \textbf{μόνο μία φορά}.
Και τελικά:
\begin{equation} \label{eq:Tcount}
    \vec{c_1} = (L \odot (L^T \cdot L)) \cdot \vec{1} \Rightarrow \vec{c_1}_{(i)} = \sum_{j=0}^{N-1} \sum_{k=0}^{N-1} l_{ij} \cdot l_{ki} \cdot l_{kj} \quad \forall k>i>j
\end{equation}
Απ' όπου φαίνεται πως το $\vec{c_1}$ είναι διάνυσμα που στη κάθε γραμμή έχει το άθροισμα των τριγώνων μετρημένο μόνο σε μία ακμή(την $l_{ix}, \forall x>i$ του αρχικού γράφου), όχι και στις τρεις.
Το άθροισμα αυτού του διανύσματος, προφανώς, ισοδυναμεί με το συνολικό αριθμό των \textit{\textbf{μη διατεταγμένων τριγώνων}} του γράφου.

% References
% =============================
%\begin{thebibliography}{100}
%\bibitem{item}item...
%\end{thebibliography}

\end{document}