diff --git a/haskell/massiv-vs-accelerate/CHANGELOG.md b/haskell/massiv-vs-accelerate/CHANGELOG.md new file mode 100644 index 0000000..930165a --- /dev/null +++ b/haskell/massiv-vs-accelerate/CHANGELOG.md @@ -0,0 +1,5 @@ +# Revision history for massiv-vs-accelerate + +## 0.1.0.0 -- YYYY-mm-dd + +* First version. Released on an unsuspecting world. diff --git a/haskell/massiv-vs-accelerate/Dockerfile b/haskell/massiv-vs-accelerate/Dockerfile new file mode 100644 index 0000000..baed0d5 --- /dev/null +++ b/haskell/massiv-vs-accelerate/Dockerfile @@ -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 \ No newline at end of file diff --git a/haskell/massiv-vs-accelerate/README.md b/haskell/massiv-vs-accelerate/README.md new file mode 100644 index 0000000..663f857 --- /dev/null +++ b/haskell/massiv-vs-accelerate/README.md @@ -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 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 "..."`. + + diff --git a/haskell/massiv-vs-accelerate/app/BasicComputation.hs b/haskell/massiv-vs-accelerate/app/BasicComputation.hs new file mode 100644 index 0000000..89a95a9 --- /dev/null +++ b/haskell/massiv-vs-accelerate/app/BasicComputation.hs @@ -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 + ] diff --git a/haskell/massiv-vs-accelerate/app/Main.hs b/haskell/massiv-vs-accelerate/app/Main.hs new file mode 100644 index 0000000..4b1161f --- /dev/null +++ b/haskell/massiv-vs-accelerate/app/Main.hs @@ -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 ] + diff --git a/haskell/massiv-vs-accelerate/cabal.project b/haskell/massiv-vs-accelerate/cabal.project new file mode 100644 index 0000000..e83fa70 --- /dev/null +++ b/haskell/massiv-vs-accelerate/cabal.project @@ -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 \ No newline at end of file diff --git a/haskell/massiv-vs-accelerate/massiv-vs-accelerate.cabal b/haskell/massiv-vs-accelerate/massiv-vs-accelerate.cabal new file mode 100644 index 0000000..de0635a --- /dev/null +++ b/haskell/massiv-vs-accelerate/massiv-vs-accelerate.cabal @@ -0,0 +1,82 @@ +cabal-version: 3.14 +-- The cabal-version field refers to the version of the .cabal specification, +-- and can be different from the cabal-install (the tool) version and the +-- Cabal (the library) version you are using. As such, the Cabal (the library) +-- version used must be equal or greater than the version stated in this field. +-- Starting from the specification version 2.2, the cabal-version field must be +-- the first thing in the cabal file. + +-- Initial package description 'massiv-vs-accelerate' generated by +-- 'cabal init'. For further documentation, see: +-- http://haskell.org/cabal/users-guide/ +-- +-- The name of the package. +name: massiv-vs-accelerate + +-- The package version. +-- See the Haskell package versioning policy (PVP) for standards +-- guiding when and how versions should be incremented. +-- https://pvp.haskell.org +-- PVP summary: +-+------- breaking API changes +-- | | +----- non-breaking API additions +-- | | | +--- code changes with no API change +version: 0.1.0.0 + +-- A short (one-line) description of the package. +-- synopsis: + +-- A longer description of the package. +-- description: + +-- The license under which the package is released. +license: NONE + +-- The package author(s). +author: Alexander Coronel + +-- An email address to which users can send suggestions, bug reports, and patches. +maintainer: alexcoronel1995@gmail.com + +-- A copyright notice. +-- copyright: +build-type: Simple + +-- Extra doc files to be distributed with the package, such as a CHANGELOG or a README. +extra-doc-files: CHANGELOG.md + +-- Extra source files to be distributed with the package, such as examples, or a tutorial module. +-- extra-source-files: + +common warnings + ghc-options: -Wall + + +executable massiv-vs-accelerate + -- Import common warning flags. + import: warnings + + -- .hs or .lhs file containing the Main module. + main-is: Main.hs + + -- Modules included in this executable, other than Main. + other-modules: BasicComputation + + -- LANGUAGE extensions used by modules in this package. + -- other-extensions: + + -- Other library packages from which modules are imported. + build-depends: + base ^>=4.18.3.0, + massiv, + accelerate, + accelerate-llvm-native, + vector, + criterion, + optparse-applicative + + -- Directories containing source files. + hs-source-dirs: app + + -- Base language which the package is written in. + default-language: Haskell2010 + ghc-options: -threaded -O2 -with-rtsopts=-N