ParaSail: Less is more with multicore
No global variables
Many of us have been warned of the evils of global variables in our early programming courses, but nevertheless, global variables are widely used in most programs. Sometimes they aren’t globally visible, but nevertheless, anything that represents variable global state, such as a static variable in C, C++, or Java, or a singleton object in Scala , can create the same kinds of problems associated with unexpected side-effects, hidden algorithmic coupling, etc.
In a sequential program, global variables are often considered bad practice. In a parallel program, global variables can create nasty race conditions, or synchronization bottlenecks.
So how do you live without global variables? The ParaSail model is very straightforward – a function may only update data that are part of a var parameter passed to the function. This means that the specification of a function indicates all of the possible effects of the function.
There are no mysterious side effects to worry about. A typical function that is often implemented using a global variable is a (pseudo) random number generating function. In ParaSail, a module that provides random numbers could have the following interface:
Click on image to enlarge.
This could be used as follows:
Click on image to enlarge.
If desired, the first parameter in a call in ParaSail may generally be moved to the front, so instead of Next(Ran), one could write Ran.Next(). Note in the above example the types for First and Second are inferred from context, though they could be given explicitly. Below we show explicit types, and the alternative call notation:
const First : Univ_Integer := Ran.Next() mod 6 + 1;
const Second : Univ_Integer := Ran.Next() mod 6 + 1;
In the above declarations we are using Univ_Integer, a universal integer type that supports arbitrarily large integers. In many cases, the programmer will instead choose to declare a new integer type with a specified range, such as:
type Dice_Sum is Integer<2..12>;
func Roll_The_Dice(var Ran : Random) -> Dice_Sum is
Doing this can provide more efficiency and additional consistency checking, as well as more information to the user of the routine.
No Garbage-Collected Global Heap
Using a single garbage-collected heap is a common feature of languages like Java and other JVM languages, C# and other .NET languages, Eiffel, ML, and many other modern languages. But a single global heap can be a problem for a highly parallel application.
Due to the importance of the cache to performance, locality of reference is important within a single thread, while separation is important for threads operating in parallel to avoid cache interference effects, such as false sharing .
Unfortunately, a single global heap can produce the worst of both worlds – the globally-shared heap results in the objects manipulated by a single thread being potentially spread all over memory, while objects created by concurrently executing threads being allocated in neighboring memory locations.
So what is the alternative to a global, garbage-collected heap, without forcing users to resort to completely manual storage management?