genericsf#inlinefunctor

Seq.iter slower than native mapping : any generic solution?


The issue has already shown up before, and its reason answered by John Palmer : why is Seq.iter and Seq.map so much slower?

let ar = Array.zeroCreate<int> (64*1024*1024)
#time
//Real: 00:00:00.171, CPU: 00:00:00.171, GC gen0: 0, gen1: 0, gen2: 0
for i in 0 .. ar.Length - 1 do
    ar.[i] <- ar.[i]*3

//Real: 00:00:00.327, CPU: 00:00:00.328, GC gen0: 0, gen1: 0, gen2: 0
ar |> Array.iteri(fun i _ -> ar.[i] <- ar.[i]*3)

//Real: 00:00:02.249, CPU: 00:00:02.250, GC gen0: 0, gen1: 0, gen2: 0
ar |> Seq.iteri(fun i _ -> ar.[i] <- ar.[i]*3)

I wonder if there are some kind of "inlining" or other generic mechanisms that could map, say a Sequence to (its last known?) concrete type to accelerate those behaviour. For instance here i have static guarantee that I will iterate over an array.

Do you know of satisfactory solution that exists in theory to this ? (what would be there fancy name ?)

Are there some langage that acknowledge and solve that issue nicely ?


Solution

  • It's true that you don't have Higher Kinded Types in F# but still you can use inline functions and emulate some Typeclass behaviour. By defining a Functor you have a generic solution to native mapping:

    type Fmap = Fmap with
        static member ($) (_Functor:Fmap, x:List<_> ) = fun f -> List.map    f x  
        static member ($) (_Functor:Fmap, x:array<_>) = fun f -> Array.map   f x
        static member ($) (_Functor:Fmap, x:_ [,]   ) = fun f -> Array2D.map f x
        static member ($) (_Functor:Fmap, x:_ [,,]  ) = fun f -> Array3D.map f x
        static member ($) (_Functor:Fmap, x:_ [,,,] ) = fun f ->
            Array4D.init (x.GetLength 0) (x.GetLength 1) (x.GetLength 2) (x.GetLength 3) (fun a b c d -> f x.[a,b,c,d])
    
    let inline fmap f x = (Fmap $ x) f
    

    Take for instance fmap, this function is inline and it will accept any type defined in the overloads and it will execute as fast as calling the function directly (which is what actually happens behind the scenes). To iter you can use fmap and discard the result, in your case you may want to define something like fmapi to have an index available.

    Keep in mind you should call these functions always with the the concrete type, if you pass a Seq<'a> it will fail, you should not mix both approaches: subtype and ad-hoc polymorphism.