El paralelismo en Haskell permite ejecutar múltiples cálculos simultáneamente, aprovechando los procesadores multinúcleo para mejorar el rendimiento de las aplicaciones. En este módulo, aprenderemos los conceptos básicos del paralelismo en Haskell, cómo utilizar las bibliotecas disponibles y cómo escribir programas paralelos eficientes.
Contenido
- Introducción al Paralelismo
- Estrategias de Evaluación
- Uso de la Biblioteca
Control.Parallel
- Paralelismo con
par
ypseq
- Ejemplo Práctico
- Ejercicios
- Introducción al Paralelismo
El paralelismo se refiere a la ejecución simultánea de múltiples tareas. En Haskell, esto se puede lograr utilizando varias bibliotecas y técnicas que permiten dividir el trabajo en partes que pueden ejecutarse en paralelo.
Conceptos Clave
- Paralelismo vs Concurrencia: El paralelismo implica ejecutar múltiples tareas al mismo tiempo, mientras que la concurrencia se refiere a la gestión de múltiples tareas que pueden no ejecutarse simultáneamente.
- Granularidad: La cantidad de trabajo que se divide en tareas paralelas. La granularidad fina implica muchas tareas pequeñas, mientras que la granularidad gruesa implica menos tareas más grandes.
- Estrategias de Evaluación
Haskell utiliza la evaluación perezosa por defecto, lo que significa que las expresiones no se evalúan hasta que se necesitan. Para el paralelismo, es importante controlar cuándo y cómo se evalúan las expresiones.
Estrategias Comunes
- Eager Evaluation: Evaluar las expresiones tan pronto como sea posible.
- Lazy Evaluation: Evaluar las expresiones solo cuando se necesitan.
- Uso de la Biblioteca
Control.Parallel
Control.Parallel
La biblioteca Control.Parallel
proporciona primitivas básicas para el paralelismo en Haskell.
Instalación
Para usar Control.Parallel
, asegúrate de tener la biblioteca instalada. Puedes agregarla a tu proyecto usando cabal
o stack
.
Importación
- Paralelismo con
par
y pseq
par
y pseq
Las funciones par
y pseq
son las primitivas básicas para el paralelismo en Haskell.
par
La función par
sugiere que su primer argumento puede ser evaluado en paralelo con el segundo.
pseq
La función pseq
fuerza la evaluación de su primer argumento antes de evaluar el segundo.
Ejemplo Básico
import Control.Parallel -- Una función que calcula la suma de dos listas en paralelo parallelSum :: [Int] -> [Int] -> Int parallelSum xs ys = sumXs `par` (sumYs `pseq` (sumXs + sumYs)) where sumXs = sum xs sumYs = sum ys main :: IO () main = do let xs = [1..1000000] ys = [1000001..2000000] print $ parallelSum xs ys
En este ejemplo, sumXs
y sumYs
se calculan en paralelo, y luego se suman.
- Ejemplo Práctico
Vamos a implementar un ejemplo más complejo utilizando paralelismo para calcular la suma de cuadrados de una lista de números.
Código
import Control.Parallel -- Función para calcular la suma de cuadrados en paralelo sumOfSquares :: [Int] -> Int sumOfSquares xs = sum1 `par` (sum2 `pseq` (sum1 + sum2)) where (firstHalf, secondHalf) = splitAt (length xs `div` 2) xs sum1 = sum (map (^2) firstHalf) sum2 = sum (map (^2) secondHalf) main :: IO () main = do let numbers = [1..1000000] print $ sumOfSquares numbers
Explicación
- División de la Lista: La lista se divide en dos mitades.
- Cálculo en Paralelo: Se calcula la suma de los cuadrados de cada mitad en paralelo.
- Combinación de Resultados: Los resultados se combinan para obtener la suma total.
- Ejercicios
Ejercicio 1: Paralelismo en la Multiplicación de Matrices
Implementa una función que multiplique dos matrices en paralelo.
Solución
import Control.Parallel type Matrix = [[Int]] -- Multiplicación de matrices en paralelo parallelMatrixMult :: Matrix -> Matrix -> Matrix parallelMatrixMult a b = [[sum $ zipWith (*) ar bc | bc <- transpose b] | ar <- a] where transpose ([]:_) = [] transpose x = (map head x) : transpose (map tail x) main :: IO () main = do let matrixA = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] matrixB = [[9, 8, 7], [6, 5, 4], [3, 2, 1]] print $ parallelMatrixMult matrixA matrixB
Ejercicio 2: Paralelismo en la Búsqueda de Primos
Implementa una función que encuentre todos los números primos en un rango dado en paralelo.
Solución
import Control.Parallel -- Función para verificar si un número es primo isPrime :: Int -> Bool isPrime n = n > 1 && all (\x -> n `mod` x /= 0) [2..(n-1)] -- Función para encontrar primos en paralelo parallelPrimes :: Int -> Int -> [Int] parallelPrimes start end = primes1 `par` (primes2 `pseq` (primes1 ++ primes2)) where (firstHalf, secondHalf) = splitAt ((end - start) `div` 2) [start..end] primes1 = filter isPrime firstHalf primes2 = filter isPrime secondHalf main :: IO () main = do let start = 1 end = 10000 print $ parallelPrimes start end
Conclusión
En esta sección, hemos aprendido los conceptos básicos del paralelismo en Haskell, cómo utilizar las primitivas par
y pseq
, y cómo aplicar estas técnicas en ejemplos prácticos. El paralelismo puede mejorar significativamente el rendimiento de las aplicaciones, especialmente en sistemas con múltiples núcleos de procesamiento. En el siguiente módulo, exploraremos la concurrencia en Haskell, que nos permitirá gestionar múltiples tareas de manera eficiente.
Curso de Programación en Haskell
Módulo 1: Introducción a Haskell
- ¿Qué es Haskell?
- Configuración del Entorno de Haskell
- Sintaxis Básica y Hola Mundo
- Haskell REPL (GHCi)