CharlesIrvine

CharlesIrvine

Need some advice for Mozart performance testing

I have just started performance testing for mozart (BPM platform) and I am seeing something I don’t understand and am hoping to get some advice.

I have a GenServer module named ProcessEngine. Instances of this module are spawned via a DynamicSupervisor.

Each ProcessEngine instance spawned is initialized with a data structure representing a defined business process. To clarify, a “business process” is not an Elixir process.

The ProcessEngine instances runs until the “business process” has ran out of work to do, that is, it has finished it’s intended function.

So, here is the issue that I am trying to understand.

If I spawn 1,000 GenServers, they finish execution in about 300,000 microseconds:

iex [09:24 :: 6] > :timer.tc(fn -> run_process_n_times(%{}, :process_with_single_service_task, 1000) end)
{287086, :ok}

If I spawn 10 times that number, i.e. 10,000, they finish execution in 26,602,559, or about 100 times longer than the execution of a 1000 instances.

iex [09:24 :: 8] > :timer.tc(fn -> run_process_n_times(%{}, :process_with_single_service_task, 10000) end)
{26602559, :ok}

So, executing 10 times more GenServer instances takes 100 times the time to complete. I had assumed that execution time would increase linearly with the number of GenServer instances.

If I run the observer, I do see scheduler utilization go to 100% for 2 out of 12 schedulers. It’s always scheduler 1 & 2 that to to 100%. Couple of questions:

Why don’t I see more schedulers become active?
Is the 100% for two schedulers indicative of a problem?

Finally, is there any advice on how to analyze this?

Most Liked

al2o3cr

al2o3cr

One thing that’s not helping concurrency: doing work in init means that start_link takes longer to return. Consider moving the code from ProcessEngine.init to a handle_continue callback.

Another thing that isn’t helping concurrency: init and execute both need to make calls to singleton processes (ProcessModelService and ProcessService)

With a nearly 100x increase based on 10x more input, I’d start by carefully looking at where data’s being collected in the code; all it takes is one List.append that’s called per-ProcessEngine to make things quadratic.

Some other random thoughts:

  • terminate doesn’t do any formatting on the reason argument, so if you call Process.exit(some_pid, :shutdown) (eg) you’ll get :shutdown as an argument - and the ProcessEngine target will crash!

  • harping on the same point: terminate is not guaranteed, there are lots of (admittedly uncommon) scenarios where it will not run before the process disappears. Consider checkpointing the state during intermediate steps, if “resuming” is important.

  • consider extracting type-specific code like this to a per-step (or per-type) “callback module”

al2o3cr

al2o3cr

Instead of checking type everywhere, you’d define a single map:

@callback_modules %{
  decision: DecisionCallbacks,
  service: ServiceCallbacks,
  send: SendCallbacks,
  ...
}

A method like complete_able simplifies to:

defp complete_able(t) do
  @callback_modules[t.type].complete_able(t)
end

DecisionCallbacks then defines:

defmodule DecisionCallbacks do
  def complete_able(_task), do: true

  ...more functions used elsewhere in ProcessEngine...
end

This would also be a valuable place to utilize a behaviour to ensure that the callback modules implement a consistent interface.


A further, even more dynamic approach, would have a field on Task that contains the callback module name. That would allow tasks with the same type to have different callbacks. Whether that’s a bug or a feature will depend on your specific needs :stuck_out_tongue:


One general note about both variants: they make the code easier to read, but also obscure it from parts of Elixir’s static analysis.

For instance, you would get a compiler error if you wrote DecisionCallbacks.complete_able() (calling with the wrong arity) explicitly, but writing @callback_modules[t.type].complete_able() will only crash at RUNTIME.

Where Next?

Popular in Questions Top

openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
sacepums
Hey guys. I'm new to elixir and im really stocked about it. But I ran into a bit of problem - I need to convert a date sting, for examp...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
Fl4m3Ph03n1x
Background Let’s assume I have a typical GenServer that receives messages as requests, does some operation in a DB and returns responses....
New
chewm
Hi guys, nice to meet you to the whole forum, I’m new here, I’m trying to configure visual studio code for elixir, right now the intellis...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
hpopp
To simplify some tasks at work, I wrote and published this package yesterday. It’s a simple macro that enables Access behaviour on struct...
New

Other popular topics Top

peerreynders
Manning 2016 Halloween weekend sale via Deal of the Day Friday, October 28 - Half off all MEAPs - code WM102816LT Saturday, October 29 ...
326 29600 154
New
dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list....
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New

We're in Beta

About us Mission Statement