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
parypseq - 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.ParallelLa 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 pseqLas 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 ysEn 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 numbersExplicació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 matrixBEjercicio 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 endConclusió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)
