EMS stores tags internally that are used for synchronization of user data,
allowing synchronization to happen independently of the number or kind of processes accessing
the data.
The EMS intrinsics like stacks, queues, and transactional
memory are built on read, write, and read-modify-write operations that
enforce atomic access to JSON objects and values.
The API is structurally identical between languages, in many cases the exact
same syntax works for both Python2/3 and Node.js.
Unlike Python and Node.js,
C provides several different kinds of shared memory parallelism, including
OpenMP on which the BSP execution is based, the C API does not include functions
to initiate or control parallelism.
The C interface uses a structure to describe dynamically typed values defined in ems.h
:
#define EMS_TYPE_INVALID ((unsigned char)0)
#define EMS_TYPE_BOOLEAN ((unsigned char)1)
#define EMS_TYPE_STRING ((unsigned char)2)
#define EMS_TYPE_FLOAT ((unsigned char)3)
#define EMS_TYPE_INTEGER ((unsigned char)4)
#define EMS_TYPE_UNDEFINED ((unsigned char)5)
#define EMS_TYPE_JSON ((unsigned char)6)
typedef struct {
size_t length; // Defined only for JSON and strings
void *value; // Scalar value or pointer to buffer/string
unsigned char type; // One of the above EMS_TYPE_ values
} EMSvalueType;
SYNOPSIS |
Initialize the EMS module, starting
all the other threads. Thread identity and processor affinity
is assigned when the thread is created. Node/Python only, C parallelism is user managed.
|
Node.js |
require('ems') ( nThreads [, threadAffinity [,
parallelType [, contextName ] ] ] )
|
Python |
import ems
ems.initialize( nThreads [, threadAffinity [, parallelType [, contextName ] ] ] )
|
C |
#include "ems.h" // Parallelism in C is always user managed
|
ARGUMENTS |
nThreads |
<Number> |
Total number of threads the job should use.
|
|
threadAffinity |
<Boolean> |
(Optional, Default = false , Affects only Linux.)
Set the scheduling affinity of each thread to it's own
core, assigning over-subscribed threads in a round-robin
fashion.
|
|
parallelType |
<String> |
(Optional, Default=bsp )
One of bsp , fj , or user . Execution model:
bsp will use EMS' built-in Bulk Synchronous Parallel execution,
fj uses EMS' built-in Fork-join execution
(Python not supported in v1.4),
and user creates no parallelism.
|
|
contextName |
<String> |
(Optional, Default=anonymous )
Unique name of parallel context being initialized, required to distinguish
between multiple EMS parallel programs running simultaneously on the
same system.
|
RETURNS |
json = {
nThreads : Number, // Number of threads executing
myID : Number // This thread's ID 0..nThreads-1
}
|
Node.js EXAMPLES |
ems = require('ems')(process.argv[2]) |
Use the first command line argument as the number of nodes:
node foo.js 4 executes using 4 threads.
|
|
ems = require('ems')() |
Run on one node |
|
ems = require('ems')(process.argv[2], false, true)
|
Use first command line argument as number of nodes, do not set
affinity of the threads to a specific CPU, execute using fork-join parallelism.
|
Python EXAMPLES |
import EMS
ems = EMS.initialize(4, contextName = "/tmp/EMS_897")
|
Four processes operating in a private context persisting on /tmp
|
SYNOPSIS |
Attach to an existing or create a new EMS array.
Creation of new (do not use existing) EMS memory regions implies a barrier,
whereas using an existing EMS file will block until the file exists.
|
Node.js |
ems.new( [ nElements [, heapSize [, fileName] ] ] ) |
|
ems.new( emsArrayDescriptor ) |
Python |
ems.new( [ nElements [, heapSize [, fileName] ] ] ) |
|
ems.new( emsArrayDescriptor ) |
C |
int EMSinitialize(int64_t nElements,
int64_t heapSize,
bool useMap,
const char *filename,
bool persist,
bool useExisting,
bool doDataFill,
bool fillIsJSON,
EMSvalueType *fillValue,
bool doSetFEtags,
bool setFEtags,
int EMSmyID,
bool pinThreads,
int64_t nThreads,
int64_t pctMLock);
|
ARGUMENTS |
nElements |
<Number> |
(Optional, Default is 1) Maximum number of elements in the EMS array or map. |
|
|
<Array> |
An array of dimensions of a multi-dimensional array.
A 100×30×50 cube is described by [100, 30, 50] .
|
|
heapSize |
<Number> |
(Optional, Default is 0)
Maximum number of bytes reserved for strings,
arrays, maps, and object elements in this array.
The actual amount of memory allocated for use is
rounded up to the nearest power of 2
(until a better memory allocator is implemented)
.
Additional memory is allocated for bookkeeping.
Memory block size is defined in ems_alloc.h: EMS_MEM_BLOCKSZ
but should be configurable at create time
.
|
|
fileName |
<String> |
(Optional, Default is anonymous) Fully qualified file name
of the file to use as a persistent backing store for the EMS array,
tags, and bookkeeping information.
|
|
emsArrayDescriptor |
<Object> |
As an alternative to scalar argument(s),
A complete EMS array descriptor may be passed as the only argument.
emsArrayDescriptor = {
dimensions : 100, // Required: Max # elements in EMS array
// Integer for 1-D, or array of dims [x, y, z]
heapSize : 100000, // Optional, default=0: Space, in bytes, for
// strings, maps, and objects. Rounded up to nearest power of two
mlock : 0, // Optional, default=0: % of EMS memory to lock into RAM
ES6proxies: false, // Optional, default=False: Enable implied read/write in JS
useMap : true, // Optional, default=false: Map keys to indexes
useExisting : true, // Optional, default=false:
// Preserve data if an file already exists
persist : true, // Optional, default=true:
// Preserve the file after threads exit
doDataFill : false, // Optional, default=false: Initialize memory
dataFill : undefined,// Optional, If this property is defined,
// the EMS memory is filled with this value
doSetFEtags: false, // Optional, If true, tags are set to setFEtags.
setFEtags : 'full', // Optional, 'full' or 'empty', ignored if doSetFEtags==false
filename : '/path/to/file' // Optional, default=anonymous:
// Path to the persistent file of this array
}
NOTE: ES6 Proxy support varies with version of Node.js,
(it may not be supported, require a polyfill and/or
command line option, or work with nothing at all)
and may impose a performance penalty.
|
RETURNS |
<EMS Array | Number>
Node & Python return the EMS array, C returns an integer handle to the EMS array.
|
Node.js EXAMPLES |
var foo = ems.new(nItems) |
Create a new non-persistent shared memory EMS
array with no heap space. Scalar data (Number, Boolean, Undefined)
may be stored in this array, but not strings, arrays, or objects.
|
|
var foo = ems.new(nItems, size,
'/tmp/EMS_foo')
|
Create a file-backed EMS shared memory
space with the filename /tmp/EMS_foo . In addition to the
scalar storage,
space for strings totaling
size bytes is also reserved for strings, arrays, objects, and maps.
|
Python EXAMPLES |
x = ems.new({
'dimensions': [1000],
'heapSize': 20000,
'useMap': True,
'useExisting': False,
'filename': '/tmp/test.ems',
'doSetFEtags': True,
'setFEtags': 'empty'
}) |
Create a new non-persistent shared memory EMS
array with 1000 elements, uses mapped keys, and sets all data to Empty.
|
SYNOPSIS |
Parallel loop execution, distributing
the iterations among all the threads. The function is invoked
with one argument, the current iteration number. Iterations are
divided among the threads according to the
scheduling method specified.
Parallel for loops must not be nested.
The system should check for this and fall back on serial execution.
A barrier is implied at
the end.
|
Node.js |
ems.parForEach( first, last, function [, scheduling [, minChunk] ] ) |
Python |
ems.parForEach( first, last, function [, scheduling [, minChunk] ] ) |
C |
Parallelism provided by C program |
ARGUMENTS |
first |
<Number> |
Index to start iterating |
|
last |
<Number> |
Index to stop iterating (non-inclusive). |
|
func |
<Function> |
Loop body, only input argument is current loop index:
function foo(idx) {...} |
|
scheduling |
<String> |
If option is omitted, defaults to guided with a minimum chunk size of 1 iteration.
guided [minChunk] : Decreasing amounts of work are assigned to each task until
minChunk iterations per thread is reached. Load balancing occurs when new chunks are assigned
to threads.
static : Equal number of iterations are given to each thread, no dynamic load balancing is
performed.
dynamic : All threads share one index which is atomically
incremented by 1 after each iteration. Provides ideal load balancing at the
cost of high per-iteration overhead.
|
|
minChunk |
<Number> |
(Optional, only used when scheduling='guided' , default=1 ) Minimum
number of iterations assigned to a single thread.
|
Node.js EXAMPLES |
ems.parForEach(0, nItems-1, func) |
Execute the func function nItems-1 times with indexes
0..nItems-1 , inclusive.
|
|
ems.parForEach(10000, 20000, func,
'guided', 200)
|
Distribute iterations numbered 10,000-20,000 (inclusive) using the guided
method with a minimum chunk size of 200 iterations
|
Python EXAMPLES |
ems.parForEach(0, 999,
func, 'static')
|
Execute func() 1,000 times with indexes
0..999 inclusive. Distribute the iterations evenly across
threads in contiguous blocks.
|
SYNOPSIS |
Perform function func()
mutually exclusive of other threads. Serializes execution through
all critical regions.
Named regions would be more like OpenMP
|
Node.js |
ems.critical( func [, timeout] ) |
Python |
ems.critical( func [, timeout] ) |
C |
int EMScriticalEnter(int mmapID, int timeout) |
|
bool EMScriticalExit(int mmapID) |
ARGUMENTS |
func |
<Function> |
Function to perform sequentially. |
|
timeout |
<Number> |
Number of microseconds to wait before timing out.
Defaults to 10000μs. |
RETURNS |
|
<Boolean> |
True if critical region executed safely, otherwise False for timeout or other error. |
SYNOPSIS |
The read family of EMS
memory operations return the data stored in an EMS array element.
The value may be any JSON object or intrinsic value.
Python and Javascript support the ability to make EMS data access
transparent to different degrees, allowing expressions like
emsArray.foo = 123;
read
- Immediately and unconditionally returns the
stored value, ignoring the tag state.
Reading uninitialized mapped indexes
will return
undefined regardless of the default value.
In contrast to that, intent to modify operations
(readFE, readEF, cas, or faa )
all return the default value specified when the array was declared.
readFE
- Blocks until the data element is full, then
atomically reads the value and marks it empty.
readFF
-
Blocks until the data element is full, then
atomically reads leaving it full. This allows safe read access
of data which may be updated
simultaneously.
readFF ensures mutual exclusion,
and will block if the data is already under a Readers-Writer
lock.
readRW, releaseRW
-
Blocks until the data element is full or
already under a Readers-Writer lock, then increments the
Readers-Writer reference count. The
function
emsArray.releaseRW() decrements the reference
count, restoring the state to Full if no readers remain.
|
Node.js and Python |
emsArray.read(key), emsArray[key].read, x = emsArray.foo
emsArray.readFE(key), emsArray[key].readFE()
emsArray.readFF(key), emsArray[key].readFF()
emsArray.readRW(key), emsArray[key].readRW()
emsArray.releaseRW(key), emsArray[key].releaseRW()
|
C |
bool EMSreadRW(const int mmapID, EMSvalueType *key, EMSvalueType *returnValue);
bool EMSreadFF(const int mmapID, EMSvalueType *key, EMSvalueType *returnValue);
bool EMSreadFE(const int mmapID, EMSvalueType *key, EMSvalueType *returnValue);
bool EMSread(const int mmapID, EMSvalueType *key, EMSvalueType *returnValue);
int EMSreleaseRW(const int mmapID, EMSvalueType *key);
|
ARGUMENTS |
key |
<Number | String> |
key in the EMS array of data to read |
RETURNS |
read__ : < Number | Boolean | String | Undefined | Array | Object > |
|
releaseRW : <Number> |
Number of pending readers sharing the lock. |
EXAMPLES |
n = histogram.read(3)
|
Read the value in bin 3 of the histogram. |
|
idx = 'foo'
word = dictionary.read(idx)
word = dictionary[idx].read()
word = dictionary.foo |
Three functionally equivalent ways to read dictionary word at
key='foo' in the EMS array called dictionary . |
|
x = arr.readFE(idx) |
Block until the element at index i
is full, atomically read the value and mark it empty.
|
|
x = arr.readFF(idx) |
Block until the element at index i is full,
atomically read the value and leave it full.
|
|
x = arr.readRW(idx)
// Do work using x while it cannot be modified
arr.releaseRW(idx) |
Acquire a shared-exclusive readers-writer lock. |
SYNOPSIS |
Write a JSON value or object to an EMS array.
write
- Immediately and unconditionally
writes the value to memory. This operation does not honor or
modify the full/empty tag status.
writeXE
- Unconditionally and atomically writes the value to the data element and marks the element empty.
writeXF
- Unconditionally and atomically writes the value to the data element and marks the element full.
writeEF
- Blocks until the element is empty, and then atomically writes the value and marks the element
full.
|
Node.js and Python |
emsArray.write(index, value), emsArray[index].write(value), emsArray.foo = x
emsArray.writeXE(index, value), emsArray[index].writeXE(value)
emsArray.writeXF(index, value), emsArray[index].writeXF(value)
emsArray.writeEF(index, value), emsArray[index].writeEF(value)
|
C |
bool EMSwriteXF(int mmapID, EMSvalueType *key, EMSvalueType *value);
bool EMSwriteXE(int mmapID, EMSvalueType *key, EMSvalueType *value);
bool EMSwriteEF(int mmapID, EMSvalueType *key, EMSvalueType *value);
bool EMSwrite(int mmapID, EMSvalueType *key, EMSvalueType *value);
|
ARGUMENTS |
index |
<Number | String | Bool> |
Index in the EMS array of data to read |
|
value |
<Any> |
Primitive value to store in the array at element numbered index. |
EXAMPLES |
idx = 'foo'
histogram.write(idx, 0)
histogram[idx].write(0)
histogram.foo = 0 |
Initialize the value of histogram['foo'] to 0,
ignoring full/empty status. |
|
arr.writeXE(i, undefined) |
Purge the memory at index i of the EMS array arr . |
|
arr.writeXF(j, 'Mr. Jones') |
Unconditionally write the
string 'Mr. Jones' to the EMS array arr
at index j and atomically mark the element full. |
|
arr.writeEF(2, v) |
Block until the element at index 2
of arr is empty, atomically write the
value v and mark the memory full. |
Node.js and Python |
emsArray.faa( index, value ), emsArray[index].faa(value) |
C |
bool EMSfaa(int mmapID, EMSvalueType *key, EMSvalueType *value, EMSvalueType *returnValue);
|
SYNOPSIS |
Atomically read the element (scalar or string, not array or object),
add the value, and write the new value
back to memory. Return the original contents of the memory.
|
ARGUMENTS |
index |
<Number | String> |
Index of the element in the EMS array emsArray
to atomically add to.
|
|
value |
< Number | Boolean | String | Undefined > |
Value to add to the EMS memory. |
SYNOPSIS |
Atomically read the JSON primitive element (scalar or string, not object or array)
stored at the array's index,
compare the original value to oldValue , and if they are equivalent
store the new value.
The CAS operation succeeded
if the value returned is equivalent
to oldValue .
CAS will block until the EMS memory is marked full. CAS is the
equivalent of atomically performing:
if (arr[idx] == oldValue) then arr[idx] = newValue
|
Node.js and Python |
emsArray.cas( index, oldValue, newValue )
emsArray[index].cas(oldValue, newValue)
|
C |
bool EMScas(int mmapID, EMSvalueType *key, EMSvalueType *oldValue, EMSvalueType *newValue, EMSvalueType *returnValue);
|
ARGUMENTS |
index |
<Integer | String> |
Index into the EMS array to update.
|
|
oldValue |
<Number | Boolean | String | Undefined> |
Value to compare to the value stored in memory.
|
|
newValue |
<Any > |
Value to store if the value in memory is oldValue
|
Composed operations use EMS intrinsics to perform
deadlock free atomic operations
involving multiple EMS elements. The composed operations use the tags
and require data to be full or empty as appropriate for the semantics of the operation.
SYNOPSIS |
Lock (by transitioning tags from Full to Empty)
one or more EMS elements in a deadlock
free way. When multiple locks must be acquired, this function
guarantees at least one thread will always make progress. The
optional third element indicates the element is read-only and will
not be modified by the task while the lock is held. Read-only
data is locked using a Readers-Writer lock, permitting additional concurrency.
Performing transactions within transactions can result in deadlock
if the thread tries to recursively lock an element.
|
Node.js and Python |
ems.tmStart( tmElements )
ems.tmEnd( tmHandle, doCommit )
|
C |
C transactions are not presently implemented
|
ARGUMENTS |
tmElements |
<Array> |
Array identifying which EMS array
elements should be locked. Each array element is itself an array
naming the EMS array and index/key of the data
and an optional Read-Only hint:
[ emsArray, index (, isReadOnly) ]
|
|
tmHandle |
<Object> |
Returned from tmStart() ,
contains state information needed to abort or commit the transaction.
|
|
doCommit |
<Boolean> |
Commits the transaction if true ,
or aborts and rolls back the transaction if false or undefined .
|
RETURNS |
ems.tmStart() : < tmHandle > |
Transaction Handle used later to commit or abort. |
EXAMPLES |
tm = ems.tmStart( [ [users, 'bill', true],
[comments, 'george'] ] )
|
Lock element 'bill' in the users
EMS array with a read-only intent, and also lock record 'george' in
the comments EMS array.
|
|
tm = ems.tmStart( [ [arrA, idxA0],
[arrA, idxA1] ] )
|
Lock indexes idxA0 and idxA1 in array arrA
for update to both values.
|
|
tm = ems.tmStart([ [arrA, idxA0],
[arrA, idxA1, true],
[arrB, idxB0, true] ])
|
Acquire and free locks on the elements in lockList .
Element arrA[idxA0] may be modified, but elements
arrA[idxA1] and arrB[idxB0] are read-only.
|
|
ems.tmEnd( tm, true )
|
Commit the transaction
|