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..839ac11 --- /dev/null +++ b/haskell/massiv-vs-accelerate/Dockerfile @@ -0,0 +1,58 @@ +# 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 \ + clang \ + 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 symlinks so GHC and Accelerate tools exist cleanly in the PATH +# We use -sf to force overwrite existing default paths safely +RUN ln -sf /usr/bin/clang-15 /usr/bin/clang && \ + ln -sf /usr/bin/opt-15 /usr/bin/opt && \ + ln -sf /usr/bin/llc-15 /usr/bin/llc && \ + ln -sf /usr/bin/llvm-config-15 /usr/bin/llvm-config + +# 2. Set the environment variables globally for the container +ENV LLVM_CONFIG=/usr/bin/llvm-config +ENV ACCELERATE_LLVM_CLANG_PATH=/usr/bin/clang + +# Set up the working directory inside the container +WORKDIR /app + +# Update the package list +RUN cabal update + +# Copy the cabal configuration first to cache dependencies (optimization step) +COPY *.cabal cabal.project /app/ + +# Copy the rest of the application source files +COPY . /app + +# Pre-compile the project inside the container image +RUN cabal build + +# Set the default entry point to execute your compiled benchmark suite +ENTRYPOINT ["cabal", "run", "massiv-vs-accelerate", "--"] \ 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..99f77db --- /dev/null +++ b/haskell/massiv-vs-accelerate/README.md @@ -0,0 +1,97 @@ +# 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) +- `src/` — Library and dependency sources (submodules) +- `Dockerfile` — Containerized build and run environment matching target LLVM pipelines +- `cabal.project`, `massiv-vs-accelerate.cabal` — Cabal build configuration + +## Prerequisites + +- [Docker](https://www.docker.com/get-started) (highly recommended for ARM64 toolchain compatibility and reproducibility) +- Or: [GHC 9.6+](https://www.haskell.org/ghc/), [Cabal](https://www.haskell.org/cabal/), and **LLVM 15** installed with `clang`/`opt`/`llc` binaries mapped to your system path. + +## Quick Start (Docker) + +1. **Build the containerized environment:** + +```sh + docker build -t massiv-vs-accelerate . +``` + +2. **Run isolated library benchmarks:** +Pass the specific operation name directly to the container runner to evaluate both libraries back-to-back: + +Massiv Stencil Only: +```bash +docker run --rm massiv-vs-accelerate stencil-massiv +``` +Accelerate Stencil Only: + +```bash +docker run --rm massiv-vs-accelerate stencil-accel +``` + +3. **Passing Criterion and GHC Runtime (+RTS) Flags:** + +Because the container features an automated entrypoint wrapper, you can append reporting flags or memory profiling switches straight to your commands: + +```bash +# Export an interactive HTML chart to your current host directory +docker run --rm -v $(pwd):/app/out massiv-vs-accelerate stencil --output out/results.html + +# Inspect memory allocation and GC productivity profiles (+RTS -s) +docker run --rm massiv-vs-accelerate stencil-massiv +RTS -s + +``` + +4. **Manual Setup (Without Docker on Mac)** +Configure dependencies: + +Ensure llvm-15 toolchains are [globally](https://www.acceleratehs.org/get-started.html) exposed on your machine. + +1. Install llvm dependencies + +brew install llvm@15 libffi pkg-config + +2. Expose the New Binaries to Your Local Terminal +```bash +# 1. Force your active terminal context to prioritize LLVM 15 +export PATH="/opt/homebrew/opt/llvm@15/bin:$PATH" + +# 2. Bind development header search flags +export LDFLAGS="-L/opt/homebrew/opt/llvm@15/lib" +export CPPFLAGS="-I/opt/homebrew/opt/llvm@15/include" + +# 3. Explicitly link tool locations for the GHC and Accelerate compiler pipelines +export LLVM_CONFIG="/opt/homebrew/opt/llvm@15/bin/llvm-config" +export ACCELERATE_LLVM_CLANG_PATH="/opt/homebrew/opt/llvm@15/bin/clang" +``` + +Trigger the native build +``` +cabal clean && cabal build +``` +Run a benchmark: + +```Bash +cabal run massiv-vs-accelerate -- [map|dot|stencil|stencil-massiv|stencil-accel] [criterion flags] +``` +Example: + +```Bash +cabal run massiv-vs-accelerate -- stencil +``` +5. **Benchmark Types** +- map — Map (+1) over a large array + +- dot — Dot product of two large arrays + +- stencil — 3x3 mean blur matrix operations comparison + +- stencil-massiv — Isolated Massiv stencil loop + +- stencil-accel — Isolated Accelerate LLVM backend stencil \ No newline at end of file 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..50393fd --- /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)" $ + nf (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..3843c6d --- /dev/null +++ b/haskell/massiv-vs-accelerate/massiv-vs-accelerate.cabal @@ -0,0 +1,89 @@ +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: -O2 + -threaded + -rtsopts + -with-rtsopts=-N + -fllvm + -fno-liberate-case + -funfolding-use-threshold=100 + -pgma clang \ No newline at end of file