Question

The following code:

import Control.Exception
import Data.List

updateAverage :: (Fractional t) => (t, t) -> t -> (t, t)
updateAverage (old_value, old_counter) x =
    let new_counter = old_counter + 1
    in 
        assert(new_counter /= 0)
        old_value `seq` (old_value + (x - old_value) / new_counter, new_counter)

average values = fst (foldl' updateAverage (0.0, 0.0) values) -- version I

main = do
    let v = [1 .. 1000000]
    let a = average v
    putStrLn (show a)

becomes faster (compilation options: ghc.exe -O3) when I replace the definition of average function with

average = fst . foldl' updateAverage (0.0, 0.0) -- version II

What could be the reason for this? I thought that the differences between these two lines are basically syntax. Is the second version (without free variable values) easier for the compiler to optimize?

Funnily enough, when compiled without optimization, version I becomes faster.

Timing results:

options: -O3

version I: 0.280s version II: 0.212s

options: (no optimization)

version I: 0.42s version II: 0.44s

Measured using time shell command in Cygwin.

Timing results with type=Double:

Double:

options: -O3

version I: 0.22s version II:: 0.212s

options: (no optimization)

version I: 0.34s version II: 0.35s

More info: I'm using the compiler

> $ ghc -v Glasgow Haskell Compiler, Version 7.0.4, for Haskell 98,
> stage 2 booted by GHC version 6.12.2 Using binary package database:
> C:\Program Files\Haskell
> Platform\2011.4.0.0\lib\package.conf.d\package.cache wired-in package
> ghc-prim mapped to ghc-prim-0.2.0.0-e1f7c380581d61d42b0360d440cc35ed
> wired-in package integer-gmp mapped to
> integer-gmp-0.2.0.3-91607778cf3ae8f3948a50062b4f8479 wired-in package
> base mapped to base-4.3.1.0-f520cd232cc386346843c4a12b63f44b wired-in
> package rts mapped to builtin_rts wired-in package template-haskell
> mapped to template-haskell-2.5.0.0-7d9b1443ac5ab69e5ed705a487990deb
> wired-in package dph-seq not found. wired-in package dph-par not
> found. Hsc static flags: -static
> *** Deleting temp files: Deleting:
> *** Deleting temp dirs: Deleting: ghc.exe: no input files Usage: For basic information, try the `--help' option.
under Cygwin.*
Was it helpful?

Solution

I conjecture that something might be going on with stream fusion or loop fusion. Perhaps there is a rewrite rule buried deep in the Prelude that is firing in one case or not in the other. Or, because you don't say how much faster, you could simply be seeing cache effects.

If you want to know more, learn to fish:

  • Use ghc -ddump-simpl to see the code that's actually being generated and compare it.

  • Use valgrind to count the number of instructions being executed.

If nothing else, these tools will give you enough information that you can ask a more focused, detailed question.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top