Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
C
CI316
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Harbor Registry
Model registry
Operate
Environments
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
jigjunior
CI316
Commits
0931bc93
Commit
0931bc93
authored
8 years ago
by
jigjunior
Browse files
Options
Downloads
Patches
Plain Diff
Versão final do relatório...
parent
8169eaa6
No related branches found
No related tags found
No related merge requests found
Changes
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
doc/relatorio/relatorio_t2.tex
+100
-71
100 additions, 71 deletions
doc/relatorio/relatorio_t2.tex
with
100 additions
and
71 deletions
doc/relatorio/relatorio_t2.tex
+
100
−
71
View file @
0931bc93
...
@@ -38,15 +38,16 @@
...
@@ -38,15 +38,16 @@
\begin{abstract}
\begin{abstract}
O algoritmo de Floyd Warshall consiste em encontrar todos os menores caminhos
O algoritmo de Floyd Warshall consiste em encontrar todos os menores caminhos
entre pares de vértices de um grafo.
entre pares de vértices de um grafo com peso, direcionado ou não. Uma de suas
Esse trabalho se propõe a implementar esse algoritmo de forma serial e paralela
restrições é que não pode haver ciclos negativos no grafo.
Esse trabalho se propõe a implementar esse algoritmo de forma sequencial e paralela
considerando uma matriz de adjacência usando apenas uma dimensão (row-wise).
considerando uma matriz de adjacência usando apenas uma dimensão (row-wise).
\end{abstract}
\end{abstract}
\section
{
Desenvolvimento
}
\section
{
Desenvolvimento
}
O algoritmo foi
escrit
o em linguagem C++ e consiste na utilização
O algoritmo foi
implementad
o em linguagem C++ e consiste na utilização
de uma matriz de adjacência com tamanho k x k.
de uma matriz de adjacência com tamanho k x k.
Inicialmente, o grafo é lido do arquivo de entrada na matriz de
Inicialmente, o grafo é lido do arquivo de entrada na matriz de
...
@@ -365,10 +366,79 @@ index % time self children called name
...
@@ -365,10 +366,79 @@ index % time self children called name
\includegraphics
[scale=0.45]
{
imagens/speedup
}
\includegraphics
[scale=0.45]
{
imagens/speedup
}
\end{center}
\end{center}
\section
{
Complexidade
}
\subsection
{
Sequencial
}
Na versão sequencial, tem-se claramente a seguinte complexidade:
\begin{items}
\item
\texttt
{
Busca na matriz de adjâcencia
}
:
\newline
Como deve-se passar n-etapas, percorrendo toda a matriz, a
complexidade é de
$
O
(
|V|
^
3
)
$
no pior ou melhor caso;
\newline
\item
\texttt
{
Inicialização da matriz de adjacência
}
:
\newline
Como é necessário inicializar toda a matriz de adjacência, temos
temos aqui
$
O
(
|V|
^
2
)
$
.
\end{items}
\subsection
{
Paralela
}
Desconsiderando o tempo de leitura do arquivo de entrada e
criação da matriz de adjacência (que será igual na implementação
paralela), temos dois trechos de código candidatos à implementação
em paralelo:
\begin{items}
\item
\texttt
{
Busca na matriz de adjâcencia
}
;
\newline
\item
\texttt
{
Inicialização da matriz de adjacência
}
.
\end{items}
\section
{
Implementação paralela
}
\subsection
{
Modelo PRAM
}
Na seção Complexidade estão as duas partes do algoritmo que
podem ser melhoradas se implementadas em paralelo.
Uma das vantagens no modelo PRAM é o fato de se poder usar
tantos processadores quanto forem necessários.
Com isso em mente, podemos melhorar as três partes do código
da seguinte maneira:
\begin{items}
\item
\texttt
{
Busca na matriz de adjâcencia
}
:
\newline
Como deve-se passar n-etapas, percorrendo toda a matriz, a
complexidade é de
$
O
(
|V|
)
$
no pior ou melhor caso,
utilizando-se
$
p
^
2
$
processadores, levando-se em conta
que o laço das k-etapas não é facilmente paralelizável;
\newline
\item
\texttt
{
Inicialização da matriz de adjacência
}
:
\newline
Como é necessário passar por todos os elementos dos vetores
de vértices anteriores e distância, temos aqui
$
O
(
1
)
$
.
\end{items}
O trabalho executado em paralelo é o mesmo que na versão sequencial,
já que não existem operações adicionais de gerenciamento e todas
as arestas da matriz de adjacência devem ser visitadas.
\newline
Dessa forma, teremos um ganho considerável no speedup comparando as
duas versões.
\begin{items}
\item
\texttt
{$
n
^
2
$
processadores
}
:
\newline
\begin{equation}
S
_
p(n
^
2) =
\frac
{
|V|
^
3
}{
|V|
}
= |V|
^
2
\end{equation}
\end{items}
Temos aqui um speedup linear, na teoria.
Na prática, o ideal é limitarmos o número de threads para o mesmo
da arquitetura, pois se tivermos
$
n
^
2
$
threads, apenas algumas estarão
em execução ao mesmo tempo, enquanto as outras esperarão até que
aconteça uma troca de contexto, gerando um overhead muito grande.
\newpage
\section
{
Análise do Código e resultados obtidos
}
\section
{
Análise do Código e resultados obtidos
}
O código foi implementado e testado no seguinte hardware:
\begin{items}
\item
Intel® Core™ i5-4210U CPU @ 1.70GHz x 4 (2 threads / 4 núcleos);
\item
6GB de RAM;
\item
Sistema Operacional Linux Debian SID.
\end{items}
\subsection
{
Fontes de ganho e queda de desempenho
}
\subsection
{
Fontes de ganho e queda de desempenho
}
Existem algumas fontes influenciadoras
de
desempenho.
Existem algumas fontes influenciadoras
, que podem melhorar ou piorar o
desempenho.
Ganhos:
Ganhos:
\begin{items}
\begin{items}
...
@@ -384,6 +454,11 @@ index % time self children called name
...
@@ -384,6 +454,11 @@ index % time self children called name
diminuição na granularidade, no último laço. Cada thread passa a ser
diminuição na granularidade, no último laço. Cada thread passa a ser
responsável apenas por calcular uma aresta da matriz de adjacência.
responsável apenas por calcular uma aresta da matriz de adjacência.
O tempo chega a dobrar nesse caso;
\newline
O tempo chega a dobrar nesse caso;
\newline
\item
\texttt
{
schedule
}
: alterar o schedule de static para dynamic ou guided
piora um pouco o desempenho. Tendo em vista que as tarefas são
homogêneas e cada thread vai executar uma quantidade parecida, pode-se
escalonar inicialmente todas as tarefas para cada uma delas.
\newline
Alterar o valor do chunk para o static schedule também não melhora nada.
\end{items}
\end{items}
\subsection
{
Resultados obtidos
}
\subsection
{
Resultados obtidos
}
...
@@ -403,63 +478,6 @@ index % time self children called name
...
@@ -403,63 +478,6 @@ index % time self children called name
diminuição do tempo (baseando-se na parte do código que pode ser
diminuição do tempo (baseando-se na parte do código que pode ser
paralelizado), quanto na tendência do overhead, speedup e eficiência.
paralelizado), quanto na tendência do overhead, speedup e eficiência.
\section
{
Complexidade
}
\subsection
{
Sequencial
}
Desconsiderando o tempo de leitura do arquivo de entrada e
criação da lista de adjacência (que será igual na implementação
paralela), temos três trechos de código candidatos à implementação
em paralelo:
\begin{items}
\item
\texttt
{
Busca na árvore binária
}
:
\newline
Utilizando a estrutura
\emph
{
set
}
do C++, que utiliza uma árvore binária,
temos como complexidade
$
O
(
|A|
*
log |V|
)
$
;
\newline
\item
\texttt
{
Desempilha / Empilha
}
:
\newline
O empilhamento/desempilhamento, possui custo
$
O
(
log |V|
)
$
;
\newline
\item
\texttt
{
Inicialização das estruturas de dados (vetor de vértices e caminho)
}
:
\newline
Como é necessário passar por todos os elementos dos vetores
de vértices anteriores e distância, temos aqui
$
O
(
|V|
)
$
.
Considerando um grafo conexo, temos:
$
|A|
\leq
|V|
-
1
$
ou
$
|V|
\geq
|A|
+
1
$
.
Assim, a complexidade total é igual à
$
O
(
|V|
*
log |V|
+
|A|
*
log |V|
)
$
, ou
$
(
|A|
*
log |V|
)
$
.
\end{items}
\section
{
Implementação paralela
}
\subsection
{
Modelo PRAM
}
Na seção Complexidade estão as três partes do algoritmo que
podem ser melhoradas se implementadas em paralelo.
Uma das vantagens no modelo PRAM é o fato de se poder usar
tantos processadores quanto forem necessários.
Com isso em mente, podemos melhorar as três partes do código
da seguinte maneira:
\begin{items}
\item
\texttt
{
Busca na árvore binária
}
:
\newline
Possuindo
$
n
^
2
$
processadores, podemos utilizar um
algoritmo de busca com complexidade O(1), visto em
sala de aula.
\item
\texttt
{
Desempilha / Empilha
}
: O(log V);
\newline
A princípio, fica igual.
\item
\texttt
{
Inicialização das estruturas de dados (vetor de vértices e caminho)
}
:
\newline
Com
$
2
n
$
processadores, cada um ajustando o valor
inicial em algum item do vetor de anteriores ou do
vetor de distâncias, teremos aqui também complexidade
O(1).
\end{items}
A diferença nessa abordagem, fica por conta da separação entre
p-processadores.
Inicialmente, o vetor é separado em p clusters, de modo
que os menores caminhos sejam calculados para cada cluster,
baseado no vértice que estiver mais perto do vértice inicial.
Após o cálculo local, a distância será enviada a todos os
processadores, para que atualizem os seus respectivos vetores
de distância.
\newpage
\newpage
\section
{
Execução do código
}
\section
{
Execução do código
}
...
@@ -475,7 +493,7 @@ index % time self children called name
...
@@ -475,7 +493,7 @@ index % time self children called name
\end
{
lstlisting
}
\end
{
lstlisting
}
\item
\texttt
{
Compile e gere a documentação
}
:
\item
\texttt
{
Compile e gere a documentação
}
:
\begin
{
lstlisting
}
\begin
{
lstlisting
}
$
make
dijkstra
&&
make doc
$
make
&&
make doc
\end{lstlisting}
\end{lstlisting}
\item
\texttt
{
Execute
}
\item
\texttt
{
Execute
}
\begin{lstlisting}
\begin{lstlisting}
...
@@ -497,9 +515,9 @@ index % time self children called name
...
@@ -497,9 +515,9 @@ index % time self children called name
\end
{
lstlisting
}
\end
{
lstlisting
}
\end
{
items
}
\end
{
items
}
\section
{
Verificação de corretude
}
\section
{
Utilitários
}
\subsection
{
S
cript de benchmark
}
\subsection
{
Teste de corretude
-
s
cript de benchmark
}
No diretório
/
build
/
do projeto foi criado um script
(
gera
\_
bench.sh
)
que roda todos
No diretório
/
build
/
do projeto foi criado um script
(
gera
\_
bench.sh
)
que roda todos
os executáveis com todas as entradas de grafos cíclicos
(
do diretório
/
entrada
/)
e
os executáveis com todas as entradas de grafos cíclicos
(
do diretório
/
entrada
/)
e
joga a saída
(
matriz de adjacência final, depois de todas as atualizações feitas
)
joga a saída
(
matriz de adjacência final, depois de todas as atualizações feitas
)
...
@@ -507,4 +525,15 @@ index % time self children called name
...
@@ -507,4 +525,15 @@ index % time self children called name
As saídas foram checadas e são iguais para as mesmas entradas.
As saídas foram checadas e são iguais para as mesmas entradas.
\subsection
{
Gerador de entradas
}
No diretório
/
entrada
/
do projeto foi criado um script
(
gera
\_
grafo.py
)
que
gera entradas cíclicas ou aciclicas.
\begin
{
items
}
\item
\texttt
{
Para gerar a entrada, basta executar o script
}
:
\begin
{
lstlisting
}
[
language
=
bash
]
$
./gera
_
grafo A 50
$
.
/
gera
_
grafo C
50
2
\end
{
lstlisting
}
\end
{
items
}
\end
{
document
}
\end
{
document
}
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment