Performant Polymorphism: Rewrite Rules in Haskell

Performant Polymorphism: Rewrite Rules in Haskell

GHC usually does an excellent job of transforming well written
Haskell code into efficient machine code, but sometimes "fast" is
not "fast enough". Common optimisation techniques when dealing with
concrete data types often do not apply to polymorphic data and
functions. A concise, generic algorithm may perform poorly for some
types, but providing a faster version with a less polymorphic type
sacrifices reusability and parametricity! What's a principled
programmer to do?

Fortunately GHC has got your back here, too. In this talk we will
learn about GHC's *rewrite rules* feature, which can be used for
substituting alternative, better performing implementations of
polymorphic functions at particular (less polymorphic) types,
without changing the type signature that users see, preserving reuse
and parametricity. We will see also how to define transformation
rules that employ theorems (free or otherwise) to optimise programs.

We will also briefly examine how rules are applied by observing the
firing of rules and changes effected in the produced Core (GHC's
fully desugared intermediate language), and see how to control the
order of inlining and rewrite rules to achieve the desired
outcome.

Finally, we'll look at a real-world example of how rewrite rules are
used in the 'fresnel'[1] library, a unified parser-printer
combinator library based on the 'Cons' abstraction from the 'lens'
library, to dramatically speed up printing for certain output types.

This will be a hands-on talk with live coding, benchmarking and
profiling (no optimising without metrics!) and Core spelunking.
Audience members familiar with Haskell should expect to learn some
basics of Haskell benchmarking and profiling, gain an understanding
of when and how to use rewrite rules in their own code, and
walk out feeling comfortable (or less trepidatious, perhaps) about
reading and analysing their programs' Core.

[1] https://github.com/frasertweedale/hs-fresnel

7c0f9b056604fe541691e18aeb679cf7?s=128

Fraser Tweedale

May 08, 2017
Tweet

Transcript

  1. Performant polymorphism Rewrite rules in Haskell Fraser Tweedale @hackuador May

    8, 2017
  2. There’s a hole in my program. . . _ ::

    B.ByteString -> L.ByteString _ :: B.ByteString -> [Word8] _ :: T.Text -> String _ :: String -> T.Text _ :: TL.Text -> T.Text
  3. There’s a hole in my program. . . L.fromStrict ::

    B.ByteString -> L.ByteString B.unpack :: B.ByteString -> [Word8] T.unpack :: T.Text -> String T.pack :: String -> T.Text TL.toStrict :: TL.Text -> T.Text
  4. Control.Lens.Cons class Cons s t a b where _Cons ::

    Prism s t (a, s) (b, t) instance ByteString ByteString Word8 Word8 instance Text Text Char Char instance [a] [b] a b cons :: Cons s s a a => a -> s -> s uncons :: Cons s s a a => s -> Maybe (a, s)
  5. recons recons :: (Cons s1 s1 a a, Cons s2

    s2 a a, AsEmpty s2) => s1 -> s2
  6. There’s a hole in my program. . . _ ::

    B.ByteString -> L.ByteString _ :: B.ByteString -> [Word8] _ :: T.Text -> String _ :: String -> T.Text _ :: TL.Text -> T.Text
  7. There’s a hole in my program. . . recons ::

    B.ByteString -> L.ByteString recons :: B.ByteString -> [Word8] recons :: T.Text -> String recons :: String -> T.Text recons :: TL.Text -> T.Text
  8. Why not concrete functions? Reuse Refactoring Readability (parametricity)

  9. Parametricity mibbup :: (Cons s1 s1 a a, Cons s2

    s2 a a, AsEmpty s2) => s1 -> s2 wossit :: [Word8] -> L.ByteString
  10. Parametricity recons :: (Cons s1 s1 a a, Cons s2

    s2 a a, AsEmpty s2) => s1 -> s2 L.pack :: [Word8] -> L.ByteString
  11. criterion whnf :: (a -> b) -> a -> Benchmarkable

    nf :: NFData b => (a -> b) -> a -> Benchmarkable bench :: String -> Benchmarkable -> Benchmark
  12. Glasgow Haskell Compiler Haskell desugars to Core Simplifier applies Core-to-Core

    transformations Core → STG → C-- Compile C-- to machine code Secrets of the Haskell Inliner: https://www.microsoft.com/en-us/research/wp-content/uploads/2016/07/inline-jfp.pdf Compilation by transformation: https://www.microsoft.com/en-us/research/wp-content/uploads/1998/09/comp-by-trans-scp.pdf
  13. Phase control -- Before phase 2 Phase 2 and later

    {-# INLINE f #-} -- Yes Yes {-# NOINLINE f #-} -- No No {-# INLINE [2] f #-} -- No Yes {-# INLINE [~2] f #-} -- Yes No {-# NOINLINE [2] f #-} -- No Maybe {-# NOINLINE [~2] f #-} -- Maybe No
  14. Rewrite rules {-# RULES "map/map" forall f g xs. map

    f (map g xs) = map (f . g) xs #-}
  15. Rewrite rules {-# RULES "map/map" [2] forall f g xs.

    map f (map g xs) = map (f . g) xs #-}
  16. Rewrite rules LHS rewrites to RHS always exported compile with

    -O no termination / semantic equivalence checks
  17. Rewrite rules {-# RULES "whups" forall x y. f x

    y = f y x #-}
  18. Rewrite rules {-# RULES "rev-involutive" forall xs. reverse (reverse xs)

    = xs #-}
  19. Rewrite rules - fusion data Stream a where Stream ::

    (s -> Step s a) -> s -> Stream a data Step s a = Yield a s | Skip s | Done map :: (a -> b) -> [a] -> [b] map f = unstream . map . stream -- http://code.haskell.org/~dons/papers/icfp088-coutts.pdf
  20. Rewrite rules - fusion map g . map f =

    unstream . map g . stream . unstream . map f . stream
  21. Rewrite rules - fusion map g . map f =

    unstream . map g . id . map f . stream
  22. Rewrite rules - fusion map g . map f =

    unstream . map g . map f . stream
  23. Compiler options -ddump-rule-firings -ddump-rule-rewrites -ddump-inlinings -ddump-simpl-iterations

  24. concise Control.Lens.Cons.Extras.recons :: (Cons s1 s1 a a, Cons s2

    s2 a a, AsEmpty s2) => Getter s1 s2 -- https://hackage.haskell.org/package/concise
  25. fresnel sepBy :: Grammar s a -> Grammar s ()

    -> Grammar s [a] sepByT :: Grammar T.Text a -> Grammar T.Text () -> Grammar T.Text [a] sepByT g sep = prism (\(as, s) -> T.intercalate (print sep ()) (print g <$> as) <> s) (preview (sepBy g sep)) {-# RULES "sepBy/T" sepBy = sepByT #-} -- https://github.com/frasertweedale/hs-fresnel
  26. Recap The three Rs: reuse, refactoring, readability Rewrite rules: make

    generic functions go fast Other optimisations enabled by rewrite rules Phase control: make inliner and simplifier play nice
  27. Except where otherwise noted this work is licensed under http://creativecommons.org/licenses/by/4.0/

    https://speakerdeck.com/frasertweedale @hackuador