Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions haskell/massiv-vs-accelerate/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Revision history for massiv-vs-accelerate

## 0.1.0.0 -- YYYY-mm-dd

* First version. Released on an unsuspecting world.
37 changes: 37 additions & 0 deletions haskell/massiv-vs-accelerate/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# We use GHC 9.6 as our base
FROM haskell:9.6-slim

# Install wget and gnupg to add the LLVM repository
RUN apt-get update && apt-get install -y \
wget \
gnupg \
lsb-release \
software-properties-common \
&& rm -rf /var/lib/apt/lists/*

# Use the official LLVM setup script to install LLVM 15
# This handles the repository addition for any Debian version (Buster/Bullseye/Bookworm)
RUN wget https://apt.llvm.org/llvm.sh \
&& chmod +x llvm.sh \
&& ./llvm.sh 15 \
&& apt-get install -y \
llvm-15-dev \
libffi-dev \
libedit-dev \
libfftw3-dev \
pkg-config \
zlib1g-dev \
g++ \
make \
&& rm -rf /var/lib/apt/lists/*

# THE FIXES:
# 1. Create the symlink so 'clang' exists in the PATH
RUN ln -s /usr/bin/clang-15 /usr/bin/clang

# 2. Set the environment variables globally for the container
ENV LLVM_CONFIG=/usr/bin/llvm-config-15
ENV ACCELERATE_LLVM_CLANG_PATH=/usr/bin/clang-15

WORKDIR /app
RUN cabal update
133 changes: 133 additions & 0 deletions haskell/massiv-vs-accelerate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Massiv vs Accelerate Benchmark Suite

This project benchmarks and compares the performance of [Massiv](https://github.com/lehins/massiv) and [Accelerate](https://github.com/AccelerateHS/accelerate) Haskell array libraries using a variety of operations (map, dot product, stencil/mean blur).

## Project Structure

- `app/Main.hs` — Main benchmarking entry point (Criterion-based)
- `app/BasicComputation.hs` — Additional benchmark/support module
- `Dockerfile` — Containerized build and run environment
- `cabal.project`, `massiv-vs-accelerate.cabal` — Cabal build configuration

## Prerequisites

- [Docker](https://www.docker.com/get-started) (recommended for reproducibility)
- Or: [GHC](https://www.haskell.org/ghc/), [Cabal](https://www.haskell.org/cabal/)

## Quick Start (Docker)

The `Dockerfile` provides a reproducible Haskell/LLVM build environment.

Recommended workflow: run an interactive container with the project mounted at `/app`, then execute benchmarks from inside the container.

1. **Build the Docker image:**

```sh
docker build -t massiv-vs-accelerate .
```

2. **Start an interactive container:**

```sh
docker run --rm -it \
--name massiv-vs-accelerate-dev \
-v "$PWD":/app \
-w /app \
massiv-vs-accelerate \
bash
```

3. **Run benchmarks inside the container shell:**

```sh
cabal run massiv-vs-accelerate -- stencil
```

Example with RTS stats:

```sh
cabal run massiv-vs-accelerate -- stencil-massiv +RTS -s
```

4. **If the container is already running, attach with `exec`:**

```sh
docker exec -it massiv-vs-accelerate-dev bash
```

Or by container id:

```sh
docker exec -it <container_id> bash
```

You can also run other benchmarks:

- Map: `cabal run massiv-vs-accelerate -- map`
- Dot Product: `cabal run massiv-vs-accelerate -- dot`
- Stencil (Massiv only): `cabal run massiv-vs-accelerate -- stencil-massiv`
- Stencil (Accelerate only): `cabal run massiv-vs-accelerate -- stencil-accel`

You can pass additional [Criterion](https://hackage.haskell.org/package/criterion) flags after the benchmark type, e.g.:

```sh
cabal run massiv-vs-accelerate -- stencil --output results.html
```

One-shot alternative (without opening a shell):

```sh
docker run --rm -v "$PWD":/app -w /app massiv-vs-accelerate \
bash -lc "cabal run massiv-vs-accelerate -- stencil"
```

## Manual Setup (Without Docker)

1. **Install dependencies:**

```sh
cabal update
cabal build --only-dependencies
```

2. **Build the project:**

```sh
cabal build
```

3. **Run a benchmark:**

```sh
cabal run massiv-vs-accelerate -- [map|dot|stencil|stencil-massiv|stencil-accel] [criterion flags]
```

Example:

```sh
cabal run massiv-vs-accelerate -- stencil
```

## Benchmark Types

- `map` — Map (+1) over a large array
- `dot` — Dot product of two large arrays
- `stencil` — 3x3 mean blur (Massiv vs Accelerate)
- `stencil-massiv` — Only Massiv stencil benchmark
- `stencil-accel` — Only Accelerate stencil benchmark

## Notes

- For large benchmarks, you may want to pass RTS flags for memory and GC stats, e.g.:
```sh
cabal run massiv-vs-accelerate -- stencil +RTS -s
```
- The Docker image uses `haskell:9.6-slim`, installs LLVM 15, and sets:
- `LLVM_CONFIG=/usr/bin/llvm-config-15`
- `ACCELERATE_LLVM_CLANG_PATH=/usr/bin/clang-15`
- `cabal.project` pins Accelerate dependencies to GitHub `master` branches (`accelerate` and `accelerate-llvm` repos).
- If you see `The program 'ghc' ... could not be found`, the command is likely running outside the Docker environment. Confirm you are either:
- inside the container shell (`root@...:/app#`), or
- invoking commands through `docker run ... bash -lc "..."`.


31 changes: 31 additions & 0 deletions haskell/massiv-vs-accelerate/app/BasicComputation.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module BasicComputation (
benchBasicComputation,
BenchArgs(..)
) where

import Criterion.Main (Benchmark, bench, nf, whnf)
import qualified Data.Massiv.Array as M
import qualified Data.Array.Accelerate as A
import qualified Data.Array.Accelerate.LLVM.Native as Native

-- | Arguments for benchmarking basic computation
-- Add more fields as needed for future extensibility
-- For now, only size is used

data BenchArgs = BenchArgs
{ size :: Int
}

benchBasicComputation :: BenchArgs -> [Benchmark]
benchBasicComputation args =
let n = size args
-- Massiv Setup (Parallel Array)
massivArr = M.makeArray M.Par (M.Sz1 n) id :: M.Array M.P M.Ix1 Int
-- Accelerate Setup (Expression Graph)
accelArr = A.fromList (A.Z A.:. n) [0..n-1] :: A.Vector Int
in
[ bench "Massiv (CPU Parallel)" $
nf (M.computeAs M.P . M.map (+1)) massivArr
, bench "Accelerate (LLVM Native)" $
whnf (Native.run . A.map (+1) . A.use) accelArr
]
129 changes: 129 additions & 0 deletions haskell/massiv-vs-accelerate/app/Main.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
module Main where

import Criterion.Main
import System.Environment (getArgs, withArgs)
import qualified Data.Massiv.Array as M
import qualified Data.Array.Accelerate as A
import qualified Data.Array.Accelerate.LLVM.Native as Native


-- | 3x3 mean (average) stencil for 2D arrays in Massiv.
--
-- This stencil computes the average of a 3x3 neighborhood around each element.
-- It is used for mean blurring (smoothing) operations on 2D arrays.
--
-- The stencil uses zero-padding (see usage with 'M.Fill 0.0') at the boundaries.
--
-- Example usage:
--
-- > M.mapStencil (M.Fill 0.0) massivMeanStencil arr
--
-- Where 'arr' is a 2D Massiv array of type 'M.Array M.P M.Ix2 Double'.
massivMeanStencil :: M.Stencil M.Ix2 Double Double
massivMeanStencil = M.makeStencil (M.Sz2 3 3) (1 M.:. 1) $ \get ->
( get (-1 M.:. -1) + get (-1 M.:. 0) + get (-1 M.:. 1)
+ get ( 0 M.:. -1) + get ( 0 M.:. 0) + get ( 0 M.:. 1)
+ get ( 1 M.:. -1) + get ( 1 M.:. 0) + get ( 1 M.:. 1)
) / 9

-- | 3x3 mean (average) stencil function for Accelerate.
--
-- This function computes the average of a 3x3 neighborhood for use with Accelerate's 'stencil' operation.
-- It is typically used for mean blurring (smoothing) on 2D arrays.
--
-- The input is a 3x3 tuple of values (top-left, top-center, top-right, etc.).
--
-- Example usage:
--
-- > A.stencil accelMeanStencil A.clamp arr
--
-- Where 'arr' is an Accelerate 2D array of type 'A.Array A.DIM2 Double'.
accelMeanStencil :: A.Stencil3x3 Double -> A.Exp Double
accelMeanStencil ((tl, tc, tr),
(ml, mc, mr),
(bl, bc, br)) = (tl + tc + tr + ml + mc + mr + bl + bc + br) / 9

size :: Int
size = 10000000

main :: IO ()
main = do
allArgs <- getArgs
case allArgs of
(testType:rest) ->
-- 'withArgs' replaces the global command-line arguments
-- for the duration of the provided IO action.
withArgs rest $ case testType of
"map" -> runMapBenchmarks
"dot" -> runDotBenchmarks
"stencil-massiv" -> runMassivOnly -- Run only the Massiv stencil benchmark with +RTS -s flag
"stencil-accel" -> runAccelOnly -- Run only the Accelerate stencil benchmark with +RTS -s flag
"stencil" -> runStencilBenchmarks
_ -> putStrLn "Unknown test type. Use 'map', 'dot', or 'stencil'."
_ -> putStrLn "Usage: cabal run massiv-vs-accelerate -- [map|dot|stencil] [criterion flags]"

runMapBenchmarks :: IO ()
runMapBenchmarks = do
let massivArr = M.makeArray M.Par (M.Sz1 size) id :: M.Array M.P M.Ix1 Int
let accelArr = A.fromList (A.Z A.:. size) [0..size-1] :: A.Vector Int

defaultMain [
bgroup "Map (+1)" [
bench "Massiv (CPU Parallel)" $
nf (M.computeAs M.P . M.map (+1)) massivArr,
bench "Accelerate (LLVM Native)" $
whnf (Native.run . A.map (+1) . A.use) accelArr
]
]

runDotBenchmarks :: IO ()
runDotBenchmarks = do
let m1 = M.makeArray M.Par (M.Sz1 size) (const 2) :: M.Array M.P M.Ix1 Int
let m2 = M.makeArray M.Par (M.Sz1 size) (const 3) :: M.Array M.P M.Ix1 Int

let a1 = A.fromList (A.Z A.:. size) (replicate size 2) :: A.Vector Int
let a2 = A.fromList (A.Z A.:. size) (replicate size 3) :: A.Vector Int

defaultMain [
bgroup "Dot Product" [
bench "Massiv (CPU Parallel)" $
nf (\(x, y) -> M.sum (M.zipWith (*) x y)) (m1, m2),
bench "Accelerate (LLVM Native)" $
whnf (\(x, y) -> Native.run (A.fold (+) 0 (A.zipWith (*) (A.use x) (A.use y)))) (a1, a2)
]
]

runStencilBenchmarks :: IO ()
runStencilBenchmarks = do
let side = 3162
let sz = M.Sz2 side side

-- Massiv Setup
let mArr = M.makeArray M.Par sz (\(i M.:. j) -> fromIntegral (i + j)) :: M.Array M.P M.Ix2 Double

-- Accelerate Setup
let aArr = A.fromList (A.Z A.:. side A.:. side) [0..(fromIntegral (side*side - 1))] :: A.Array A.DIM2 Double

defaultMain [
bgroup "3x3 Mean Blur" [
bench "Massiv (Stencil)" $
nf (M.computeAs M.P . M.mapStencil (M.Fill 0.0) massivMeanStencil) mArr,

bench "Accelerate (LLVM Stencil)" $
whnf (Native.run . A.stencil accelMeanStencil A.clamp . A.use) aArr
]
]

runMassivOnly :: IO ()
runMassivOnly = do
let side = 3162
let sz = M.Sz2 side side
let mArr = M.makeArray M.Par sz (\(i M.:. j) -> fromIntegral (i + j)) :: M.Array M.P M.Ix2 Double
defaultMain [ bench "Massiv (Isolated)" $ nf (M.computeAs M.P . M.mapStencil (M.Fill 0.0) massivMeanStencil) mArr ]

runAccelOnly :: IO ()
runAccelOnly = do
let side = 3162
let aArr = A.fromList (A.Z A.:. side A.:. side) [0..(fromIntegral (side*side - 1))] :: A.Array A.DIM2 Double
defaultMain [ bench "Accelerate (Isolated)" $ whnf (Native.run . A.stencil accelMeanStencil A.clamp . A.use) aArr ]

22 changes: 22 additions & 0 deletions haskell/massiv-vs-accelerate/cabal.project
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
packages: .

source-repository-package
type: git
location: https://github.com/AccelerateHS/accelerate.git
tag: master

source-repository-package
type: git
location: https://github.com/AccelerateHS/accelerate-llvm.git
tag: master
-- This tells Cabal to look inside these specific folders for the .cabal files
subdir:
accelerate-llvm
accelerate-llvm-native

allow-newer:
*:bytestring,
*:base,
*:template-haskell,
llvm-hs:llvm-hs-pure,
llvm-hs-pure:base
Loading