pleepo
pleepo

Reputation: 13

Inline optimization for recursive functions, with the help of a where clause (in Haskell)

Prelude

  1. Link 1 This post says:

"For a self-recursive function, the loop breaker can only be the function itself, so an INLINE pragma is always ignored," according to the GHC manual

  1. Link 2 This answer describes a method of optimization via in-lining by GHC.

  2. Link 3 This (unrelated) post explains compilers are free to reject explicit inline suggestions anyway. Or perhaps passing the -O2 flag to the compiler achieves the same effect as the OPTIMIZED VERSION shown below?

Example code

(as seen in Link 2)

--PRE-OPTIMIZED VERSION--
func :: (a -> a) -> [a] -> [a]
func f [] = []
func f [x] = [x]
func f (x:s:xs) = x:(f s):(func f xs)

----OPTIMIZED VERSION----
func :: (a -> a) -> [a] -> [a]
func f = loop
  where
    loop []  = []
    loop [x] = [x]
    loop (x:s:xs) = x : f s : loop xs 

------Eg-USAGE----------
> mapToEverySecond (*2) [1..10]
[1,4,3,8,5,12,7,16,9,20] 

Question

To quote @amalloy's doubts in the comments of Link 2, surely

f is "still implicitly passed around just as much [as explicitly passing f in the PRE-OPTIMIZED VERSION] by the closure-creating machinery" ?

As an aside, I am reading up on fusion techniques, an alternative approach as described in Link 1, although I am not trying to seek the fastest implementation possible in lieu of idiomatic styles.

Merely curious as to whether/how "where-definition" trick seen in the OPTIMIZED VERSION works (should the compiler decide to inline such a function).

Extra links are most welcome!

Upvotes: 1

Views: 230

Answers (1)

dfeuer
dfeuer

Reputation: 48611

This is a rather bad example because there's very little if anything to be gained by inlining this particular code. But similar patterns slow up all the time in situations where it matters. The big idea is that in the "optimized" func, if I write

func myFun

GHC can inline func and get

let
  loop []  = []
  loop [x] = [x]
  loop (x:s:xs) = x : myFun s : loop xs 
in loop

In this context, that's not meaningfully better, but if the result of myFun were demanded strictly, then it would replace a call to an arbitrary address with a call to a known address, which is faster. Furthermore, if myFun were used in an "interesting" context (where something is known about the argument passed to it or what's done with its result), then GHC would likely inline myFun to take advantage of that.

Upvotes: 2

Related Questions