-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.tex
592 lines (486 loc) · 49.3 KB
/
main.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
\documentclass{article}
\usepackage[utf8]{inputenc}
\usepackage[spanish]{babel}
\usepackage{listingsutf8}
\usepackage{xcolor}
\usepackage{pdfpages}
\usepackage{geometry}
% to install algorithm2e pckg: sudo apt-get install texlive-science
\usepackage[ruled, vlined, nofillcomment]{algorithm2e}
\usepackage{float}
\usepackage{hyperref}
\usepackage{amsmath}
\usepackage{framed}
\usepackage{enumitem}
\usepackage{svg}
\geometry{
a4paper,
margin=1.2in
}
% Some settings for coding style
\lstset{
basicstyle=\linespread{0.9}\ttfamily\footnotesize,
frame=single,
frameround=tttt,
numbers=left,
numberstyle=\tiny,
linewidth=14cm,
literate=
{á}{{\'a}}1 {é}{{\'e}}1 {í}{{\'i}}1 {ó}{{\'o}}1 {ú}{{\'u}}1
{Á}{{\'A}}1 {É}{{\'E}}1 {Í}{{\'I}}1 {Ó}{{\'O}}1 {Ú}{{\'U}}1
{à}{{\`a}}1 {è}{{\`e}}1 {ì}{{\`i}}1 {ò}{{\`o}}1 {ù}{{\`u}}1
{À}{{\`A}}1 {È}{{\'E}}1 {Ì}{{\`I}}1 {Ò}{{\`O}}1 {Ù}{{\`U}}1
{ä}{{\"a}}1 {ë}{{\"e}}1 {ï}{{\"i}}1 {ö}{{\"o}}1 {ü}{{\"u}}1
{Ä}{{\"A}}1 {Ë}{{\"E}}1 {Ï}{{\"I}}1 {Ö}{{\"O}}1 {Ü}{{\"U}}1
{â}{{\^a}}1 {ê}{{\^e}}1 {î}{{\^i}}1 {ô}{{\^o}}1 {û}{{\^u}}1
{Â}{{\^A}}1 {Ê}{{\^E}}1 {Î}{{\^I}}1 {Ô}{{\^O}}1 {Û}{{\^U}}1
{œ}{{\oe}}1 {Œ}{{\OE}}1 {æ}{{\ae}}1 {Æ}{{\AE}}1 {ß}{{\ss}}1
{ű}{{\H{u}}}1 {Ű}{{\H{U}}}1 {ő}{{\H{o}}}1 {Ő}{{\H{O}}}1
{ç}{{\c c}}1 {Ç}{{\c C}}1 {ø}{{\o}}1 {å}{{\r a}}1 {Å}{{\r A}}1
{€}{{\euro}}1 {£}{{\pounds}}1 {«}{{\guillemotleft}}1
{»}{{\guillemotright}}1 {ñ}{{\~n}}1 {Ñ}{{\~N}}1 {¿}{{?`}}1
}
\title{66.26 - Arquitecturas Paralelas\\Trabajo final:\\Estudio de factores que afectan el speedup en la resolución numérica de problemas mediante OpenFAOM}
\author{
\\\\\\\\
% \Large{Group name}\\
Arturi, Augusto(\texttt{\#97498})\\\texttt{turitoh@gmail.com}\\
\\
Rozanec, Matias (\texttt{\#97404})\\\texttt{rozanecm@gmail.com}\\
% \\
% surname2, name2 (\texttt{\#padron})\\\texttt{mail@example.com}\\
\\\\\\\\\\\\\\
}
\date{Diciembre 2018}
\begin{document}
\maketitle
\begin{figure}[!htp]
\centering
\includegraphics[scale=1]{../res/fiuba_logo.png}
\end{figure}
\begin{center}\normalsize{Facultad de Ingeniería, Universidad de Buenos Aires}\end{center}
\newpage
\tableofcontents
\newpage
% *** CONSIGNA ***
% \part{Consigna}
% \includepdf[pages=-]{consigna.pdf}
% *** RESOLUCION ***
\begin{abstract}
En este trabajo se presenta el estudio de los distintos factores que puedan llegar a afectar el speedup en la resolución de problemas que requieren soluciones numéricas. El mismo está centrado en las funcionalidades que brinda el software OpenFOAM, que permite la discretización del problema y la paralelización de la resolución de problemas mediante el trabajo en conjunto con alguna implementación de MPI (Message Passing Library). Para este trabajo se utilizó mpirun y se trabajó sobre un problema de Dinámica de Fluidos. Se estudia el desempeño del solver en función de la cantidad de procesadores disponibles y en función de las distintas opciones de binding que admite mpirun.
Finalizado el trabajo, se pudieron sacar conclusiones en cuanto al speedup en función de la cantidad de procesadores disponibles, en cuanto al tipo de descomposición utilizada en la discretización, y en cuanto a las distintas configuraciones posibles de binding.
\end{abstract}
\newpage
\section{Objetivos}
El objetivo del presente trabajo final es poder integrar todos los conceptos estudiados en la materia a lo largo del cuatrimestre en un estudio de caso real. De esta forma se busca salir del esquema fuertemente estructurado propio del aprendizaje teórico y entrar en un análisis interpolado que exija desarrollar una mirada dinámica de los temas y que deba ser apreciado desde distintas perspectivas.
\section{Introducción}
La Dinámica de Fluidos (CFD por sus siglas en inglés) se usa para simular el flujo de fluidos en aplicaciones industriales. A medida que los simuladores se vuelven más complejos, el poder de cálculo que se requiere aumenta significativamente. En casos en que el tiempo de cómputo puede llevar días o meses, aún cambios relativamente pequeños en la eficiencia de cálculo pueden representar grandes mejoras en el cómputo.
El código usado para correr las simulaciones es OpenFOAM, un paquete CFD open source. Este software es diseñado para correr en paralelo, pudiendo ser configurado para correr efectivamente en cualquier cantidad de cores distribuidos a lo largo de cualquier número de computadoras presentes en una misma red. Idealmente los casos que se puedan correr en paralelo serán divididos en partes iguales distribuidas en cuantas unidades de procesamiento haya disponibles. Si cada proceso es capaz de correr independientemente, entonces el speedup será (en teoría) linealmente proporcional al incremento del hardware de cómputo.
Debido a la naturaleza de los cálculos de CFD, OpenFOAM requiere un nivel significativo de comunicaciones inter-proceso para asegurar resultados consistentes en el dominio del caso. Esto significa que aunque el software sea en teoría infinitamente paralelizable, cada proceso adicional agrega costo comunicacional que reduce el speedup. Adicionalmente, a medida que el tamaño del hardware crece, varios cuellos de botella del sistema, como la latencia de la red, pueden obstaculizar posibles mejoras y llegar a un límite en que ya no convenga agregar más hardware. Estas limitaciones que se encuentran en la práctica hay que encararlas con especial cuidado, ya que es lo que define el nivel del grano con que se va a trabajar en la paralelización: de tener un problema no demasiado grande, es probable que recurrir a un grano fino, o sea, subdividir el problema en partes muy pequeñas, no termine rindiendo tan bien como se esperaba debido a que el costo comunicacional probablemente supere la ventaja de paralelizar el problema, quedando esta última muy disminuida o incluso anulada.
En el presente trabajo se realizaron varias mediciones que permiten apreciar el desempeño en varios escenarios.
\newpage
\section{Herramientas}
\subsection{Hardware}
\begin{description}[align=right]
\item [PC1] Intel Core i5-7200U 2.5 GHz with Turbo Boost up to 3.1 Ghz\\
2 núcleos, 4 subprocesos.\\
2 canales de memoria\\
6 GB DDR4 memoria RAM 2400 MHz
\begin{table}[H]
\centering
\begin{tabular}{ r l }
Architecture: & x86\_64\\
CPU op-mode(s): & 32-bit, 64-bit\\
Byte Order: & Little Endian \\
CPU(s): & 4\\
On-line CPU(s) list: & 0-3 \\
Thread(s) per core: & 2 \\
Core(s) per socket: & 2 \\
Socket(s): & 1 \\
NUMA node(s): & 1\\
Vendor ID: & GenuineIntel \\
CPU family: & 6 \\
Model: & 142 \\
Model name: & Intel(R) Core(TM) i5-7200U CPU @ 2.50GHz \\
Stepping: & 9 \\
CPU MHz: & 601.260 \\
CPU max MHz: & 3100,0000 \\
CPU min MHz: & 400,0000 \\
BogoMIPS: & 5424.00 \\
Virtualization: & VT-x \\
L1d cache: & 32K \\
L1i cache: & 32K \\
L2 cache: & 256K \\
L3 cache: & 3072K\\
\end{tabular}
\caption{Información brindada por el comando \texttt{lscpu} para la PC1.}
\end{table}
\item [PC2] AMD Ryzen 5 2400G 3.6GHz\\
4 nucleos, 8 subprocesos.\\
2 canales de memoria\\
16 GB DDR4 memoria RAM 2400 MHz
\begin{table}[H]
\centering
\begin{tabular} { r l }
Architecture: & x86\_64\\
CPU op-mode(s): & 32-bit, 64-bit\\
Byte Order: &Little Endian\\
CPU(s): & 8\\
On-line CPU(s) list: &0-7\\
Thread(s) per core: &2\\
Core(s) per socket: &4\\
Socket(s): &1\\
NUMA node(s): & 1\\
Vendor ID: & AuthenticAMD\\
CPU family: & 23\\
Model: & 17\\
Model name: & AMD Ryzen 5 2400G with Radeon Vega Graphics\\
Stepping: & 0\\
CPU MHz: & 1530.102\\
CPU max MHz: & 3600,0000\\
CPU min MHz: & 1600,0000\\
BogoMIPS: & 7186.53\\
Virtualisation: & AMD-V\\
L1d cache: & 32K\\
L1i cache: & 64K\\
L2 cache: & 512K\\
L3 cache: & 4096K\\
NUMA node0 CPU(s): & 0-7\\
\end{tabular}
\caption{Información brindada por el comando \texttt{lscpu} para la PC 2.}
\end{table}
\item [PC3] Intel Core i5-5200U CPU @ 2.20GHz 3M Cache, up to 2.70 GHz\\
2 nucleos, 4 subprocesos.\\
2 canales de memoria\\
8 GB DDR4 memoria RAM 2400 MHz
\begin{table}[H]
\centering
\begin{tabular}{ r l }
Architecture: & x86\_64\\
CPU op-mode(s): & 32-bit, 64-bit\\
Byte Order: & Little Endian\\
CPU(s): & 4\\
On-line CPU(s) list: & 0-3\\
Thread(s) per core: & 2\\
Core(s) per socket: & 2\\
Socket(s): & 1\\
NUMA node(s): & 1\\
Vendor ID: & GenuineIntel\\
CPU family: & 6\\
Model: & 61\\
Model name: & Intel(R) Core(TM) i5-5200U CPU @ 2.20GHz\\
Stepping: & 4\\
CPU MHz: & 2271.156\\
CPU max MHz: & 2700.0000\\
CPU min MHz: & 500.0000\\
BogoMIPS: & 4390.10\\
Virtualization: & VT-x\\
L1d cache: & 32K\\
L1i cache: & 32K\\
L2 cache: & 256K\\
L3 cache: & 3072K\\
NUMA node0 CPU(s): & 0-3\\
\end{tabular}
\caption{Información brindada por el comando \texttt{lscpu} para la PC 3.}
\end{table}
\end{description}
\subsection{Software}
\subsubsection{Sistema Operativo}
En las PCs 1 y 3 se trabajó con el sistema operativo Ubuntu 16.04 LTS.
La PC 2 en cambio está equipada con Ubuntu 18.04 LTS.
\subsubsection{Software de resolución numérica}
El software utilizado para la resolución de problemas numéricos es OpenFOAM, un software de código abierto desarrollado por OpenCFD Ltd. desde 2004. Éste es utilizado en muchas áreas de ciencia e ingeniería, tanto en ámbitos comerciales como educativos. OpenFOAM presenta una amplia gama de características que le permite resolver una amplísima gama de problemas de fluidos, incluyendo pero no limitándose a reacciones químicas, turbulencias, transferencia de calor, acústica, mecánica de sólidos y electromagnetismo entre otros.
\subsubsection{Software de paralelización}
Para poder correr el solver en paralelo, se hace uso de mpirun (Open MPI) versión 1.10.2.
\subsubsection{Software de medición de tiempo}
Debido a que el programa puede ser operado desde una terminal, las mediciones de las corridas se han podido realizar directamente mediante el comando time, obteniendo las siguientes mediciones:
\begin{description}[align=left]
\item [Real:] mide el tiempo desde que arrancó a correr el programa hasta que terminó. Este tiempo incluye los time slices que el procesador está ocupado con otros procesos no relacionados con el programa de interés. Sin embargo, como esto siempre sucede en un sistema real\footnote{ Un sistema operativo siempre distribuye el tiempo de procesador de la mejor forma posible, y si bien puede haber tareas más importantes que otras, siempre habrá algunos procesos que requerirán algún tiempo de CPU, aunque el mismo sea mínimo, por lo que habrá ciclos de clock dedicados a tareas varias distintas a la medida.}, al haber hecho las mediciones corriendo solamente el software de interés sin otros procesos importantes de fondo, se puede considerar esta medición como buena.
\item [User:] mide tiempo en que la CPU está ocupada en modo usuario. Notar que este tiempo en teoría no podría ser mayor al tiempo Real, sin embargo esto no es cierto si se corre un proceso en varios procesadores: en tal caso, User indica el total de tiempo contemplando a todas las CPUs, por lo que se esperan tiempos mayores al Real.
\item [Sys:] mide tiempo en que la CPU está ocupada en modo kernel.
\end{description}
\newpage
\section{Análisis preliminar}
\subsection{Hardware}
Hay una variedad de factores de hardware que pueden afectar la eficiencia en el cálculo de CFD.
\begin{description}
\item [Velocidad de CPU/RAM:] se listan la velocidad de cores y velocidad de RAM de las PC utilizadas para las simulaciones, notando que OpenFoam es una aplicación de alto consumo de memoria, por ende se espera una mejor perfomance en aquellas RAM con mayor frecuencia de clock.
\item [Canales de memoria RAM:] tanto la velocidad de la memoria RAM como el número de canales entre la CPU y la memoria compartida puede afectar la velocidad de una simulación CFD. Dado que los procesadores que se utilizan tienen 2 canales de memoria, cuando la cantidad de cores activos sea mayor a 2 y el canal de memoria esté saturado dado el límite de ancho de banda, algunos cores quedarán en idle hasta que se transfiera toda la información a memoria, este proceso puede enlentecer la simulación.
\item [Turbo boost:] la capacidad de overclockear el procesador con el fin de aumentar su frecuencia, es una opción que puede maximizar la performance a un alto riesgo de dañar el CPU. Otra forma es utilizar el turbo, capacidad que solo tienen los chips Intel, que es una función designada a operar solo cuando unos pocos cores del CPU están siendo utilizados y el calor excedente producido por la disipacion de energia puede ser distribuido a lo largo del mecanismo de cooling del chip. Teóricamente, si todos los cores están siendo utilizados simultáneamente, el turbo boost no se activa.
\item [Hyper-threading:] esta característica le permite a cada núcleo del CPU presentarse al sistema operativo como si fuesen dos, de los que uno será real y el otro virtual. Luego el SO puede asignar trabajos a los cores virtuales y asignarles trabajo cuando los cores reales están en idle (como por ejemplo esperando una lectura/escritura en memoria), de esta forma se maximiza la utilización del CPU. Sin embargo, OpenFoam es generalmente una aplicacion de uso intensivo de memoria: en raros casos durante una simulación CFD, la utilización de la CPU baja del 100\%, por lo tanto incrementar el número de procesos incrementará la comunicación, generando más overhead, en conclusión a priori se podría decir que este factor afectaría a cualquier beneficio de la utilización de cores virtuales, se espera demostrarlo en el presente trabajo.
\end{description}
\subsection{Software}
\begin{description}
\item [Binding and distribution:] el método por el cual los procesos que se ejecutan son alocados y limitados en los cores, tienen un impacto muy significativo en la velocidad del paralelismo, la proximidad de los procesos en la arquitectura de hardware afecta la velocidad y eficiencia del proceso de intercomunicación.
En una computadora de escritorio la asignación de los procesos es provista por el scheduler del sistema operativo en uso, el cual hará lo necesario para balancear la carga y ejecutar los procesos de la mejor manera posible, a su vez esta tarea podría implicar mover procesos entre distintos cores, siempre con la intención de mejorar la performance. Las aplicaciones CFD asignan los datos discretos a cada proceso en paralelo que es guardado en la memoria de cada núcleo, si por alguna razón el SO decide mover el proceso a un core el cual en memoria no tiene los datos necesarios, la información debe ser reescrita en la memoria de este para que el trabajo pueda continuar; esto produce un efecto perjudicial a la performance.\\
Sin embargo, OpenMPI provee algunas primitivas para que en su ejecución se eviten este tipo de reasignaciones antes de que el trabajo sea terminado mediante el binding de un proceso a un core particular.
\item [Case size and decomposition:] cuando una simulación es preparada para correr en paralelo, el dominio total es repartido en piezas del mismo tamaño, las cuales se asignan acorde a la cantidad de cores disponibles. Así como el número de procesos incrementa, cada pieza se torna más pequeña y el cálculo se completa más rápidamente, pero tal como se comentó anteriormente, incrementar el paralelismo implica incrementar las intercomunicaciones en orden de mantener el resultado esperado consistente. Como resultado, el número de procesos a paralelizar alcanza un máximo práctico, donde el speedup no se puede mejorar dado el aumento de tiempo en comunicación. Para un caso dado, este punto máximo es importante de encontrar para asegurar que los recursos computacionales no son desperdiciados en vano.\\
El método por el cual es descompuesto el dominio afecta el tamaño y la forma de cada pieza de este, así como también el orden en el que estas son numeradas, lo cual afecta a las posiciones relativas dentro del dominio (distancia dentro del procesador).\\
OpenFoam provee de 4 métodos de descomposición:
\begin{description}
\item [Simple:] la descomposición es geométrica en la cual el dominio se divide en partes a partir de la dirección en los ejes x,y,z. Esta forma se suele utilizar en problemas en los cuales la naturaleza del problema es simétrico.
\item [Jerárquica:] es igual a la simple con la salvedad de que el usuario puede especificar el orden en el cual quiere se divida por eje, es decir, primero por el eje Y, luego X y finalmente Z por ejemplo.
\item [Scotch:] esta descomposición es un poco más interesante ya que no divide geométricamente por igual, por lo cual suele ser la más apropiada en problemas de la vida real. Esta forma intenta minimizar las comunicaciones entre los procesadores haciendo que los límites entre estos sean los menores posibles; más adelante se lo explicará con un ejemplo práctico. A su vez se pueden asignar pesos a los trabajos por procesador, característica útil en caso de tener a disposición chips de distinta frecuencia para realizar la simulación.
\item [Manual:] el usuario especifica directamente la locación del área que se le asigna a cada procesador. Sin embargo, en un ejemplo en el cual se tienen que asignar una alta cantidad (millones) de celdas el proceso puede ser tedioso o impráctico.
\end{description}
\end{description}
\newpage
\section{Casos de estudio}
Se ha elegido como caso de estudio la resolución de algún problema que deba ser resuelto mediante métodos numéricos. Este tipo de problemas se caracteriza por no poder ser modelados en rigor matemático, o porque aún pudiendo ser modelados, la resolución exacta no es computable.
Esto mismo es lo que suele pasar en la vida real: si bien todas las ciencias duras están basadas en observaciones de la vida real, el caso de estudio suele ser un caso aislado y de condiciones muy controladas que simplifican el entendimiento del estudio en cuestión. Todas esas simplificaciones que permiten un mejor entendimiento de la naturaleza son al mismo tiempo las que hacen que los modelos obtenidos no sean aplicables tal como se deducen a la vida real debido a la enorme cantidad de otras variables que están en juego.
Dentro de los problemas que requieren de métodos numéricos para su resolución hubo que buscar problemas que admitan ser fraccionados y de esta forma ser computados en paralelo, que es la materia de interés del presente trabajo.
Por todo lo mencionado es que se decidió recurrir al software OpenFOAM.\\
El software OpenFOAM incluye en su instalación una enorme cantidad de tutoriales a modo de ejemplo para los usuarios con el fin de demostrar la amplísima gama de problemas que se pueden resolver con el mismo. Debido a la complejidad de este tipo de problemas y por no ser el foco del presente trabajo, se decide trabajar con uno de los tutoriales que utiliza el \textit{compressibleInterFoam solver}, que es un solver para dos fluidos compresibles no isotérmicos e immiscibles.
Entre las posibilidades de usar el caso 2D y el 3D, se decidió realizar pruebas con el 2D por un tema práctico: el caso 3D es computacionalmente muchísimo más complejo que el 2D, lo que extendería muchísimo el tiempo dedicado a correr el solver en sus distintas configuraciones posibles. Muy probablemente el estudio de los tiempo de ejecución y sus comparaciones serían más vistosos, pero dado el alcance del trabajo, se ha decidido que dicho límite es aceptable.
\newpage
\section{Preparación del entorno}
El tutorial con el que se trabajó se encuentra en el directorio
\texttt{\$FOAM\_RUN/tutorials/multiphase/compressibleInterFoam/laminar/depthCharge2D}.\\
Para el trabajo se han realizado leves cambios en los archivos originales, los cuales se encuentran todos debidamente documentados y adjuntos en el apéndice del trabajo.
\subsection{Caso distribuido}
Para poder resolver el problema en paralelo aprovechando los varios núcleos presentes en una PC, es necesario preparar el dominio de acuerdo a la cantidad de procesadores disponibles o que se quieran utilizar.\\
Como primer paso, hay que correr el comando \texttt{decomposePar}, que subdivide el problema en $n$ partes iguales. $n$ se especifica en el archivo \texttt{system/decomposeParaDict} en la línea 17. Al ejecutar el comando, se obtiene una salida similar a la siguiente, que puede variar en función de $n$:
\begin{lstlisting}
Decomposing mesh region0
Create mesh
Calculating distribution of cells
Selecting decompositionMethod simple [4]
Finished decomposition in 0 s
Calculating original mesh data
Distributing cells to processors
Distributing faces to processors
Distributing points to processors
Constructing processor meshes
Processor 0
Number of cells = 3200
Number of faces shared with processor 1 = 80
Number of faces shared with processor 2 = 40
Number of processor patches = 2
Number of processor faces = 120
Number of boundary faces = 6520
Processor 1
Number of cells = 3200
Number of faces shared with processor 0 = 80
Number of faces shared with processor 3 = 40
Number of processor patches = 2
Number of processor faces = 120
Number of boundary faces = 6520
Processor 2
Number of cells = 3200
Number of faces shared with processor 0 = 40
Number of faces shared with processor 3 = 80
Number of processor patches = 2
Number of processor faces = 120
Number of boundary faces = 6520
Processor 3
Number of cells = 3200
Number of faces shared with processor 1 = 40
Number of faces shared with processor 2 = 80
Number of processor patches = 2
Number of processor faces = 120
Number of boundary faces = 6520
Number of processor faces = 240
Max number of cells = 3200 (0% above average 3200)
Max number of processor patches = 2 (0% above average 2)
Max number of faces between processors = 120 (0% above average 120)
Time = 0
Processor 0: field transfer
Processor 1: field transfer
Processor 2: field transfer
Processor 3: field transfer
End
\end{lstlisting}
En la salida se puede ver cómo el total de celdas queda dividido en 4, ya que se tienen disponibles los 4 procesadores y a su vez cómo se comparte la información de las caras de la figura entre cada uno de los procesadores.\\
Además se han creado 4 directorios nuevos, cada uno representando a un core distinto.\\
La información a continuación es importante en el caso de querer analizar visualmente la información del problema y su respectiva subdivisión en subproblemas. Es una práctica muy recomendable.\\
A continuación se ejecutará el comando \texttt{foamToVTK} en cada uno de los directorios de los procesadores, lo cual creará un directorio \texttt{VTK} que contendrá información de los subdominios para ser analizada con \texttt{paraView}.
\begin{figure}[H]
\centering
\includegraphics[scale=0.5]{../res/imgs/image8.png}
\caption{\textbf{El problema elegido} En esta imagen se puede apreciar la geometría del problema elegido: dos líquidos immiscibles en un recipiente, y una burbuja en la parte inferior del mismo.}
\end{figure}
\begin{figure}[H]
\centering
\includegraphics[scale=0.5]{../res/imgs/image5.png}
\caption{\textbf{Subdivisión del problema }En esta figura se puede cómo se asigna un núcleo para cada parte de la figura.}
\end{figure}
\begin{figure}[H]
\centering
\includegraphics[scale=0.5]{../res/imgs/image3.png}
\caption{\textbf{Detalle de la discretización} En esta imagen se ve en detalle la discretización, cómo cada core se subdivide en 3200 celdas (eje $y$ dividido en 40, el $x$ en 30). Notar que para realizar las operaciones en el límite de las caras se necesita información de varios procesadores distintos: es importante no pasar por alto este detalle, ya que es la variable que juega en contra de la paralelización por traer consigo un costo comunicacional extra. En el hipotético caso de tener 3200 cores para resolver este problema, claramente la situación no escala, debido a que el costo comunicacional terminaría ocupando la mayor porción del tiempo.}
\end{figure}
\begin{figure}[H]
\centering
\includegraphics[scale=0.5]{../res/imgs/image6.png}
\caption{\textbf{Descomposición Jerárquica vs. Scotch} En esta descomposición se puede ver que existe una ligera modificación en cómo se asignan las caras y la distribución de celdas en cada uno de los cores. Este tipo de descomposición es ideal para usar cuando la geometría del problema no es simétrica como en el caso del ejemplo, donde le trata de asignar a cada core la misma cantidad de celdas y minimizar las caras entre estos en base a un algoritmo u estrategia.}
\end{figure}
\newpage
\section{Ejecución}
\subsection{Monocore}
Para ejecutar el solver sin paralelismo, es suficiente con correr el comando \texttt{./Allrun}. El mismo se encarga de crear la malla de discretización y de correr el solver.
\subsection{Multicore}
Para la resolución con paralelismo se hace uso del MPI (Message Passing Interface) mediante el comando mpirun. Al mismo se le pasan flags indicando cantidad de procesadores y tipo de binding, tema que se cubrirá a continuación. Por un tema de prolijidad se sugiere redireccionar la salida del programa a un archivo log.
\begin{lstlisting}[caption={Ejemplo de comando a ejecutar para resolver con paralelismo.}, captionpos=b]
mpirun -np 4 compressibleInterFoam -parallel > log.6626
\end{lstlisting}
Al estar ejecutándose la simulación se puede ver el uso de cpu mediante comandos como \texttt{top} o \texttt{htop}: de acuerdo a la cantidad de procesadores que se haya decidido utilizar, se observa un uso cercano o igual al 100\% en las cpus en uso.
\begin{figure}[h!]
\centering
\includegraphics[scale=0.6]{../res/imgs/image7.png}
\caption{Detalle de ejecución de \texttt{top}}
\end{figure}
\subsubsection{Bindings}
El binding se setea mediante el flag \texttt{--bind-to}. Por defecto se hace \texttt{--bind-to core}, lo que asegura que un mismo proceso se corra siempre en el mismo procesador. Esto es positivo, ya que si el proceso se va cambiando de procesador, se pierde toda la información que el procesador haya guardado en sus registros o cache. Para desactivar esto, se usa \texttt{--bind-to none}.
El flag \texttt{--bycore} asegura que procesos secuenciales de MPI corran en procesadores adyacentes.
Lo esperado es que el binding incremente la performance, previniendo al sistema operativo mover procesos de un core a otro con el fin de balancear la carga.
\newpage
\section{Análisis de las ejecuciones}
En esta sección se realizará un análisis basado en gráficos que permitan apreciar los distintos speedups obtenidos con las distintas configuraciones.
Para el análisis se usará el tiempo real, ya que si bien contempla también tiempos no usados exclusivamente para el procesamiento, se la considera una medida más real debido al tiempo que se usa para comunicación.
Los speedup fueron calculados tomando como tiempo viejo el tiempo necesario para correr el solver en un único núcleo, ya que es el único caso que no hace uso del paralelismo y se quiere ver cómo se puede mejorar a partir de ese caso paralelizando el problema.
\subsection{PC \#2}
\begin{figure}[h!]
\includesvg[svgpath=../res/imgs/]{pc2cores2a4}
\caption{Speedups de PC 2: cantidad de núcleos entre 2 y 4.}
\end{figure}
En esta PC se puede observar que ya usando dos núcleos se obtiene un speedup de 53\% en el mejor de los casos. Este speedup que supera el 50\% es muy bueno considerando que duplicando la cantidad de núcleos el óptimo teórico tiene un tope de 100\%, el cual se sabe que es inalcanzable en la práctica. Notar de todas formas que hay un 14\% de diferencia con el speedup obtenido en el peor caso. Si bien un speedup de 39\% es de todas formas muy bueno, es evidente ya desde el principio que los distintos tipos de binding juegan un papel clave en el manejo de la paralelización.
En el caso de la corrida con 3 núcleos se observa que el mejor speedup fue alcanzado por dos de las tres técnicas de binding, logrando un speedup de 73\%. En este caso la diferencia con el peor speedup logrado es mucho menor, de tan solo 9\%. Sigue siendo, de todas formas, una diferencia significativa. A diferencia del caso anterior, se observa que se logra un cuarto del speedup óptimo teórico (300\%).
Fue necesario ocupar 4 núcleos para alcanzar un speedup del 100\%. La relación entre el óptimo teórico y el mejor real es igual al caso anterior: se logra solamente un cuarto del máximo teórico (400\%). Además se puede ver nuevamente que el binding juega un papel no menor, ya que la diferencia entre mejor y peor caso utilizando 4 núcleos es del 27\%.
Como la PC en análisis cuenta con 8 subprocesos pero solamente 4 núcleos, será interesante ver los resultados de las mediciones subsiguientes. ¿Seguirá aumentando el speedup, o fue el máximo posible ya alcanzado?
\begin{figure}[h!]
\includesvg[svgpath=../res/imgs/]{pc2cores5a8}
\caption{Speedups de PC 2: cantidad de núcleos entre 5 y 8.}
\end{figure}
A primera vista se puede ver que los speedups obtenidos son todos inferiores al mejor speedup obtenido con hasta 4 núcleos.
En los casos de 5 y 6 núcleos casi no hay diferencia entre los distintos tipos de binding, mientras que en el caso de 7 u 8 núcleos, dependiendo del tipo de binding se puede obtener hasta un 21\% o 29\% de diferencia respectivamente (!).
En el caso de los 5 núcleos se observa un speedup de entre el 76\% y 82\%.
En el caso de los 6 núcleos el speedup fue menor: en el mejor caso se alcanzó el 67\% (peor caso: 62\%).
Con 7 núcleos la variedad de resultados desorienta bastante: en el mejor de los casos, el speedup es casi igual al peor caso de los 5 núcleos (!).
En la corrida con 8 núcleos en el mejor de los casos se llega a empatar el speedup con 6 núcleos, mientras que en el peor caso el speedup es más parecido al speedup obtenido con solamente 2 núcleos que a cualquier otra cosa. Alarmante.
De todas estas mediciones con 5 o más núcleos, se pueden rescatar las siguientes observaciones contraintuitivas: en primer lugar, el máximo speedup fue logrado con 5 núcleos, que es la menor cantidad de la muestra observada. Por otro lado, este speedup es muy similar al speedup obtenido con 3 núcleos, lo que significa que todos los speedups con 5 o más están muy por detrás del speedup obtenido con 4 núcleos.
\subsection{PC \#3}
\subsubsection{Descomposicion Jerarquica}
\begin{figure}[H]
\includesvg[svgpath=../res/imgs/]{pc3Jerarquica}
\caption{Speedups de PC 3 con descomposición jerárquica}
\end{figure}
Con esta pc se puede ver que utilizando la descomposición jerárquica, ya usando solamente 2 núcleos se obtiene un excelente speedup, un poco mejor al 80\%, que es el 80\% del óptimo teórico posible: llama la atención, ya que no es común acercarse tanto al óptimo teórico. Notar, de todas formas, que si bien con dos tipos de binding se obtienen excelentes resultados, al usar bind to none by core, el speedup es muchísimo menor: solamente es del 30\%, teniendo un 50\% de diferencia con los otros dos tipos de binding (!). Indudablemente no es un detalle menor.
Con 3 núcleos se observa un desempeño relativamente parejo entre los tres tipos de bindings, habiendo solamente un 7\% de diferencia entre los casos extremos. Sorprende un poco que el speedup no sólo no haya mejorado con respecto al caso de los dos núcleos, sino que ha empeorado alrededor del 10\%.
En el caso de los 4 núcleos, los resultados son un tanto variables: en el mejor de los casos se obtiene un speedup del 75\%, mientras que en el peor de los casos el speedup obtenido es de 46\%, lo que hace una diferencia de 30\% entre los extremos. Nuevamente se observa que el speedup no supera al del caso de los 2 núcleos, y además es bastante parecido al obtenido con 3 núcleos.
Indudablemente, la descomposición jerárquica ha sorprendido un poco con los resultados obtenidos.
\subsubsection{Descomposición Scotch}
\begin{figure}[H]
\includesvg[svgpath=../res/imgs/]{pc3Scotch}
\caption{Speedups de PC 3 con descomposición scotch}
\end{figure}
En el caso de la descomposición scotch con dos núcleos se midió un speedup de entre 75\% y 80\%. Nuevamente, como se comentó en el caso de la descomposición jerárquica, dicho speedup es excelente considerando que el máximo teórico es del 100%.
Lamentablemente el speedup no sigue creciendo a medida que se agregan más núcleos: viendo los gráficos de 3 y 4 núcleos se puede ver que en ninguno de los casos se alcanza, con ninguno de los tres bindings posibles, el peor de los speedups obtenidos con solamente 2 núcleos.
\subsubsection{Jerarquica vs Scotch}
\begin{figure}[H]
\includesvg[svgpath=../res/imgs/]{jerVsScotch}
\caption{Speedups de PC 2 con descomposición jerárquica y scotch}
\end{figure}
Si bien se pueden observar pequeñas diferencias que en los tres casos favorecen a la descomposición Jerárquica, estas diferencias no son mayores. De todas formas toda la evidencia indica que es mejor trabajar con descomposición Jerárquica.
\subsection{Comparación de bindings}
\subsubsection{2 núcleos}
\begin{figure}[H]
\includesvg[svgpath=../res/imgs/]{ComparacionDeBindings2cores}
\caption{Comparación de bindings con 2 núcleos}
\end{figure}
En el caso de computar la solución con dos núcleos, se ve que en ambas máquinas el \texttt{--bind-to none -bycore} es el que da peores resultados. Se puede suponer que esto sucede porque si bien el flag \texttt{-bycore} asegura que los procesos son asignados a núcleos adyacentes, como no se asegura que los mismos vuelvan al mismo procesador en que estaban previamente, termina siendo una combinación fatal: por un lado debe cumplir la condición impuesta por el \texttt{bycore}, pero sin obtener nada a cambio, ya que tranquilamente puede terminar estando en otro procesador.
\subsubsection{3 núcleos}
\begin{figure}[H]
\includesvg[svgpath=../res/imgs/]{ComparacionDeBindings3cores}
\caption{Comparación de bindings con 3 núcleos}
\end{figure}
En este caso se puede ver que ninguna de las tres configuraciones de flags castiga sistemáticamente el speedup.
Una posible explicación es la siguiente: todo proceso es desalojado del procesador cada tanto, luego si de 4 posibles núcleos los ocupados con el solver son solamente 2, entonces cada vez que un proceso vuelve al procesador puede volver a uno de 3 restantes (considerando que hay uno solo ocupado dado que se considera que la única tarea grande corriendo en el momento es el solver en el otro núcleo), habiendo una probabilidad igual a 0.33 de volver al mismo núcleo de antes (considerando que el otro solver no fue desalojado mientras). Si, en cambio, se corre el solver en 3 núcleos, realizando un razonamiento análogo se llega a que la probabilidad de volver al mismo núcleo es de 0.5, significativamente mayor. Si bien los números distan de ser correctos por la inmensidad de cuestiones que están involucradas y que son incontrolables e inmedibles, en líneas generales se lo puede considerar útil y representativo.
\subsubsection{4 núcleos}
\begin{figure}[H]
\includesvg[svgpath=../res/imgs/]{ComparacionDeBindings4cores}
\caption{Comparación de bindings con 4 núcleos}
\end{figure}
Esta figura respalda en cierta forma el análisis realizado en el caso de los 3 núcleos con respecto a la configuración de flags \texttt{--gind-to none -bycore}, de hecho en una de las máquinas fue esta configuración justamente la que dio el speedup óptimo con bastante margen.
\newpage
\section{Lecciones aprendidas}
Durante el transcurso del trabajo práctico se aprendió a relacionar los conceptos estudiados en las clases teóricas en un problema aplicado a la vida real, si bien los recursos de hardware para implementar pruebas de paralelismo son elevados, con lo disponible se pudo probar las hipótesis deseadas.
El primer desafío fue encontrar qué problema resolver. En un principio pensamos en problemas NP-completos: el tiempo de cómputo aumentaría rápidamente con solamente agregar algunas variables, y esto haría muy notorio el beneficio de poder sumar unidades de cómputo. Sin embargo, si bien muchos de los problemas np completos son fácilmente paralelizables, no tendría mucho sentido estudiarlos debido a que aún contando con recursos ideales, se llega rápidamente a un tope práctico.
Siguiendo con la búsqueda nos encontramos con esta herramienta, la cual el profesor confirmó que cumplía con lo solicitado: enfrentar un problema de la vida real y lograr una paralelización. Luego nos encontramos con desafíos en cuanto a la documentación para comprender y estudiar el dominio del problema, tanto de fluidos como de paralelismo. Finalmente, tras varios intentos de generar un sistema distribuido, la poca documentación encontrada con respecto a esto dificulto su ejecución, con lo cual lo se abarcó a este desde un punto de vista teórico.
Realizar mediciones y estudiar el speedup en términos experimentales también fue una tarea nueva, la cual en términos prácticos nunca habíamos realizado; por estas razones consideramos al trabajo realizado de gran aporte en material de estudio en el transcurso de nuestra carrera.
\newpage
\section{Conclusiones}
A partir de las mediciones se puede ver que no siempre se cumple que al agregar más procesadores aumenta el speedup. Esto tiene que ver con cuestiones planteadas al principio del trabajo: el costo comunicacional consume tiempo, y si bien es subconscientemente subestimado debido a que el enfoque está en la parte computacional, termina siendo evidente que no es un tema menor.
Si bien en la PC 2 se pudo observar un speedup creciente a medida que se iba aumentando la cantidad de núcleos reales, no sucedió lo mismo con la PC 3, donde si bien no se esperaban valores exagerados e irreales que se acerquen demasiado al speedup óptimo teórico, sí se esperaba que aumente por lo menos un poco al agregar más núcleos para el cómputo.
En cuanto a los tipos de binding se puede concluir que es importante tener presente el la topología en uso, ya que el binding correcto a usar puede variar de acuerdo a cada caso.
\newpage
\section{Posibles extensiones del Proyecto}
En esta sección se describen posibles extensiones que se pueden desarrollar ya conociendo el presente trabajo. Las mismas son ideas que podrían pertenecer a este proyecto, pero que por una cuestión de extensión se han decidido dejar afuera.
\subsection{Continuar la investigación expandiéndose a un cluster de computadoras y estudiar el desempeño aumentando el tamaño del problema}
Habiéndose estudiado en el análisis previo el comportamiento del paralelismo en un solo nodo, sería interesante continuar la investigación expandiéndose a un cluster de computadoras. Algunas de las razones por las que no se pudo realizar esta práctica fue por la falta de hardware como así también la falta de documentación provista por OpenFoam para realizar una corrida distribuida, por ende se analizaran resultados en el marco teórico con lo estudiado en la materia.
El desafío principal al que se enfrenta el paralelismo distribuido, es a la comunicación de procesos entre los distintos nodos de la red. La conexión de red puede rápidamente convertirse en un cuello de botella para la performance, de manera tal que la forma de cómo distribuir a los procesos a lo largo de esta es primordial para gestionar la comunicación, junto con el grado de granularidad de cómo tomar muestras que serán asignadas a los respectivos cores, son factores esenciales a la hora de construir paralelismo en clusters.
En primer lugar se decide realizar un breve ejemplo haciendo suposiciones dentro un marco teórico: Se tiene una conexión de red que es inferior en órdenes de magnitud a la velocidad del microprocesador, así como también un conjunto de varios nodos, cada nodo con las mismas características de hardware. Se ha comprobado anteriormente que los flags provistos por MPI tienen incidencia en los resultados en términos de speed up: se utilizarán \texttt{-bind-to-core –bycore} (el cual asocia procesos a los sucesivos cores) y \texttt{-bind-to-core –bynode} (lanza un proceso por nodo, ciclando a cada nodo con una política de scheduling round robin) Por lo tanto, luego de realizar la ejecución de la aplicación se esperarían obtener los siguientes resultados:
\begin{itemize}
\item Para el flag \texttt{-by node}, la performance esperada sería que haciendo uso de pocos procesos en cada nodo, el nivel de paralelismo sea muy efectivo, dado que por ejemplo se podría asignar un proceso o dos por nodo en donde toda la memoria de cada computador estaría disponible, incluso el TurboBoost, dado que como no se estaría utilizando toda la capacidad de procesamiento la frecuencia podría aumentar ya que la disipación de energía lo permitiría. Sin embargo, si ahora se utilizan todos los núcleos disponibles de cada uno de nuestros nodos, dado el flag propuesto, se entraría en un cuello de botella, ya que no se discriminaría la forma de distribuir los procesos entre los nodos, el scheduler asignaría un trozo o ``slice'' de cada proceso utilizando round robin, por lo tanto el tiempo que se tarda en trasladar la información entre los nodos sería contraproducente, inclusive hasta perdiendo performance. Sin lugar a dudas, esta opción no escalará.
\item Para el flag \texttt{-by core}, la performance esperada sería distinta. Aquí se trataría de utilizar todo el poder de cálculo del cluster ya que la comunicación bajaría notablemente, dado que cada problema se trabajaría en el nodo correspondiente y meramente para realizar cálculos que impliquen a otros nodos se solicitará la información, por ende los speedups obtenidos deberían ser acordes a los conseguidos en nuestro análisis práctico.
\end{itemize}
Se continúa el análisis tomando el segundo caso, ya que es el que mejor escala. Se ha estudiado que el nivel de granularidad es relevante a la hora de paralelizar, en OpenFoam esto se ve reflejado en el método de descomposición utilizado, el cual tiene un impacto directo en la comunicación entre procesos, donde la topología de red y la distancia entre nodos son importantes. Dado que el método de descomposición Scotch está optimizado para reducir el número de relaciones entre procesadores, para justamente reducir la comunicación entre ellos, sería lo esperable que este resulte en la de mejor perfomance para un caso distribuido. Sin embargo, cabe destacar que esta característica puede ser muy propia de cada problema a resolver, dada la geometría o simetría del mismo, las diferentes formas de distribuir las muestras para ser entregadas a los procesadores, si bien un algoritmo puede interpretar el problema e inferir una solución, no necesariamente esta puede ser la óptima.
Finalmente restaría hacer un análisis basado en la Ley de Gustafson, donde ahora para el mismo problema, se disminuye la granularidad, tomando más muestras, para obtener resultados más específicos ya que se espera que dada cierta cantidad de nodos, el speedup no mejore, con esta técnica se puede aprovechar más el procesamiento de cada uno de estos.
\subsection{Utilizar otra implementación de MPI}
Una posibilidad es estudiar la influencia de la implementación MPI: se pueden comparar los resultados sobre un mismo problema usando distintas implementaciones de MPI, o se puede ver si distintas implementaciones funcionan mejor con algún tipo particular de problema, si la elección de la implementación tiene algún peso o funciona de forma transparente en cualquiera de los casos. En este trabajo se usó OpenMPI; otra posible implementación es IntelMPI\footnote{\url{https://software.intel.com/en-us/mpi-library}}.
\subsection{Usar software de análisis de desempeño (profiling)}
Otra opción es hacer uso de software de análisis de desempeño, también conocido como profiling software, para detectar secciones críticas en el código y poder hacer un estudio más enfocado en el código. Para esto se puede utilizar el software Intel Parallel Studio\footnote{\url{https://software.intel.com/en-us/parallel-studio-xe}} que tiene versión gratuita por 30 días, o por tiempo ilimitado para estudiantes.
\newpage
\section{Anexo}
\subsection{Mediciones}
La hoja de datos con las mediciones junto a los gráficos correspondientes están disponibles para una mejor apreciación en este \href{https://docs.google.com/spreadsheets/d/e/2PACX-1vQOSLYH4y81OxVleLZk2YwzFhrl4-AzE2-IUF04Y9lkk0pP6SZe1VFkkFW7r9lDK-Qo8OkGUuwHFlOE/pubhtml}{link}\footnote{https://docs.google.com/spreadsheets/d/e/2PACX-1vQOSLYH4y81OxVleLZk2YwzFhrl4-AzE2-IUF04Y9lkk0pP6SZe1VFkkFW7r9lDK-Qo8OkGUuwHFlOE/pubhtml}.
\subsection{Código del caso}
\lstinputlisting[caption={\texttt{0.orig/alpha.water}}, captionpos=b]{../res/depthCharge2d/0.orig/alpha.water}
\lstinputlisting[caption={\texttt{0.orig/p}}, captionpos=b]{../res/depthCharge2d/0.orig/p}
\lstinputlisting[caption={\texttt{0.orig/p\_rgh}}, captionpos=b]{../res/depthCharge2d/0.orig/p\_rgh}
\lstinputlisting[caption={\texttt{0.orig/T}}, captionpos=b]{../res/depthCharge2d/0.orig/T}
\lstinputlisting[caption={\texttt{0.orig/U}}, captionpos=b]{../res/depthCharge2d/0.orig/U}
\lstinputlisting[caption={\texttt{constant/g}}, captionpos=b]{../res/depthCharge2d/constant/g}
\lstinputlisting[caption={\texttt{constant/thermophysicalProperties}}, captionpos=b]{../res/depthCharge2d/constant/thermophysicalProperties}
\lstinputlisting[caption={\texttt{constant/thermophysicalProperties.air}}, captionpos=b]{../res/depthCharge2d/constant/thermophysicalProperties.air}
\lstinputlisting[caption={\texttt{constant/thermophysicalProperties.water}}, captionpos=b]{../res/depthCharge2d/constant/thermophysicalProperties.water}
\lstinputlisting[caption={\texttt{constant/turbulenceProperties}}, captionpos=b]{../res/depthCharge2d/constant/turbulenceProperties}
\lstinputlisting[caption={\texttt{system/blockMeshDict}}, captionpos=b]{../res/depthCharge2d/system/blockMeshDict}
\lstinputlisting[caption={\texttt{system/controlDict}}, captionpos=b]{../res/depthCharge2d/system/controlDict}
\lstinputlisting[caption={\texttt{system/decomposeParDict}}, captionpos=b]{../res/depthCharge2d/system/decomposeParDict}
\lstinputlisting[caption={\texttt{system/fvSchemes}}, captionpos=b]{../res/depthCharge2d/system/fvSchemes}
\lstinputlisting[caption={\texttt{system/fvSolution}}, captionpos=b]{../res/depthCharge2d/system/fvSolution}
\lstinputlisting[caption={\texttt{system/setFieldsDict}}, captionpos=b]{../res/depthCharge2d/system/setFieldsDict}
\lstinputlisting[caption={\texttt{Allrun}}, captionpos=b]{../res/depthCharge2d/Allrun}
\lstinputlisting[caption={\texttt{Allclean}}, captionpos=b]{../res/depthCharge2d/Allclean}
\newpage
\addcontentsline{toc}{section}{References}
\begin{thebibliography}{9}
\bibitem{}
Why would be sometimes running CFD in parallel processing with 600 CPUs is faster than 800 CPUs even though it has more processors?
\url{https://www.researchgate.net/post/Why_would_be_sometimes_running_CFD_in_parallel_processing_with_600_CPUs_is_faster_than_800_CPUs_even_though_it_has_more_processors2}
\bibitem{time}
What do 'real', 'user' and 'sys' mean in the output of time(1)?
\url{https://stackoverflow.com/questions/556405/what-do-real-user-and-sys-mean-in-the-output-of-time1}
\bibitem{}
William Gropp.
Parallel compuring and domain decomposition.
\url{http://ftp.mcs.anl.gov/pub/tech_reports/reports/P257.pdf}
\bibitem{paraview}
Para view: \url{https://www.paraview.org/}.
\bibitem{}
OpenFOAM: \url{https://www.openfoam.com/}.
\bibitem{}
MPI FAQ: General run-time tuning: \url{https://www.open-mpi.org/faq/?category=tuning#using-paffinity-v1.4}.
\bibitem{}
mpirun(1) man page (version 1.5.5): \url{https://www.open-mpi.org/doc/v1.5/man1/mpirun.1.}
\bibitem{}
Tomasso Lucchini: Running OpenFOAM in parallel \url{http://web.student.chalmers.se/groups/ofw5/Basic_Training/runningInParallelLucchini.pdf}
\bibitem{}
\url{https://www.cfd-online.com/Forums}
\bibitem{}
Keough, Shannon: Optimising the Parallelisation of OpenFOAM Simulations.
\url{https://apps.dtic.mil/docs/citations/ADA612337}
\bibitem{}
Kai Hwang: Advanced Computer Architecture: Parallelism, scalability, programmability.
\end{thebibliography}
\end{document}