0

What is the cost of a scala Future? Is it bad practice to spin up, say, 1000 of them only to flatMap them away again right away?

In my case, I don't need 1000 futures - I could actually get away with about 10 or so, but it makes my code cleaner to use more futures, and I'm trying to get a sense of tradeoffs between code elegance and abusing resources. Obviously if I had blocking code, they'd be expensive, but if not, how many should I feel free to spin up to save a few lines of code?

thund
  • 1,842
  • 2
  • 21
  • 31
  • I'm guessing you mean either "scala futures" or "akka actors", as futures are a feature of the scala language, independent of akka. (although there's a bit of history there). – Alvaro Carrasco Apr 29 '17 at 00:36
  • Yes, I mean scala futures. I was stuck in the old akka history. I've changed the question to remove akka references. – thund Apr 29 '17 at 00:45
  • Depends on what was did in the future. eg, you have 1000 futures which call thirdpart api in parallel, you should consider whether the file discriptior and bandwith can bear such parallel requests. Future itself only occupy a little memory, it is not a problem at most case – jilen Apr 29 '17 at 02:04
  • Right, so my question assumes that absolutely nothing is done inside the extra futures. They're simply a bookkeeping device that allow me to deal with a homogeneous list of `Future[T]` instead of a mixed list containing some elements of type `T` and some of type `Future[T]`. How much memory and other overhead does a future cost? Is it ok to create 1000 extra futures? How about an extra million? – thund Apr 29 '17 at 05:11

2 Answers2

2

You say you create some of them just to deal with a homogeneous list of Future[T]. In that case, if you just want to lift some T to a Future[T], you can do Future.successful(myValue). This causes no asynchronous background operations to be performed. It's a ready value, just wrapped in Future context.

EDIT: After re-reading your question and comments, I believe this is enough for an answer. Continue reading for extra info.

Regarding flatMapping, be aware that if you create 1000 futures beforehand as 1000 different vals, they will start right away (well, whenever JVM execution context decides that it's a good time to start, but definitely as soon as possible). However, if you create them in-place inside the flatMap, they will be chained (whole point of M-word's flatMap is to chain stuff in sequential series, with each step possibly depending on the result of previous one).

Demo:

import scala.concurrent.{Await, Future}
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global

val f1 = Future({ Thread.sleep(2000); println("one") })
val f2 = Future({ Thread.sleep(2000); println("two") })

val result1 = f1.flatMap(f => f2)

Await.result(result1, 5 seconds)

val result2 = Future({ Thread.sleep(2000); println("one") })
  .flatMap(f => Future({ Thread.sleep(2000); println("two") }))

Await.result(result2, 5 seconds)

In the case of result1, you will get "one" and "two" printed out together after two seconds (sometimes even first "two" then "one"). But in the second case where we created the futures in-place inside the flatMap, first "one" is printed after two seconds, and then after another two seconds "two". This means that if you chain 1000 Futures like this, but the chain breaks at step 42, the rest 958 futures will not be calculated.

By combining these two facts about Futures you can avoid creating unnecessary ones.

I hope I helped at least a little, because regarding your main question - how much memory and other overhead does a Future cost - I don't have the numbers. That really depends on the settings of your JVM and the machine that's running the code. I do think however that even if your system can take anything you throw at it, you shouldn't be doing (a lot of) unnecessary background Future computations. And even though there are such things as sensible timeouts and cancelling via their respective Promises, creating an extra million of Futures you won't need sounds like a bad design IMHO.

(Note that I said "background computations". If you mainly need all these Futures to keep all types "in the future level" so that the whole code is easier to work with (e.g. with for comprehensions), in that case aforementioned Future.successful is your friend since it's not a computation, just an already computed value stored in a Future context)

slouc
  • 9,508
  • 3
  • 16
  • 41
  • So I was previously lifting `T` to `Future[T]` by using `Future(myValue)`. Are you saying it's more efficient to use `Future.successful(myValue)` instead? (I've tested and both work.) – thund Apr 29 '17 at 13:29
  • 2
    Yes, because `successful` doesn't create the computation (which would then be handled by ExecutionContext, passed on to a thread etc, so even if your value is trivial, a mini asynchronous computation would be performed). `Future.successful` means "don't compute anything, don't even bother the ExecutionContext, just pretend that there was some successful computation and this is its result". So yes, it's more efficient. But this doesn't mean you should change all `Future`s to `Future.successful`s :) Real computations should be performed asynchronously, that's the whole point of Futures. – slouc Apr 29 '17 at 15:38
  • I see. I'd naively assumed that the compiler would be smart enough to interpret `Future(3)` as `Future.successful(3)`. But your answer gives me confidence that if I use `Future.successful` to make my list homoegneous, I won't be causing a performance hit. Thanks. – thund Apr 29 '17 at 20:19
  • Future(3) is Future.UNIT.map(_ => 3) – Viktor Klang May 01 '17 at 13:47
2

I might have misunderstood your question. Correct me if I am mistaken.

What is the cost of a scala Future?

Whenever you wrap expression(s) in future, a java runnable is created under the hood. And a Runnable is just an interface with a run(). Your block of codes is then wrapper inside the run method of the runnable. This runnable is then submitted to the execution context.

In a very general sense, a future is nothing more than a runnable with bunch of helper methods. An instance of future is no different from other objects. You may reference this thread to get a rough idea on what's the memory consumption of a single java object.

If you are interested, you can trace the whole chain of action starting from the creation of a future

Community
  • 1
  • 1
user2829759
  • 3,372
  • 2
  • 29
  • 53
  • 1
    Those are interesting links. I think that for my purposes the accepted answer below contains the important concept - ie I should use Future.successful to ensure no extra work for the ExecutionContext. Thanks. – thund Apr 29 '17 at 20:24