0
votes

with a basic understanding of Akka classic, I moved to Typed and noticed, that the typed version of my code is significantly slower than the classic one.

The task is to aggregate "ticks" (containing an instrument name, a timestamp and a price) per instrument.

In the classic code, I dynamically create one actor for each instrument and kep a Map<Instrument, ActorRef> outside the actor system to delegate the incoming ticks to.

In the typed code, a "parent" was required, thus I moved the routing logic with the Map into this parent actor, so I ended up with two Actors classes here (the actual tick actor and the routing parent actor).

Otherwise, the code is pretty much the same, just once implemented via the classic api and once typed.

When testing both logics (primitively) I found that the version using the classic logic took a bit less than 1.5 seconds to process 1,000,000 ticks, while the typed one required a bit more than 3.5 seconds.

The obvious first reason was to move the guardian parent (which is also the router) to its own PinnedDispatcher, so it could run on its own thread, with all the other actors using the default threadpool. This increased performance a good bit, leading to around 2.1 seconds to process 1,000,000 ticks.

My question is: Does anyone have an idea where the remaining performance (0.6 seconds) might be lost?

1

1 Answers

2
votes

Typed runs on top of classic (a typed Behavior<T> is effectively wrapped in a function which casts messages to T; once wrapped, it can then be treated as basically a classic Receive), so it introduces some overhead per-message.

I'm guessing from the improvement in putting the routing parent on a pinned dispatcher that the typed implementation sent every tick through the parent, so note that you're incurring that overhead twice. Depending on how many Instruments you have relative to the number of ticks, the typed code can be made much more like the classic code by using something like the SpawnProtocol for the parent, so the code outside the ActorSystem would, at a high-level:

  • check a local Map<Instrument, ActorRef<Tick>> (or whatever)
  • if there's an ActorRef for the instrument in question send the tick to that ActorRef
  • otherwise, ask the parent actor for an ActorRef<Tick> corresponding to the instrument in question; then save the resulting ActorRef in the local Map and send the tick to that ActorRef

This is more like the situation in classic: the number of messages (ignoring internal system messages) is now 1 million plus 2x the number of Instruments, vs. 2 million.