DelegateEvents use DynamicInvoke under the hood and so can be slow when trigged frequently.  As we have an existing model which requires an event interface, this was was causing our F# implementation to be a bit slower than the C# we were comparing it to.  Thankfully, F# allows you to implement your own eventing via IDelegateEvent.  With a standard Invoke version of DelegateEvent provided by James Margetson, our implementation in F# is running at equivalent speed to C#.

The other day I was working with Steve on trying to discover why a particular segment of apparently equivalent code was running ~25% slower in F# when compared to C#.  We broke out AQTime and did a line by line release-mode profile.  The frequent triggering of a DelegateEvent immediately jumped out as the cause. 

Needless to say, I was a bit concerned.  What could be causing this slow eventing in my favorite language?  After a brief review of the generated IL, nothing in particular seemed fishy.  So, I quickly built a sample and sent it off to my favorite language team.

 

The contents of the F# library assembly:

type FsEventClass(num) =
    let event = new DelegateEvent<System.EventHandler<System.EventArgs>>()
    
    [<CLIEvent>]
    member this.Event = event.Publish
            
    member this.Run () =
        let args = [| this :> obj ; System.EventArgs.Empty :> obj |]
        for i in 1 .. num do
            args.[1] <- new System.EventArgs() :> obj
            event.Trigger( args )

 

And this, the C# client:

static void Main(string[] args)
{
    int iters = 1000000;

    DateTime fsStart = DateTime.Now;
    FSharpEventingLib.FsEventClass fs = new FSharpEventingLib.FsEventClass(iters);
    int fsCalled = 0;
    fs.Event += (s, a) => fsCalled++;
    fs.Run();
    DateTime fsEnd = DateTime.Now;
    TimeSpan fsTime = fsEnd - fsStart;
    System.Console.WriteLine(String.Format("F# took: {0} when called {1} times", fsTime, fsCalled));
}

 

Finally, the program output:

F# took: 00:00:05.6830000 when called 1000000 times

 

As it turns out, the F#’s current DelegateEvent uses a DynamicInvoke under the hood and that can slow things down quite a bit.  This won’t be important in most cases as Event is implemented with a standard Invoke.  However, in our case we had to fit to an existing C# model and so needed to do an event trigger for each resulting scanline of an image.

A big thanks to James Margetson of the F# Team for the following fast replacement for DelegateEvent.  Within same day I had mentioned my issue he had this solution for me.  

 

The new library contents:

type FastDelegateEvent() =
    let mutable multicast : System.EventHandler = null

    member x.Trigger(sender:obj,args:System.EventArgs) =
        match multicast with
        | null -> ()
        | d -> d.Invoke(sender,args)   // DelegateEvent used: d.DynamicInvoke(args) |> ignore

    member x.Publish =
        { new IDelegateEvent<System.EventHandler> with
            member x.AddHandler(d) =
                multicast <- System.Delegate.Combine(multicast, d) :?> System.EventHandler
            member x.RemoveHandler(d) =
                multicast <- System.Delegate.Remove(multicast, d)  :?> System.EventHandler }

type FsFastEventClass(num) =
   let event = new FastDelegateEvent()

   [<CLIEvent>]
   member this.Event = event.Publish

   member this.Run () =
       for i in 1 .. num do           
           event.Trigger(this, new System.EventArgs())

 

The program output with FastDelegateEvent:

F# took: 00:00:00.0390000 when called 1000000 times

 

This new event class completely resolved our issue.  With it our F# version is just as fast, if not faster, than the C# equivalent.