This repository contains a collection of programs that evaluate languages with message-passing concurrency. The programs are designed to evaluate properties of message-passing concurrency rather than general language performance. Furthermore, a selection of features from process calculi are examined for each language. These are:
- Synchronous communication.
- First-order channels.
- Higher-order channels.
- First-order processes.
- Higher-order processes.
- Parallel execution statement.
- Indexed parallel execution statement.
- State ownership.
- Process ownership.
- Selection on incoming messages.
- Indexed selection.
- Selection based on incoming value.
- Guarded selection.
- Fair selection.
- Selection with timer.
- Other selection types.
- Selection of outgoing messages.
- Multi-party synchronisation.
- Selection on multi-party synchronisation.
A description of these features is provided in the appendix below.
One of the aims of this work is the building of a classification for message-passing concurrent languages. The hope is that an understanding of common features and different approaches can be made by investigating the approaches to message-passing concurrency. At present, the classification is limited as only a handful of languages have been examined. The current classification is below:
TODO
The languages currently evaluated are:
Languages that have so far been discounted are:
- Aha!
- ALGOL 68
- AmbientTalk
- Ateji PX
- Axum
- BCPL
- C=
- Cω
- Clojure
- Concurrent Pascal, although SuperPascal may allow a similar analysis.
Details on why these languages are discounted is given in the Discounted Languages section.
There are currently three benchmark applications:
- Communication Time.
- Selection Time.
- Monte Carlo π (for multicore support analysis).
The benchmarks are described in the appendix below.
If you want to help, feel free to pull the repository, implement the benchmarks, undertake the evaluation, and make a pull request. At present, the following languages have been identified as potentially having message-passing concurrency support in the language or via the language's standard libraries but have not been completed.
- Aikido
- C# - via
BlockingCollection
. - CAL Actor Language - tricky to find an implementation. Try here.
- Dodo
- E - instructions available here.
- Eiffel - a good starting point is probably Eiffel Software. Concurrency seems to be provided via SCOOP but unclear if message passing is available.
- Elixir (although this is really just Erlang) - instructions available here.
- Esterel - instructions available here.
- Euphoria - instructions available here.
- F#
- Falcon - instructions available here.
- Fantom (although this does run on-top of other runtimes) - instructions available here.
- Fancy
- FortranM - seems out-of-date, but try here.
- Haskell - via the Control.Concurrent.Chan type. Unclear if this is part of standard Haskell.
- Hume - instructions available here.
- Go! (not to be confused with Go) - instructions available here.
- Goaldi
- Icon - although not sure a compiler exists anymore.
- Io - instructions available here.
- J - instructions available here.
- Java - the
SynchronousQueue
is part of the standard Java API and provides a synchronous channel like construct. - JoCaml - instructions available here.
- Join Java - appears to have been a PhD project. Try here.
- Joule - not sure if still relevant.
- Julia - instructions available here.
- Kotlin - instructions available here.
- LabVIEW - might be stretching the definition though. Instructions available here.
- Limbo - instructions available here.
- Logtalk - instructions available here.
- Lua - instructions available here.
- Manticore.
- Mercury - instructions available here.
- MPD - instructions available here.
- Mythryl
- Neko - instructions available here.
- Newsqueak - may not be suitable, but an interpreter is available here.
- Nim - instructions available here.
- OCaml - instructions available here.
- Oforth
- Open Object Rexx - instructions available here.
- Orc - looks interesting.
- Oz - the best approach seems to be via Mozart.
- P - Microsoft research language. Seems interesting.
- Panda
- Perl - although I think this may be via OS process communication. Instructions available here.
- Perl 6 - although see comment on Perl. Instructions available here.
- Phix - but seems just to be Euphoria.
- PicoLisp - details available here.
- Pony
- Preesm - may be stretching the definition. Instructions available here.
- ProcessJ
- Prolog - there appears to be send message and receive message support. See here.
- Python via threads and queues. Details available here.
- Racket - instructions available here.
- Red - instructions available here.
- Ruby - instructions available here. Unsure about this.
- SALSA - instructions available here.
- Scala - instructions available here. It looks like actors are no longer part of the core library though.
- SequenceL - instructions available here.
- Sequoia++
- SIGNAL - may be stretching the definition. Also tricky to find. Try The Polychrony Toolset.
- SISAL - think this is unavailable. Try here.
- SpecC - a reference compiler can be found via here.
- SR - looks like it is no longer maintained. Try here.
- Standard ML - but most likely ConcurrentML. Try here.
- SuperPascal - unlikely to be still possible, but try here and here.
- Swift (via Grand Central Dispatch) - Swift is available for Linux.
- SystemC - but looks like this is no longer available. Official website.
- SystemVerilog
- Tcl - instructions available here.
- Unicon - instructions available here.
- Unified Parallel C - may fit the criteria.
- Vorpal - seems to have become Clump.
- Wren
- XC - although unsure if this will work on a general PC. There is an open source XCore project.
- Zkl
- Zonnon - instructions available here.
- μC++ - instructions available here.
Not all of these languages may support message-passing concurrency, as only a quick overview has been made. Languages may also be missing. If you know of a language missed please get in touch. Also, to be included a current implementation of the language is required. There are historic languages with message-passing concurrency, but seem to be unavailable today. For comparison purposes, the language must run on Linux.
Language selection criteria:
- the language must provide mechanisms to send a message between components as part of the core language features (e.g., keyword support and/or standard library) and not via an additional library.
- the message must be sent in a manner so that the receiver can choose when to receive it; therefore, giving the receiver control over its internal state. A method invocation on an object is therefore not a message. This criteria implies a receiver has some thread of execution independant to the sender.
- the receiver must be able to wait passively for a message to be received - that is, a busy spinning on a value to change is not a message. Having a queue between threads that a receiver tests for readiness is not suitable.
- a communication must be made using a single command, i.e., language keyword or standard library call. A slight concesion is made for yielding due to the use of coroutine approaches that often require an explicit
yield
statement, and languages that may define a communication in some form of block statement. This is to keep in the spirit of a message-pass being a single operation. - messages must be any structured data type supported in the language. Conversion to bytes, strings, or another data serialization technique is not considered message-passing. Nor is any use of I/O mechanisms to simulate communication.
If you have to download a separate library, or write your own functions, to achieve message-passing then the criteria does not allow the language to be included. Future work will examine library support, but as numerous examples exist this is currently outside the scope of this work.
For various reasons, some languages originally considered for the study have been discounted. These languages, and some information for there discounting, are listed below:
- Aha!. Appears to be unavailable now.
- ALGOL 68 - but may be stretching the definition. ALGOL 68 requires explicit use of shared values and semaphores to enable message passing. This violates criteria 4.
- AmbientTalk - instructions available here. On analysis, AmbientTalk does not meet criteria 2 (the message must be sent in a manner so that the receiver can choose when to receive it; therefore, giving the receiver control over its internal state). This is because messages are essentially asynchronous method calls on actors, not a communication that the actor can decide when to engage in.
- Ateji PX - seems to be unavailable now.
- Axum - but looks like this is closed.
- BCPL - details here - but looks like a complicated mechanism to allow communication. On examination, although BCPL does have coroutine support, message-passing has to be implemented by the programmer. As such, BCPL is dicounted.
- C= - although unsure how easy it is to communicate between concurrent components. Appears to be unavailable now.
- Cω - Microsoft Research page. Might not be suitable. After some investigation, Cω became the Joins Concurrency Library for .NET, although this is not a standard library and therefore does not meet the criteria. A Cω compiler can be downloaded, but it requires .NET 1.1, which is no longer supported. Thus, Cω has been discounted from the list.
- Clojure - instructions available here. At present this does not seem to meet the criteria. The
agents
package provides simple agents that respond to function calls but have no control over their state. Thecore.async
package does provide the functionality, but it is not core Clojure and therefore does not meet criteria 1. - Concurrent Pascal - although it might be a stretch saying this is live. A compiler for microcontrollers is available here. After some investigation it appears that Concurrent Pascal is not available. No version exists that can be executed on Linux.
- Smalltalk. Smalltalk's concurrency support is limited to forking processes and a semaphore for synchronisation. As such, it does not meet the criteria for message passing.
This section indicates when languages where released. The aim is to illustrate any clusters of development.
- (2) Prolog
- (1) Standard ML (as ML)
- (0)
- (0)
- (0)
- (1) Icon
- (0)
- (0)
- (0)
- (0)
- (1) SIGNAL
- (4) Ada, Esterel, occam, SISAL
- (0)
- (0)
- (3) Eiffel, Erlang, LabVIEW
- (1) Perl
- (3) Object REXX, PicoLisp, Tcl
- (1) SequenceL
- (3) Haskell, J, Python
- (1) Oz
- (1) μC++
- (4) Concurrent ML, Euphoria, Lua, SuperPascal
- (2) Newsquek, Racket
- (4) Java, Limbo, Mercury, Ruby
- (1) Ocaml
- (1) E
- (1) Logtalk
- (1) SystemC
- (4) C#, Hume, Join Java, Unicon
- (4) CAL Actor Language, D, SALSA, SpecC
- (2) Io, SystemVerilog
- (4) Aikido, ChucK, Falcon, Go!
- (1) Scala
- (3) F#, Neko, XC
- (1) Sequoia++
- (1) Dodo, Manticore
- (2) Nim, PREESM
- (3) Clump, Go, ProcessJ
- (2) Fancy, Rust
- (3) Elixir, Kotlin, Red
- (1) Julia
- (2) Wren, Zonnon
- (4) Goaldi, Oforth, Pony, Swift
- (2) Panda, Perl 6
- (1) Fantom
- (0)
- (0)
Unknown - FortranM, JoCaml, MPD, Mythryl, Phix, SR, Zkl
There are a few sites which provide useful information on programming languages:
- Tiobe Index uses Internet search engine results to determine language popularity.
- GitHub popularity provides information on language popularity in GitHub projects.
- Rosetta Code provides common examples in various programming languages.
- List of programming languages on Wikipedia if you want to see how many languages are out there. Not complete though.
The properties taken from process calculi are:
Generally, process calculi work with synchronous communication - that is two or more processes must agree to communicate and do so as a single atomic operation. For example, in CSP:
channel a, b
P = a -> b -> SKIP
Q = b -> SKIP
SYSTEM = P [{b} || {b}] Q
Q
will always wait until P
is ready to communicate via b
, and vice-versa. Not all languages support synchronous communication.
Does the language have a channel-like construct? Although process calculi generally have channels for inter-process communication, not all languages do. Typically, actor-style concurrent languages do not have channels.
The π-Calculus permits channels to be sent as messages, thus reconfiguring the communication network. For example (adapted from Wikipedia):
(new x)(x<z>.0 | x(y).y<x>.x(y).0) | z(v).v<v>.0
z
is communicated over channel x
in the first step:
(new x)(0 | z<x>.x(y).0) | z(v).v<v>.0
x
is communicated over channel z
in the next step:
(new x)(0 | x(y).0) | x<x>.0
In the final step, x
is communicated via channel x
:
(new x)(0 | 0) | 0
If the language's channels support sending any type, this will typically include channels.
Assigning an active process to a variable is not a process calculus feature except in the higher-order π-Calculus. First-order processes are included for two reasons:
- Languages without channels will use a process variable to send messages to.
- To discuss higher-order processes below.
If a process launch can be assigned to a variable, then the language supports first-order processes. Actor-style concurrency requires this feature, although other languages do support it.
The higher-order π-Calculus supports sending of process variables across a channel. For example:
x<R>.P | x(Y).Q
It has been shown that in the π-Calculus process mobility can be simulated via channel mobility. However, the ability to send a process name is used extensively in actor-style concurrency to build communication networks. Typically, if a language supports first-order processes it supports higher-order processes.
In process calculi, an operator defining parallel execution is normally provided. In CSP:
P |[{a}]| Q
P ||| Q
In π-Calculus:
P | Q
Few languages directly support a parallel execution statement. occam is one such example:
PAR
P()
Q()
R()
CSP supports a parallel execution statement:
Q = || i : {0..10} @ [A(x)] P(x)
Where A
is the alphabet of the replicated process P
. CSP works on alphabetised replication, so a set of any type can be used.
Typically, such a statement is provided in a language as a parallel for
or equivalent.
Does the language ensure no data is shared between processes? This is key concept in concurrency safety. Generally, languages supporting reference passing and pointers do not promote state ownership.
When a process is created it exists and is owned within the creating context. This can be tested by spawning a child process and checking if the owning process waits for child processes to complete when it ends. In process calculi this is implicit the parallel operator must be completed before a process can continue to the next operation.
A key feature of process calculi is the ability to emit behaviour based on a choice. Process calculi may provide different mechanisms for choice. For example, CSP specifies both internal and external choice.
An example of choice in CSP is:
channel a, b, c
P = (a -> P) [] (b -> SKIP)
Q = a -> SKIP
R = b -> SKIP
When run in parallel, P
may communicate on a
first or b
first. Depending on that choice the system can deadlock.
In the properties selection between incoming and outgoing messages has been seperated. This is because many languages will support input selection, but few support output selection. This property is met by being able to select from a range of incoming message sources. To do so will normally require first-order channels.
The ability to select from an array or similar range of incoming message sources via an index. In CSP this is called a replicated choice (either internal or external). For example:
channel a, b, c
P = a -> SKIP
Q = b -> SKIP
R = c -> SKIP
S = [] x : {a, b, c} @ x -> S
S
will select either a
, b
, or c
and complete the communication with the relevant process. It will do so three times until it deadlocks.
This feature appears to be uncommon in languages. It could be seen as an equivalant to a select for
statement. occam provides an ALT FOR
statement for this purpose.
The ability to select an incoming message based on the value of the message. In process calculi this is quite common:
channel a : Bool
P = (a.true -> P) [] (a.false -> SKIP)
In languages this is less common as it normally requires a read to be commited before the value is checked. Actor-style concurrency does provide a partial implementation based on the message type.
The ability to activate a selection choice based on a boolean condition. In CSP:
channel a, b
P(x) = (x < 5 & a -> P(x + 1)) [] (x >= 5 & b -> SKIP)
Q = b -> SKIP
SYSTEM = P(0) [{a, b} || {b}] Q
While x
is less than 5, P
will increment x
. Once x
equals 5, P
and Q
will sync and terminate.
Some languages appear to support this feature, and it there is no consistent pattern seen yet across language types.
Whether the selection can be considered fair as far as possible. Fair means that the probability of being selected is the same as other events in a selection operation over time. This can be achieved through random selection of ready guards, or deprioritising previously selected guards.
Fair selection is not a process calculi property. As long as a choice is made when possible that is enough. The probability of selection does not feature in the model. However, in practice languages attempt to support fair selection to allow easier reasoning.
Whether some form of timing can be used during a selection. Process calculi incorporating time do exist (e.g. Timed CSP). Because of this, and the prevalant use of timers in selection, timed selection has been added as a property to test for.
This criteria could include anything, but typically we want a default option that allows continuation if nothing is ready to select. In CSP, this can be modelled with SKIP:
channel a, b
P = (a -> P) [] (b -> SKIP) [] SKIP
Effectively we want to have a non-blocking selection option, which is a quite common design pattern.
As input selection, but the ability to select based on output messages. At present, only Go seems to support this feature. In process calculi there is no differentiation between input and output selction.
The ability for more than two processes to syncrhonise at once. Typically supported by a barrier, but any object that allows a group of processes to agree to synchronise. In CSP all parties must agree to synchronise on an event for it to become ready:
channel a, b, c, d
P = a -> d -> P
Q = d -> b -> Q
R = c -> d -> R
P
, Q
, and R
must agree to communicate on d
if they are suitably composed in parallel.
The ability to select on a multi-party synchronisation point. CSP does not differentiate between event types, so a multi-party synchronisation point can be in a choice operation. This has yet to be found in a programming language, although libraries such as JCSP do support it.
At present, three benchmarks have been used.
This benchmark measures the communication / coordination time of a message. Four processes are involved:
- Prefix outputs 0, then outputs what it inputs.
- Delta outputs its input on two channels sequentially.
- Successor increments its input before output.
- Consumer times how long it takes to receive N inputs.
The processes are connected together to produce the natural numbers. A rough CSP equivalent is:
channel a, b, c, d : Int
ID = c?x -> a!x -> ID
PREFIX(n) = a!n -> ID
DELTA = a?x -> b!x -> d!x -> DELTA
SUCC = b?x -> c!(x + 1) -> SUCC
CONSUMER = d?x -> CONSUMER
COMMSTIME = (((PREFIX(0) [{a,c} || {a,b,d}] DELTA) [{a,b,c,d} || {b,c}] SUCC) [{a,b,c,d} || {d}] CONSUMER)
The Communicaton Time benchmark (also referred to as commstime) was originally developed in occam by Peter Welch at the University of Kent.
This benchmark measures how long it takes to select a message from a N inputs. A single reading process services multiple inputs from N writers. The reader times how long it takes to perform a single selection for different sizes of N.
Some languages (typically actor-based ones with mailbox systems) do not fair well when N is greater than the number of threads as the reader cannot service the writers fast enough and the mailbox expands.
A simple data-parallel benchmark designed to test if the language has multi-core support. Speed-up measures are derived based on 2, 4, and 8 processes being run. It is not the time taken to perform the benchmark but the speedup that is important.