0% encontró este documento útil (0 votos)
288 vistas13 páginas

Fork Join

Este documento presenta tres resúmenes de un tema sobre programación paralela en Java con el framework Fork/Join: 1) Explica el uso básico del framework Fork/Join, dividiendo tareas grandes en subtareas más pequeñas para ejecutar en paralelo. 2) Detalla un ejemplo de desenfoque de imágenes usando Fork/Join para procesar cada píxel de forma concurrente. 3) Describe cómo obtener el promedio de batería de dispositivos de forma paralela usando la Stream API de Java 8, la

Cargado por

The Zerox
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
288 vistas13 páginas

Fork Join

Este documento presenta tres resúmenes de un tema sobre programación paralela en Java con el framework Fork/Join: 1) Explica el uso básico del framework Fork/Join, dividiendo tareas grandes en subtareas más pequeñas para ejecutar en paralelo. 2) Detalla un ejemplo de desenfoque de imágenes usando Fork/Join para procesar cada píxel de forma concurrente. 3) Describe cómo obtener el promedio de batería de dispositivos de forma paralela usando la Stream API de Java 8, la

Cargado por

The Zerox
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 13

Universidad Tecnológica de Santiago

(UTESA)

Nombre: Carlos Daniel Bautista Brito

Matricula: 1-18-4120

Asignatura: ALGORITMO PARALELO

Sección: 001

Profesor: Eduardo Arvelo

Tema: Programación paralela en Java con el framework


Fork/Join y su aplicación en el mundo real
Contenido
Introducción............................................................................................3
Qué es el Framework Fork/Join...............................................................4
Uso básico.............................................................................................5
Desenfoque para mayor claridad.............................................................6
Práctica con más tendencia: utilización de la Stream API.........................8
Práctica tradicional: utilizar un foreach secuencial...................................9
Práctica objetivo: utilización del framework Fork/Join...........................10
Conclusión.............................................................................................13
Introducción

El uso de este framework nos permite utilizar toda la potencia disponible


en nuestro ordenador con todos los procesadores disponibles en este
momento, para conseguir resultados mucho más rápidos. Del mismo modo,
podemos encontrarlo en la programación del lado del cliente en la
biblioteca RxJs.

Básicamente, lo que hace este marco es dividir las tareas en subtareas y


ejecutarlas en paralelo para distribuir el trabajo. No es común que este
marco se use explícitamente en repositorios de código. En cambio, lo
vemos escrito internamente en otras bibliotecas de programación
concurrentes o paralelas, como CompletableFuture o Stream API, ambas de
Java 8.

Es importante tener en cuenta que analizaremos el concepto de este marco


a través de un ejemplo del mundo real, para profundizar más y ver su
aplicación en el mundo real.

Java brinda soporte para implementar y ejecutar tareas paralelas, pero por
defecto, las aplicaciones desarrolladas en este lenguaje se ejecutan
secuencialmente.

En este documento veremos algunas de las técnicas que nos proporciona el


lenguaje para desarrollar aplicaciones con tareas paralelas.
Qué es el Framework Fork/Join
Divide y vencerás. Una expresión muy común en la política y la historia en
las guerras. Si reducimos esta frase a la parte aritmética de la misión,
podemos tomarla como si estuviéramos atacando a un ejército de 500
cazas. Si dividimos este dinero en pequeños grupos para luchar, será mucho
más fácil golpear a cada pequeño grupo que atacar a todo un ejército.

 Gráfica de cómo se comportaría la computación en paralelo con el


framework Fork/Join
El framework Fork/Join implementa la interfaz ExecutorService, cuya
función nos permite crear una tarea para que pueda ser procesada de forma
asíncrona por un hilo. El marco Fork/Join contiene un conjunto de
subprocesos, como el conjunto de procesadores disponibles en una
máquina virtual Java. Cada uno de estos hilos actuará como una "doble
cola", que es una doble cola o puente, es decir, es una estructura de datos
lineal que permite la inserción y eliminación de elementos en ambos
extremos, y esto puede considerarse como un mecanismo que permite
combinar en una sola estructura funciones de pila (estructuras LIFO) y
colas (estructuras FIFO), en otras palabras, estas estructuras (pilas y colas)
pueden implementarse fácilmente utilizando presas, para almacenar tareas
y llevarlas a ejecución.
Uso básico
El primer paso para usar el marco fork/join es escribir código que realice
un segmento del trabajo. Su código debe ser similar al siguiente
pseudocódigo:

si (mi parte del trabajo es lo suficientemente pequeña)

hacer el trabajo directamente de

lo contrario

dividir mi trabajo en dos partes

invocar las dos partes y esperar los resultados

Envuelva este código en una ForkJoinTasksubclase, normalmente usando


uno de sus tipos más especializados, ya sea RecursiveTask(que puede
devolver un resultado) o RecursiveAction.

Una vez que su ForkJoinTasksubclase esté lista, cree el objeto que


representa todo el trabajo a realizar y páselo al invoke()método de una
ForkJoinPoolinstancia.
Desenfoque para mayor claridad

Para ayudarlo a comprender cómo funciona el marco fork/join, considere el


siguiente ejemplo. Suponga que desea desenfocar una imagen. La imagen
de origen original está representada por una matriz de enteros, donde cada
entero contiene los valores de color para un solo píxel. La imagen de
destino borrosa también se representa mediante una matriz de enteros con
el mismo tamaño que la fuente.

El desenfoque se logra trabajando a través de la matriz de origen, un píxel a


la vez. Cada píxel se promedia con los píxeles que lo rodean (los
componentes rojo, verde y azul se promedian) y el resultado se coloca en la
matriz de destino. Dado que una imagen es una matriz grande, este proceso
puede llevar mucho tiempo. Puede aprovechar el procesamiento simultáneo
en sistemas multiprocesador implementando el algoritmo utilizando el
marco de bifurcación/unión. Aquí hay una posible implementación:
public class ForkBlur extiende RecursiveAction {
private int[] mSource;
privado int mStart;
longitud interna privada;
privado int[] mDestino;

// Tamaño de la ventana de procesamiento; debería ser extraño.


privado int mBlurWidth = 15;

public ForkBlur(int[] src, int start, int length, int[] dst) {


mSource = src;
mInicio = inicio;
mllongitud = longitud;
mDestino = dst;
}

protected void computeDirectly() {


int sidePixels = (mBlurWidth - 1) / 2;
for (int index = mStart; index < mStart + mLength; index++) {
// Calcular promedio.
flotante rt = 0, gt = 0, bt = 0;
for (int mi = -sidePixels; mi <= sidePixels; mi++) {
int mindex = Math.min(Math.max(mi + index, 0),
mSource.length - 1);
int píxel = mFuente[mindex];
rt += (flotante)((pixel & 0x00ff0000) >> 16)
/ mBlurWidth;
gt += (flotante)((pixel & 0x0000ff00) >> 8)
/ mBlurWidth;
bt += (flotante)((pixel & 0x000000ff) >> 0)
/ mBlurWidth;
}
// Reensamblar el píxel de destino.
int dpixel = (0xff000000 ) |
(((int)rt) << 16) |
(((int)gt) << 8) |
(((int)bt) << 0);
mDestino[índice] = dpixel;
}
}

Ahora implementa el método abstracto compute(), que realiza el


desenfoque directamente o lo divide en dos tareas más pequeñas. Un
umbral de longitud de matriz simple ayuda a determinar si el trabajo se
realiza o se divide.
protegido estático int sThreshold = 100000;

void protected compute() {


if (mLength < sThreshold) {
computeDirectly();
devolver;
}

int split = mLength / 2;

invoqueTodo(new ForkBlur(mSource, mStart, split, mDestination),


new ForkBlur(mSource, mStart + split, mLength - split,
mDestination));
}
Si los métodos anteriores están en una subclase de la RecursiveActionclase,
configurar la tarea para que se ejecute en una ForkJoinPooles sencillo e
implica los siguientes pasos:

1. Cree una tarea que represente todo el trabajo a realizar.

// los píxeles de la imagen de origen están en src

// los píxeles de la imagen de destino están en dst

ForkBlur fb = new ForkBlur(src, 0, src.length, dst);

2. Cree el ForkJoinPoolque ejecutará la tarea.

ForkJoinPool pool = new ForkJoinPool();

3. Ejecute la tarea.

grupo.invocar(fb);
Para obtener el código fuente completo, incluido algún código adicional
que crea el archivo de imagen de destino, consulte el ForkBlurejemplo.

Práctica con más tendencia: utilización de la Stream API

Esta sería la mejor solución por ahora. En un bloque de código de no más


de 5 líneas, "establecemos" la propiedad pin, y luego la centramos a través
de la programación funcional a través de esta hermosa biblioteca. Es
importante decir que cuando se trata de la lista de dispositivos

Como flujo de datos paralelo (método paralelo), utiliza internamente el


marco Fork/Join.

Librería Stream de Java 8 para obtener el promedio de batería de una lista


de dispositivos. Lo que hace internamente esta librería Stream de Java sería
lo siguiente:
La Imagen les muestra cómo funcionaría el método parallelStream() de la
librería Stream de Java 8 para procesar datos en paralelo. Simplemente
toma la lista de dispositivos y pone las tareas en cada hilo (Thread) y al
final obtiene el resultado.

Práctica tradicional: utilizar un foreach secuencial


A pesar de que la programación funcional ha ganado un inmenso poder y
progreso, sigue siendo popular o veremos el uso de algoritmos secuenciales
con mucha frecuencia en el sistema heredado.

Algoritmo secuencial para obtener el promedio de batería de una lista de dispositivos.


Esta imagen de referencia muestra como funcionaría el algoritmo de
obtener el promedio corriendo en un único hilo (Thread).

Práctica objetivo: utilización del framework Fork/Join


Primero, se creó una capa llamada Sumatoriabateriaforkjoin. En esta clase,
Logic nos permitirá decidir si tenemos que dividir la batería del dispositivo
a la lista más pequeña o agregar la lista actual. Además, en esta clase

Extenderemos la clase RecursiveAction correspondiente a una tarea


ForkJoin.

Si nos centramos en el método compute(), podemos ver la referencia del


siguiente ejemplo propuesto en la documentación de Java. En este método
pondremos la condición de que si la lista es suficientemente grande,
entonces que la divida en dos nodos cada uno con la mitad de elementos y
así sucesivamente hasta que tenga sublistas más pequeñas que cumplan con
el límite establecido por el programador en la variable threshold.
if(son pocos elementos en la lista)

sumar las baterías de los elementos en la lista

else

dividir mi trabajo en "sublistas" más pequeñas

invocar el trabajo de las "sublistas" más pequeñas y esperar

el resultado

En este caso, tenemos un límite (theshold) de procesamiento de 500,000


elementos por cada subtarea. Si la lista tiene un número de elementos
mayor al límite, los va a dividir en subtareas hasta cumplir esa condición
(fork). Una vez se cumpla la condición, hará la sumatoria para esos
elementos y finalmente reunirá los resultados de todas las subtareas en un
único resultado (join). Ver el siguiente código Java:
Es entonces, como vemos en el código anterior, de que si la cantidad de
elementos a procesar es mayor al límite (threshold), cogería el camino del
else, creando dos nuevos nodos izquierdo y derecho, y así sucesivamente
como se explicó anteriormente hasta tener sublistas pequeñas y poder hacer
la suma paralelamente en los diferentes nodos creados. Ver la figura #1.

En segundo lugar, crearemos un pool de hilos con los procesadores


disponibles en tiempo de ejecución. Una vez creado, le pasaremos a nuestro
pool de hilos la clase que creamos anteriormente cuya función es dividir y
ejecutar nodos paralelamente.

Esta pieza de código será la encargada de crear un objeto mediante el cual


podremos usar para invocar nuestra clase SumatoriaBateriaForkJoin en un
pool o conjunto de hilos disponibles en nuestra máquina.

El método invoke lo que hará es ejecutar la tarea que definimos en nuestra


clase que implementa la libreria del framework Fork/Join
(SumatoriaBateriaForkJoin). Esta tarea, es entonces, lo que definimos en el
método compute(). Una vez invocamos nuestra clase, empieza la magia
como lo veremos a continuación.
Conclusión

El framework fork/join nos brinda una mayor asistencia al


momento de conseguir mejores tiempos de reacción, aunque su
elaboración hace un tanto más difícil la tarea del programador.
Al menos con la librería java stream, usa el patrón fork/join,
resulta que tiene mayores tiempos de reacción; Esto tal vez se
pueda deber a que la biblioteca administra de manera automática
la cantidad de procesos que se usarán en un grupo de subprocesos
de marco llamado forkjoinpool.commonpool(), por lo que esto es
algo de lo que no debemos preocuparnos.
Al implementar el marco fork/join, podremos controlar la
cantidad de subtareas y nuestro grupo de subprocesos.
Al comprobar cómo equilibrar una tarea es una buena solución
para reducir su tiempo de ejecución, siempre que se pueda dividir
en subtareas independientes, siempre que el hardware lo permita.
Fork Join permite un uso más eficiente de los subprocesos de
hardware en situaciones asimétricas, lo que siempre sucederá si
tiene muchos subprocesos.
Gracias al framework Fork/Join podemos equilibrar fácilmente
cualquier tarea, siempre y cuando se cumplan las condiciones
necesarias, aunque no siempre optimizamos el tiempo de
ejecución del join. Aplicar en paralelo a la tarea que funciona en
grupos pequeños o realiza cualquier actividad en los cálculos más
bajos que puedan ser perjudiciales para el rendimiento.

También podría gustarte