Future Computations

Today I want to discuss about an interesting topic regarding Scala Futures. As the official documentation says, “Futures provide a nice way to reason about performing many operations in parallel– in an efficient and non-blocking way”.

But if you are familiar with Futures you maybe already know that when using for-comprehensions the chain of futures is executed sequentially and not in parallel. This is useful when the order of execution matters or when a partial result depends on another partial result like in this case:

val future = for {
  a <- webServiceCallFuture
  b <- databaseCallFuture(a)
  c <- Future(0.35 * b)
} yield c

However if the order of execution doesn’t matter you probably want to seize the power of parallelism. To achieve this goal the standard Scala library provides functions such as Future.traverse or Future.sequence as follows:

val future = Future.sequence(Seq(f1, f2, f3))
future onComplete {
  case Success(result) => //Do something with $result
  case Failure(e) => println(s"ERROR: ${e.getMessage}")
}

The signature of the sequence function is this one:

/** Simple version of `Future.traverse`. Transforms a `TraversableOnce[Future[A]]` into a `Future[TraversableOnce[A]]`.
 *  Useful for reducing many `Future`s into a single `Future`.
 */
def sequence[A, M[X] <: TraversableOnce[X]](in: M[Future[A]])(implicit cbf: CanBuildFrom[M[Future[A]], A, M[A]], executor: ExecutionContext): Future[M[A]]

Which in simple words it just the conversion of a Sequence of Futures to a single Future, for instance List[Future[String]] to Future[List[String]]. But in simple scenarios the performance of this function is really bad as you can see in the benchmark results below because of the conversions that performs.

Introducing Scala Async

Scala Async is a library which the main goal is to deal with parallel computation of Scala Futures. It’s based on Scala Macros so the code is analyzed and re-generated in compilation time.

This is how an example using it looks like:

def combined: Future[Int] = async {
  val future1 = slowCalcFuture
  val future2 = slowCalcFuture
  await(future1) + await(future2)
}

The async approach has two advantages over the use of map and flatMap that is how the for-comprehension works.

  1. The code more directly reflects the programmers intent, and does not require us to name the results on the yield part. This advantage is even more pronounced when we mix control structures in async blocks.
  2. async blocks are compiled to a single anonymous class, as opposed to a separate anonymous class for each closure required at each generator (<-) in the for-comprehension. This reduces the size of generated code, and can avoid boxing of intermediate results.

Similar Projects

While I’ve been doing my research I found a few similar projects:

  • Computation Expressions: It’s a promising project based on the Expressions concept introduced by the F# language but it’s not released yet and it seems that don’t have much activity.
  • Effectful: The idea is similar to Scala Async library, but generalized to arbitrary Monads (not just Future).
  • Scala Workflow: helps to nicely organize applicative and monadic computations but it last activity was one year ago and it’s based on Untyped Macros that are actually deprecated.

Demonstration Project

For comparison purposes I created a demonstration project on GitHub including the following Benchmark Results that shows the average time performing the sum of three simple Math.sqrt operations:

Future Sequence

  • 17.3 ms (16 15 20 15 13 14 12 26 22 20)

Future for-comprehension

  • 13.4 ms (17 9 8 11 14 17 8 19 12 19)

Async-Await

  • 6 ms (6 9 5 5 6 8 5 6 5 5)

The machine used to run this benchmark is an Intel® Xeon(R) CPU X5687 @ 3.60GHz × 4 with 23.5 GiB of memory running on Ubuntu 14.04 LTS 64 bits and Java 8.

Conclusion

The demonstration project is just the most simple case that you can create using simple math operations but imagine what you can do by taking this approach to a big scale. For instance for the execution of slow operations like web service and database calls. The difference might be huge…

Until next post!
Gabriel.

Advertisements

7 comments

  1. Tim Williams · September 10, 2015

    Why are you not using the applicative Future.zip (which executes concurrently) instead of the monadic Future.sequence (which executes sequentially), when comparing to the async-await library?

    Like

    • GMV Soft · September 11, 2015

      Do you mean something like this?

      val f = n1 zip n2 zip n3 map {
        case ((r1, r2), r3) => (r1 + r2 + r3)
      }
      

      In this cases I found though the pattern matching part of every zip if you have many Futures and the error handling part as well because if any of that Futures fail the throwable is stored in the original Future and not in the zip result.

      Like

  2. Luigi Antonini (@gigi_igig) · September 15, 2015

    Hi Gabriel, nice performance comparison, but I find the conclusion wrong, in CPU bound operation macros can be faster,
    but if you use Future when doing IO, which is the right use case, I think the time lost because of the overhead would be minimal and
    you don’t have to rely on macros black magic, also considering all the abstraction advantages that you have using Future instances
    for typeclasses like Functor, Monad, etc …

    Like

    • GMV Soft · September 15, 2015

      Hi Luigi, thanks for your comment. Probably your right, you have different tools and you have to choose the right one depending on what you’re going to do. Maybe in the nearly future I’m going to create a better comparison including IO operations and benchmarking using jmh as a Reddit user suggested. Cheers, Gabriel.

      Like

  3. Luigi Antonini (@gigi_igig) · September 15, 2015

    Hi Gabriel,
    great, I’m looking forward to see the full comparison with IO!
    Cheers,
    Luigi

    Like

  4. Pingback: Links of the Week, W39 2015 | One More Game-Dev and Programming Blog
  5. Pingback: Future Computations: Benchmarks | PartialFlow

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s