ems-typed
Version:
Persistent Shared Memory and Parallel Programming Model
216 lines (182 loc) • 9.58 kB
JavaScript
/*-----------------------------------------------------------------------------+
| Extended Memory Semantics (EMS) Version 1.4.5 |
| Synthetic Semantics http://www.synsem.com/ mogill@synsem.com |
+-----------------------------------------------------------------------------+
| Copyright (c) 2017, Jace A Mogill. All rights reserved. |
| |
| Redistribution and use in source and binary forms, with or without |
| modification, are permitted provided that the following conditions are met: |
| * Redistributions of source code must retain the above copyright |
| notice, this list of conditions and the following disclaimer. |
| * Redistributions in binary form must reproduce the above copyright |
| notice, this list of conditions and the following disclaimer in the |
| documentation and/or other materials provided with the distribution. |
| * Neither the name of the Synthetic Semantics nor the names of its |
| contributors may be used to endorse or promote products derived |
| from this software without specific prior written permission. |
| |
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SYNTHETIC |
| SEMANTICS LLC BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
| LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
| NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
+-----------------------------------------------------------------------------*/
/*
Start this JS program first, then start the Python program
Possible Orders for Starting Processes
---------------------------------------------
A persistent EMS file already exists
1. A one-time initialization process creates the EMS array
2. Any number of processes my attach to the EMS array in
any order.
A new EMS array will be created
1. The first program to start must create the EMS file with
the "useExisting : false" attribute
2. Subsequent programs attach to the new EMS file with the
"useExisting : true" attribute
*/
;
// Initialize EMS with 1 process, no thread-CPU affinity,
// "user" mode parallelism, and a unique namespace for EMS runtime
// ("jsExample") to keep the JS program distinct from Python EMS
// program also running.
let ems = require("ems")(1, false, "user", "jsExample");
const maxNKeys = 100;
const bytesPerEntry = 100; // Bytes of storage per key, used for key (dictionary word) itself
let shared = ems.new({
ES6proxies: true, // Enable native JS object-like syntax
useExisting: false, // Create a new EMS memory area, do not use existing one if present
filename: "/tmp/interlanguage.ems", // Persistent EMS array's filename
dimensions: maxNKeys, // Maximum # of different keys the array can store
heapSize: maxNKeys * bytesPerEntry,
setFEtags: "empty", // Set default full/empty state of EMS memory to empty
doSetFEtags: true,
useMap: true // Use a key-index mapping, not integer indexes
});
//------------------------------------------------------------------------------------------
// Begin Main Program
console.log("JS Started, waiting for Python to start...");
// One-time initialization should be performed before syncing to Py
shared.writeXF("nestedObj", undefined);
// Initial synchronization with Python
// Write a value to empty memory and mark it full
shared.writeEF("JS hello", "from Javascript");
// Wait for Python to start
console.log("Hello " + shared.readFE("Py hello"));
/*
This synchronous exchange was a "barrier" between the two processes.
The idiom of exchanging messages by writeEF and readFE constitutes
a synchronization point between two processes. A barrier synchronizing
N tasks would require N² variables, which is a reasonable way to
implement a barrier when N is small. For larger numbers of processes
barriers can be implemented using shared counters and the Fetch-And-Add
(FAA) EMS instruction.
The initialization of EMS values as "empty" occurs when the EMS array
was created with:
"setFEtags": "empty",
"doSetFEtags": true,
If it becomes necessary to reset a barrier, writeXE() will
unconditionally and immediately write the value and mark it empty.
*/
// Convenience function to synchronize with another process
// The Python side reverses the barrier names
function barrier(message) {
console.log("Entering Barrier:", message);
shared.writeEF("py side barrier", undefined);
shared.readFE("js side barrier");
console.log("Completed Barrier.");
console.log("---------------------------------------------------");
}
// --------------------------------------------------------------------
barrier("Trying out the barrier utility function");
// --------------------------------------------------------------------
// JS and Python EMS can read sub-objects with the same syntax as native objects,
// but writes are limited to only top level attributes. This corresponds to the
// implied "emsArray.read(key).subobj" performed
shared.top = {};
console.log("Assignment to top-level attributes works normally:", shared.top);
shared.top.subobj = 1; // Does not take effect like top-level attributes
console.log(
"But sub-object attributes do not take effect. No foo?",
shared.top.subobj
);
// A nested object can be written at the top level.
shared.nestedObj = { one: 1, subobj: { left: "l", right: "r" } };
console.log(
"Nested object read references work normally",
shared.nestedObj.subobj.left
);
// A workaround to operating on nested objects
let nestedObjTemp = shared.nestedObj;
nestedObjTemp.subobj.middle = "m";
shared.nestedObj = nestedObjTemp;
// The explicitly parallel-safe way to modify objects is similar to that "workaround"
nestedObjTemp = shared.readFE("nestedObj");
nestedObjTemp.subobj.front = "f";
shared.writeEF("nestedObj", nestedObjTemp);
// --------------------------------------------------------------------
barrier("This barrier matches where Python is waiting to use nestedObj");
// --------------------------------------------------------------------
// Python does some work now
// Initialize the counter for the next section
shared.writeXF("counter", 0);
// --------------------------------------------------------------------
barrier("JS and Py are synchronized at the end of working with nestedObj");
// --------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Use the EMS atomic operation intrinsics to coordinate access to data
// This is 10x as many iterations as Python in the same amount of time
// because V8's TurboFan compiler kicks in.
for (let count = 0; count < 1000000; count += 1) {
let value = shared.faa("counter", 1);
if (count % 100000 == 0) {
console.log("JS iteration", count, " Shared Counter=", value);
}
}
// --------------------------------------------------------------------
barrier("Waiting for Py to finish it's counter loop");
// --------------------------------------------------------------------
console.log("The shared counter should be 11000000 ==", shared.counter);
// ---------------------------------------------------------------------------
// Ready to earn your Pro Card?
// Wait for Py to initialize the array+string
barrier("Wait until it's time to glimpse into the future of Javascript");
console.log("The array+string from Py:", shared.arrayPlusString);
barrier("Waiting for shared.arrayPlusString to be initialized");
console.log(
"JS can do array+string, it produces a string:",
shared.arrayPlusString + " world!"
);
shared.arrayPlusString += " RMW world!";
console.log(
'JS performing "array + string" produces a self-consistent string:',
shared.arrayPlusString
);
barrier("Arrive at the future.");
// ---------------------------------------------------------------------------
// Leave a counter running on a timer
// First re-initialize the counter to 0
shared.writeXF("counter", 0);
function incrementCounter() {
// Atomically increment the shared counter
let oldValue = shared.faa("counter", 1);
// If the counter has been set to "Stop", clear the timer interval
if (oldValue == "stop") {
console.log("Timer counter was signaled to stop");
clearInterval(timerCounter);
}
// Show counter periodically
if (oldValue % 100 == 0) {
console.log("Counter =", shared.counter);
}
}
var timerCounter = setInterval(incrementCounter, 10); // Increment the counter every 1ms
barrier("Welcome to The Future™!");
// Fall into Node.js event loop with Interval Timer running