Tutorial - Shared Memory
Tutorial number five - prev :: next
Contents
Introduction
In this tutorial we will be looking at using the, default, shared memory model for simple communication involving a single variable. It is important to understand the memory model behind this form of communication and when variables will be subject to communication.
Mesham follows the Logic Of Global Synchrony (LOGS) model of shared memory. This actually sounds much more formidable than it is in reality and follows a simple number of practical rules. Each variable can be thought of as starting in one state, finishing in another (if and when the code terminates) and throughout the program's life be in a number of intermediate states. We go from one intermediate state to the next when synchronisation is used and this can be thought of as barrier synchronisation.
My first communication
Communication depends on exactly where variables are allocated to which in itself is driven by types.
#include <io> #include <string> function void main() { var a:Int::allocated[multiple[]]; a:=1; proc 1 { a:=99; }; sync a; proc 0 { print(itostring(a)+"\n"); }; };
If you compile and run the following code then you will see the output 1 - so lets have a look what exactly is going on here. Variable a is allocated to all processes, all processes set the value to be 1, process one will then change the value to be 99, we do a barrier synchronisation on a and then process zero will display its value. Because a is allocated to all processes (via the multiple type), assignment and access is always local - i.e. in this case, process one modifying the value will have no impact on a held on other processes such as process zero.
So we have seen that variables allocated to all processes always involve local access and assignment, let's do something a bit more interesting - change the multiple[] to be single[on[0]] and recompile and run the code. Now the output is different and it displays 99. That is because if a variable is allocated just to a specific process and another one reads/writes to it, then this will involve remote access to that memory (communication.) Let's experiment further with this, remove a from the sync statement (line 10) and recompile and rerun, the result should be the same, 99 displayed. If we specify a variable with the sync keyword then this will barrier synchronise just on that variable, the sync by itself will barrier synchronise on all variables which require it. Ok then, now comment out the sync keyword entirely and recompile and run the code - see it now displays 1 again? This is because we can only guarantee that a value has been written into some remote memory after barrier synchronisation has occurred.
We have seen that if a variable is allocated to all processes then read/write will always be a local operation but if a variable is allocated just to a single process then read/write will be a remote operation on every other process.
Further communication
#include <io> #include <string> function void main() { var a:Int::allocated[single[on[0]]]; var b:Int::allocated[multiple[]]; proc 0 { a:=1; b:=a; }; sync b; proc 1 { print(itostring(b)+"\n"); }; };
The code snippet above is similar to the first one but with some important differences. We are declaring two variables; the first, a, is held on process zero only whereas the second, b, is allocated to all processes. Process zero then alone (via the proc statement will modify a locally (as it is held there) and then assign b to be the value of a. We then synchronise based upon variable b and process one will display its value of b. Stepping back a moment, what we are basically doing here is assigning a value to a variable allocated on all processes from one allocated on a single process. The result is that process zero will write the value of variable a into b on all processes (it is a broadcast.) If you remove the sync statement on line 11 then you will see that instead of displaying the value 1, 0 is displayed (the default Int initialisation value.) This is because synchronisation must occur to update this remote value on process one from process zero.
#include <io> #include <string> function void main() { var a:Int::allocated[multiple[commgroup[0,2]]]; proc 1 { a:=2; }; sync a; group 0, 2 { print(itostring(a)+"\n"); }; };
The same thing will happen with communication groups too compile and run the following code, you will see that process one has written the value 2 into the memory of variable a which is held on processes zero and two.
Single to single
If we have two variables which are allocated to single processes then any assignment involving these will either result in local or remote access depending on whether they are on the same process or not.
#include <io> #include <string> var processOneAllocation:=0; var processTwoAllocation:=0; function void main() { var a:Int::allocated[single[on[processOneAllocation]]]; var b:Int::allocated[single[on[processTwoAllocation]]]; proc processTwoAllocation { b:=23; a:=b; }; //sync a; group processOneAllocation { print(itostring(a)+"\n"); }; };
In the example above we are allocating variables a and b both on process zero, we are then performing an assignment a:=b at line 12 which, because the variables are on the same process is local and occurs immediately. Now, change processOneAllocation to be equal to 1 and uncomment the sync keyword at line 14 and recompile and run. See the same value - but now process 0 is writing the value of b into the remote memory of a and if you comment out the sync keyword then a value of 0 will be reported. The values of processOneAllocation and processTwoAllocation can be anything - if they are the same here then it is local and if not then remote.
Limits of communication
Currently all communication is based upon assignment, to illustrate this look at the following code
#include <io> #include <string> function void main() { var a:Int::allocated[single[on[1]]]; var b:Int; proc 0 { b:=a; }; };
If we compile this then we will get the error message Assignment must be visible to process 1 which is because, as communication is assignment driven, process one (which contains a) must drive this assignment and communication. To fix this you could change from process zero to process one doing the assignment at line 8 which would enable this code to compile correctly. It is planned in the future to extend the compiler to support this pull (as well as push) remote memory mechanism.