Wednesday, June 3, 2015

The current design and implementation of ElixirFBP does not take advantage of Elixir's ability to spawn many processes cheaply and quickly. There will undoubtedly be times where it would be desirable to have multiple processes supporting a single component. Right now, each component is supported by one Elixir process.

To give ElixirFBP this capability, I used the metadata part of the flow-based network protocol to specify the number of Elixir processes that you want running in support of a component. In code for creating a graph, this looks like:
    Graph.add_node(fbp_graph_reg_name, "copier", "Jsfbp.Copier",
                   %{:number_of_processes => 4})
In this example, when the FBP graph is started, four separate processes will be spawned for this component. Any component that sends an Information Packet (IP) to this component will automatically have this IP sent to one of the spawned processes, in a round-robin manner. Note that, with this method, there is no guarantee that the order of the IPs will be preserved as they move through the remainder of the flow. I will discuss techniques for ensuring order in subsequent posts.

We should expect to see some improvement in the execution speed of a graph that uses multiple processes for a component. Recall that the Elixir/Erlang virtual machine will take advantage of multiple cores in a processor automatically. Here's an experiment to see this in action: I've created an example program - Examples.Fbptestv1Multi - that uses a component to create IPs and sends them to a "faker" component which sends them on to a discarder component that simply ignores them. The faker component will sleep for a certain number of milliseconds to simulate processing. The example is written so that the faker component can be set to run in a specified number of processes (by default one).

The example program has a timing function that takes two arguments: the number of IPs to create and send and the number of processes to assign to the faker component. I ran the following tests on my four-core, AMD processor running at 3.60 Ghz processor (the time is in seconds and the faker process was set to sleep for 20 milliseconds):

No. of IPs No. of Processes Time
1,000 1 31.282
1,000 2 15.648
1,000 4 7.830
1,000 8 3.924
1,000 16 1.972
1,000 32 1.001

You can see that as the number of processes assigned to the faker component increases the overall speed of the graph execution goes down. All the code to run this example is in my github repository.  To run it, perform the following steps after downloading the ElixirFBP code:

  1. cd to the ElixirFBP directory.
  2. You may have to run mix deps.get in order to download and install certain libraries
  3. Run iex -S mix
  4. At the iex prompt enter: Examples.Fbptestv1Multi.time_it - this will, by default, run 1,000 IPs utilizing one processor for the faker component. The time will be displayed in microseconds - not milliseconds.
  5. Enter Examples.Fbptestv1Multi.time_it(1000, 4) to use 4 faker processes.
All we needed to do to utilize the inherent concurrency present in Elixir/Erlang systems was to specify the number of processes that we wanted a component to be run in. The changes that I had to make to ElixirFBP were to simply create a tuple of  the registered names of the spawned processes and maintain an index that points to the next process to use - to send an IP to. The only other change was to how the components are written. Now, all sends return an updated value for the target out port. So,
def loop(in_port, out) do
    receive do
      {:IN, value} ->
        # Do some processing
        out = Component.send_ip(out, value)
        loop(value, out)
    end
  end
became
def loop(in_port, out) do
    receive do
      {:IN, value} ->
        # Do some processing 
        out = Component.send_ip(out, value)
        loop(value, out)
    end
  end

No comments:

Post a Comment