Preguntas paralelismo

Preguntas paralelismo

de Camilo Fossemale Zanotta -
Número de respuestas: 2
Supongamos que yo programo en algún lenguaje de programación compilado (por ejemplo C), un loop sencillo que por ejemplo hace:

Arreglo[1..n]
for i = 1 ...  n do
        Arreglo[i] = Arreglo[i] * 2
end for

Claramente este loop es 100 % paralelizable porque cada unidad de procesamiento puede tomar un i y las lecturas y escrituas van a ser independientes entre si. Una primer duda que tengo, ¿qué tan inteligentes son los compiladores para detectar este tipo de situaciones, de tal manera que nosotros podemos programar tranquilamente sin atención ni esfuerzo explícito en indicar cómo queremos que se haga la paralelización (que es la idea del curso creo, generar código donde el paralelismo le indique al compilador cómo se debe hacer de forma explícita y al mismo timepo favorecer el paralelismo implicito (teniendo en cuenta localidad de cache , etc)).

Por otro lado supongamos que el compilador detecta la posibilidad de paralelizar, entonces, en tiempo de compilación el compilador traduce el código en C a un conjunto de instrucciones en lenguaje ensamblador,
¿Es en este punto en que el compilador ya tiene que tener la inteligencia para poder generar diferentes "piezas de còdigo ensamblador" independientes entre sí y distribuibles entre los cores de un procesador (por ejemplo)?

O el lenguaje en ensamblador que se genera, se genera igual y el paralelismo se resuleve a nivel de hardware en el propio procesador?

Una última pregunta, en el ejemplo anterior, es claro que lo ideal sería tener "n" unidades de cómputo independientes , para que cada uno tome una fila, en un contexto de unidades de procesamiento homogenea.
Sin embargo, esto va a depender de la cantidad de recursos que el programa tenga asignado . Me surgen dos preguntas
1- El compilador sabe de antemano los recursos que tiene asignados por el SO? Por ejemplo, como en la clase de ayer que ya podíamos asumir de primera que teníamos 4 procesadores.
2- ¿Es el SO el que se encarga de asignar los recursos en general, siempre se asignan de antemano, o se puede hacer un poco a demanda, lo que requeriría, si no entiendo mal, mucha intervención del SO, o toda esa lógica está a programado a nivel de hardware, entre por ejemplo, los diferentes cores de un mismo multi-core?
3- El SO asigna recursos a nivel de procesador, o a nivel de core de procesador?

gracias!!

En respuesta a Camilo Fossemale Zanotta

Re: Preguntas paralelismo

de Ernesto Dufrechou -
Hola Camilo,
si compilamos ese código sin ninguna flag de optimización, el compilador no detectará paralelismo alguno y se genera algo así:
for (unsigned int i = 0; i < TAM; i++) a[i] *= 2;
  7c: c7 85 e8 ca f3 ff 00 movl   $0x0,-0xc3518(%rbp)
  83: 00 00 00 
  86: eb 24                jmp    ac <main+0xac>
  88: 8b 85 e8 ca f3 ff    mov    -0xc3518(%rbp),%eax
  8e: 8b 84 85 f0 ca f3 ff mov    -0xc3510(%rbp,%rax,4),%eax
  95: 8d 14 00              lea    (%rax,%rax,1),%edx
  98: 8b 85 e8 ca f3 ff    mov    -0xc3518(%rbp),%eax
  9e: 89 94 85 f0 ca f3 ff mov    %edx,-0xc3510(%rbp,%rax,4)
  a5: 83 85 e8 ca f3 ff 01 addl   $0x1,-0xc3518(%rbp)
  ac: 81 bd e8 ca f3 ff 3f cmpl   $0x30d3f,-0xc3518(%rbp)
  b3: 0d 03 00 
  b6: 76 d0                jbe    88 <main+0x88>

En cambio, si compilamos con -O3 -march=native -mtune=native se genera lo siguiente:

for (unsigned int i = 0; i < TAM; i++) a[i] *= 2;
70: c5 f9 6f 08 vmovdqa (%rax),%xmm1
74: 48 83 c0 10 add $0x10,%rax
78: c5 f9 72 f1 01 vpslld $0x1,%xmm1,%xmm0
7d: c5 f9 7f 40 f0 vmovdqa %xmm0,-0x10(%rax)
82: 4c 39 e0 cmp %r12,%rax
85: 75 e9 jne 70
87: 48 8d 2d 00 00 00 00 lea 0x0(%rip),%rbp # 8e
8e: 66 90 xchg %ax,%ax

Sin entrar en detalle, es posible observar que el segundo ejemplo utiliza operaciones (vmovdqa, vpslld...) y registros (%xmm...) vectoriales, por lo que varios pasos del loop se realizarán en paralelo en cada instrucción.

Sin embargo, el código ejecutará en un solo core, ya que no hemos utilizado ninguna biblioteca para la creación de threads. Para aprovechar el paralelismo de la CPU y utilizar los demás cores, lo más fácil es utilizar la biblioteca openmp, que permite agregar anotaciones antes de los loop y se encarga de paralelizarlos. Por ejemplo en este caso se podría escribir:

#pragma omp parallel for
for (unsigned int i = 0; i < TAM; i++) a[i] *= 2;

y compilar con la flag -fopenmp.

Además de todo esto existe el paralelismo a nivel de instrucción (ILP) que se refiere a ejecutar en paralelo instrucciones independientes de un mismo hilo. Entre las técnicas relacionadas con esto se pueden mencionar el pipelining (superponer distintas etapas de la ejecución de varias instrucciones), y la ejecución fuera de orden (ejecutar las instrucciones en un orden que no viole las dependencias). Esto lo hace la CPU automáticamente.

pregunta 1: El compilador no sabe sobre los recursos del sistema (además del set de instrucciones) a menos de algunas flags específicas que puedan pasarse. Uno podría perfectamente tener un programa paralelizado con openmp con más hilos que cores/proceadores en el sistema.

preguntas 2 y 3: El scheduler del sistema operativo asigna procesos y threads a cores del procesador intentando que todo funcione lo mejor posible. En algunas ocasiones es posible intervenir para evitar que el SO haga cosas que dañen la performance del programa. Por ejemplo, es posible indicar al SO que un proceso debe correr en un rango fijo de cpus seteando la "afinidad" (cpu affinity). También es posible reservar ciertas cantidades de memoria no paginable, para evitar que el SO baje a disco esa porción de memoria.