среда, 2 октября 2013 г.

DataFlow native concurrency has been implemented in SynapseGrid

As already have been discussed there are a few possible ways to run the StaticSystem (synaptic grid) on a multicore processor. The one that was implemented was running subsystems inside Akka actors.

However, this is not the finest grained concurrency possible. The whole subsystem will run inside a single thread (the current actor's thread) and it can become a bottle neck for other subsystems.

What is the least element that can run in parallel? For SynapseGrid it is a function that connects two contacts. If it is pure function then it can safely run in a separate thread.

Special care should be taken when dealing with states. The access to states should be serialized according to the principle "happen-before". We cannot read the state value before all computations that could influence it have completed. And no other computation that requires this state can start until we complete this computation. However, all the other computations can run in parallel. In particular if two computations use different states then they can run in parallel.

Recently this fine-grained approach has been implemented in SynapseGrid (synapse-grid-concurrent module). To run a system in some ExecutionContext one converts it to a parallel SimpleSignalProcessor:

   import scala.concurrent.ExecutionContext. Implicits.global
   val f = system.toStaticSystem. toParallelSimpleSignalProcessor. toMapTransducer(input, output)
   val y = f(x)

That's it. Now the whole system runs with fine-grained parallelism. To compare that the results are absolutely the same, the same system can also be converted to a traditional single-threaded version:

   val g = system.toStaticSystem. toSimpleSignalProcessor. toMapTransducer(input, output)
   val y2 = g(x)
   assert(y === y2)

The concurrency implementation guarantees that the results will be the same.

The tests for the module contains examples of how it works.

The dependency for the synapse-grid-concurrent module:

"ru.primetalk" % "synapse-grid-concurrent" % "1.3.0"

The source code is published on GitHub.

Ярлыки: , , , , ,

четверг, 19 сентября 2013 г.

Functional Reactive Programming: Introduction to SynapseGrid

Functional programming paradigm opens new ways of building complex software systems. We wish to keep all operations side-effect-free and have all benefits of immutability. Also we want a way to compose functional building blocks into bigger systems that retain the same properties.

Traditional functional composition

Constructing a complex system from functions in a traditional way looks something like:
    def f3(x) = f2(f1(x))
or
    def g3(xs:List) = xs.map(f1).flatMap(f2)
or
    def h3(xs:List) = 
      for { x <- xs
            ys = f1(x)
            y <- ys
      }
         yield f2(y)

What's wrong with it? Well, not too much. This approach works pretty good in many situations. However, what to do if we want
  1. to split the flow of data into two chains?
  2. to implement an arbitrary DataFlow processing?
  3. to modularise the construction of processing function?
The construction code becomes a bit uglier.

Builder / Runtime system separation

During the construction phase of a complex system we may tolerate mutable state, because the construction is usually single-threaded, but during runtime we want to avoid it as much as possible.
The steps looks as follows:
  1. We construct a system using an advanced DSL.
  2. Convert the system into runtime representation.
  3. Run the system on some data.
What can advanced DSL give us?
  1. Arbitrary DataFlow graph of a system can be constructed incrementally.
  2. The construction can be done in separate (/nested) modules.

System construction DSL

Of course for simple cases we wish to retain the usual DSL with maps and flatMaps. But in order to have builder/runtime separation the map and flatMap methods are replaced. Now they do not immediately execute their operation but instead defer execution to runtime.
This system can be constructed with
    val len = myContact.map( _.length )
and:
    Input >> myContact
    len >> Output

Runtime system

The system is constructed within a mutable Builder and can be converted to static immutable system definition with the method toStaticSystem. The system definition can the be statically analyzed (converted to the above picture for instance). Or further converted to a simple function.
    val s = sb.toStaticSystem
    val fun = s.toDynamicSystem.toMapTransducer(Input, Output)The fun has type of a function and can be immediately used in other parts of the program:
    println("fun(hello)="+fun("hello"))

More info

More examples on GitHub.


Ярлыки: , , ,

SynapseGrid

This blog is about SynapseGrid library. SynapseGrid is a framework for constructing reactive real-time immutable data flow systems (see Data flow programming).
The library allows construction of complex systems from building blocks as simple as functions.

Ярлыки: , , ,