Parallelism in Haskell allows you to perform multiple computations simultaneously, leveraging multi-core processors to improve performance. This module will cover the basics of parallelism, including key concepts, practical examples, and exercises to help you understand and implement parallelism in Haskell.

Key Concepts

  1. Parallelism vs. Concurrency:

    • Parallelism: Performing multiple computations at the same time.
    • Concurrency: Structuring a program to handle multiple tasks that may not necessarily run simultaneously.
  2. Strategies:

    • Strategies are a way to control the evaluation of parallel computations in Haskell.
  3. Par and pseq:

    • par: Sparks a parallel computation.
    • pseq: Ensures that one computation is evaluated before another.
  4. Parallel Libraries:

    • Control.Parallel: Basic parallelism constructs.
    • Control.Parallel.Strategies: Higher-level strategies for parallelism.

Practical Examples

Example 1: Basic Parallelism with par and pseq

import Control.Parallel (par, pseq)

-- A simple function to compute the sum of two lists in parallel
parallelSum :: [Int] -> [Int] -> Int
parallelSum xs ys = 
    let sum1 = sum xs
        sum2 = sum ys
    in sum1 `par` (sum2 `pseq` (sum1 + sum2))

main :: IO ()
main = do
    let list1 = [1..1000000]
        list2 = [1000001..2000000]
    print $ parallelSum(list1, list2)

Explanation:

  • par sparks the evaluation of sum1 in parallel.
  • pseq ensures that sum2 is evaluated before combining the results.

Example 2: Using Strategies

import Control.Parallel.Strategies (rpar, rseq, runEval)

-- A function to compute the sum of two lists using strategies
parallelSumStrategies :: [Int] -> [Int] -> Int
parallelSumStrategies xs ys = runEval $ do
    sum1 <- rpar (sum xs)
    sum2 <- rseq (sum ys)
    rseq sum1
    return (sum1 + sum2)

main :: IO ()
main = do
    let list1 = [1..1000000]
        list2 = [1000001..2000000]
    print $ parallelSumStrategies list1 list2

Explanation:

  • rpar sparks a parallel computation.
  • rseq ensures sequential evaluation.
  • runEval runs the parallel computations and returns the result.

Exercises

Exercise 1: Parallel Map

Implement a parallel version of the map function.

import Control.Parallel.Strategies (parList, rpar, using)

parallelMap :: (a -> b) -> [a] -> [b]
parallelMap f xs = map f xs `using` parList rpar

main :: IO ()
main = do
    let list = [1..1000000]
    print $ parallelMap (+1) list

Solution:

  • Use parList and rpar to parallelize the map function.

Exercise 2: Parallel Fibonacci

Compute the nth Fibonacci number in parallel.

import Control.Parallel (par, pseq)

fib :: Int -> Int
fib 0 = 0
fib 1 = 1
fib n = let x = fib (n-1)
            y = fib (n-2)
        in x `par` (y `pseq` (x + y))

main :: IO ()
main = print $ fib 30

Solution:

  • Use par and pseq to parallelize the Fibonacci computation.

Common Mistakes and Tips

  • Overhead: Parallelism introduces overhead. Ensure that the computations are large enough to benefit from parallelism.
  • Granularity: Too fine-grained parallelism can lead to inefficiency. Balance the granularity of tasks.
  • Lazy Evaluation: Haskell's lazy evaluation can interfere with parallelism. Use pseq to force evaluation order when necessary.

Conclusion

In this section, you learned about parallelism in Haskell, including key concepts, practical examples, and exercises. You now have the tools to implement parallel computations in Haskell, leveraging multi-core processors to improve performance. In the next section, we will explore the Foreign Function Interface (FFI) in Haskell.

© Copyright 2024. All rights reserved