Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Speed up the Haskell implementation #16

Merged
merged 8 commits into from Sep 26, 2021
Merged

Conversation

maoe
Copy link
Contributor

@maoe maoe commented Sep 21, 2021

This PR speeds up the Haskell implementation significantly using the LLVM backend.

Yesterday I saw someone wondering why the Haskell implementation was significantly slower than the other implementations in this test, so I took a stab at optimizing it.

The key points are:

  • Use unboxed vectors instead of (singly linked) lists
  • Replace the div/mod functions, which are known to be slow, with quot/rem
  • Switch from the native code generator to the LLVM backend, which is known to generate faster code at expense of slower compilation

Here's the result on my MacBook Pro 16'':

% hyperfine {c,cpp,rust,haskell}/main
Benchmark #1: c/main
  Time (mean ± σ):      2.327 s ±  0.051 s    [User: 2.302 s, System: 0.018 s]
  Range (min … max):    2.272 s …  2.418 s    10 runs

Benchmark #2: cpp/main
  Time (mean ± σ):      3.556 s ±  0.069 s    [User: 3.519 s, System: 0.028 s]
  Range (min … max):    3.470 s …  3.688 s    10 runs

Benchmark #3: rust/main
  Time (mean ± σ):      2.633 s ±  0.079 s    [User: 2.584 s, System: 0.019 s]
  Range (min … max):    2.582 s …  2.848 s    10 runs

Benchmark #4: haskell/main
  Time (mean ± σ):      3.105 s ±  0.058 s    [User: 3.049 s, System: 0.028 s]
  Range (min … max):    3.064 s …  3.264 s    10 runs

Summary
  'c/main' ran
    1.13 ± 0.04 times faster than 'rust/main'
    1.33 ± 0.04 times faster than 'haskell/main'
    1.53 ± 0.04 times faster than 'cpp/main'

System prerequisites are:

  • LLVM >= 9 && < 13
  • GHC 8.10.7 and cabal-install are installed using ghcup
    • Install ghcup then ghcup install ghc 8.10.7 && ghcup install cabal latest

As noted in the commit message, the Makefile expects that llc and opt are available in $PATH. If not please set OPT and LLC accordingly when running make.

It also modifies isMunchausen to look more like the other
implementations.
With this change the Haskell implementation is now only 29% slower than the Rust
implementation:

% hyperfine ./haskell/dist-newstyle/build/x86_64-osx/ghc-8.10.7/haskell-0.1.0.0/x/haskell/opt/build/haskell/haskell ./rust/target/release/rust
Benchmark jabbalaci#1: ./haskell/dist-newstyle/build/x86_64-osx/ghc-8.10.7/haskell-0.1.0.0/x/haskell/opt/build/haskell/haskell
  Time (mean ± σ):      3.277 s ±  0.080 s    [User: 3.241 s, System: 0.026 s]
  Range (min … max):    3.170 s …  3.403 s    10 runs

Benchmark jabbalaci#2: ./rust/target/release/rust
  Time (mean ± σ):      2.543 s ±  0.073 s    [User: 2.522 s, System: 0.017 s]
  Range (min … max):    2.443 s …  2.669 s    10 runs

Summary
  './rust/target/release/rust' ran
    1.29 ± 0.05 times faster than './haskell/dist-newstyle/build/x86_64-osx/ghc-8.10.7/haskell-0.1.0.0/x/haskell/opt/build/haskell/haskell'
We know the indexing is always safe because the size of the cache
is known.

% hyperfine ./haskell/dist-newstyle/build/x86_64-osx/ghc-8.10.7/haskell-0.1.0.0/x/haskell/opt/build/haskell/haskell ./rust/target/release/rust
Benchmark jabbalaci#1: ./haskell/dist-newstyle/build/x86_64-osx/ghc-8.10.7/haskell-0.1.0.0/x/haskell/opt/build/haskell/haskell
  Time (mean ± σ):      2.959 s ±  0.015 s    [User: 2.929 s, System: 0.023 s]
  Range (min … max):    2.937 s …  2.976 s    10 runs

Benchmark jabbalaci#2: ./rust/target/release/rust
  Time (mean ± σ):      2.466 s ±  0.019 s    [User: 2.449 s, System: 0.014 s]
  Range (min … max):    2.440 s …  2.494 s    10 runs

Summary
  './rust/target/release/rust' ran
    1.20 ± 0.01 times faster than './haskell/dist-newstyle/build/x86_64-osx/ghc-8.10.7/haskell-0.1.0.0/x/haskell/opt/build/haskell/haskell'
The llc and opt commands from LLVM 9-12 need to be available. If
they're not in $PATH, set OPT and/or LLC env variables:

  make OPT=$(brew --prefix llvm@12)/bin/opt LLC=$(brew --prefix llvm@12)/bin/llc
@jabbalaci jabbalaci merged commit 3d9844c into jabbalaci:master Sep 26, 2021
@maoe maoe deleted the haskell branch September 26, 2021 21:31
@jabbalaci
Copy link
Owner

Thanks. Nice optimization! Please check the dir. haskell/, I made some changes. I changed the any.base ==4.14.3.0 to any.base ==4.14.2.0 for instance, otherwise the compilation failed on my system.

@maoe
Copy link
Contributor Author

maoe commented Sep 26, 2021

I guess that's because you're probably not using GHC 8.10.7. If that change works for you, I think it's fine.

@maoe
Copy link
Contributor Author

maoe commented Sep 27, 2021

Ah apparently you're using 8.10.5 according to the updated README. In that case we should update

ghcup set ghc 8.10.7
accordingly.

@jabbalaci
Copy link
Owner

Found it. In the PATH, I had to put ~/.ghcup/bin before /bin, and now it finds GHC 8.10.7 . I restored any.base ==4.14.3.0 and re-run the tests. There's no change in the runtime but the EXE is much bigger than the EXE produced by 8.10.5 . Thanks for the contribution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants