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

  1. Introducción al Paralelismo
  2. Estrategias de Evaluación
  3. Uso de la Biblioteca Control.Parallel
  4. Paralelismo con par y pseq
  5. Ejemplo Práctico
  6. Ejercicios

  1. 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.

  1. 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.

  1. Uso de la Biblioteca 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.

cabal install parallel

Importación

import Control.Parallel

  1. Paralelismo con 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.

par :: a -> b -> b

pseq

La función pseq fuerza la evaluación de su primer argumento antes de evaluar el segundo.

pseq :: a -> b -> b

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.

  1. 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

  1. División de la Lista: La lista se divide en dos mitades.
  2. Cálculo en Paralelo: Se calcula la suma de los cuadrados de cada mitad en paralelo.
  3. Combinación de Resultados: Los resultados se combinan para obtener la suma total.

  1. 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.

© Copyright 2024. Todos los derechos reservados