Arturo Hernandez
Arturo Hernandez

Reputation: 2869

F# AsyncSeq speed issue

I am trying out AsyncSeq and I am confused by a speed issue I am having. Here is a fiddle of the code below.

open System
open FSharpx.Control

let rec numbers n x = asyncSeq {
    yield n
    //printfn "%A" n
    do! Async.Sleep(1)
    if (n + 1 = x) then
        yield n + 1
    else
        yield! numbers (n + 1) x
}

Async.Sleep(300) |> Async.RunSynchronously
for i in [0..300] do printfn "%A" i

numbers 0 300
|> AsyncSeq.iter (fun x -> printfn "%A" x)
|> Async.RunSynchronously

The top loop finishes clearly in a shorter amount of time. While the bottom async sequence takes longer. Is this normal? or am I missing something?

Upvotes: 0

Views: 214

Answers (1)

Tomas Petricek
Tomas Petricek

Reputation: 243126

Asynchronous sequences have been designed for writing computations that involve some asynchronous waiting such as disk or network I/O. For this reason, it is quite sensible to expect some overhead when using asyncSeq - in the typical use case, this is not very significant compared to the overhead of the asynchronous operations.

I had a quick look at your example and most of the overhead here is actually coming from Async.Sleep(1) - this uses System.Threading.Timer internally (to schedule the continuation to be called in a thread pool).

On my machine, the following code (with Async.Sleep) takes about 4.6 seconds:

let rec numbers n x = asyncSeq {
    yield n
    do! Async.Sleep(1) // (sleep)
    if (n < x) then yield! numbers (n + 1) x }

numbers 0 300
|> AsyncSeq.iter (fun x -> printfn "%A" x)
|> Async.RunSynchronously

But when you drop the Async.Sleep call (line marked (sleep)), the computation takes just 30ms, which is pretty much the same as the following for loop:

for i in [0..300] do 
  printfn "%A" i

Now, if you add asynchronous sleeping to the for loop, it takes 5 seconds too:

for i in [0..300] do 
  Async.Sleep(1) |> Async.RunSynchronously
  printfn "%A" i

This is not too surprising - if you replaced asynchronous sleeping with Thread.Sleep, then it would run faster (but synchronously). So, in summary:

  • There is certainly some overhead of asyncSeq itself, but it is not that big
  • Most of the overhead in your example comes from asynchronous sleeping using Async.Sleep
  • This is quite realistic model of typical uses of asynchronous sequences - they are designed for writing computations that do some asynchronous waiting
  • Measuring performance overhead using toy examples like Async.Sleep can be misleading :-)

Upvotes: 2

Related Questions