Skip to content

Commit

Permalink
Add Python/Javascript inter-langauge example
Browse files Browse the repository at this point in the history
  • Loading branch information
Jace A Mogill committed Mar 12, 2017
1 parent 2096482 commit b2efeb2
Show file tree
Hide file tree
Showing 5 changed files with 425 additions and 4 deletions.
30 changes: 27 additions & 3 deletions Examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* [Web Server](#web_server.js)
* [Word Counting](#Word\ Count)
* [Implied EMS Operations](#harmony_proxies.js)
* [Inter-language Programming](#Inter-language\ Programming)

## Simple Loop Benchmarks
The original [STREAMS](https://www.cs.virginia.edu/stream/)
Expand Down Expand Up @@ -193,9 +194,9 @@ documents from Project Gutenberg.
## harmony_proxies.js
Ordinary JS objects can be made into EMS objects by wrapping them
using ES6 proxies. Access to the object uses EMS `read` and `write` operations
that do not use full/empty tag bits preserving legacy load/store semantics,
but inherit the atomic and transactional capabilities of EMS.
The syntax allows incrementally adding EMS operations to legacy programs.
that do not use full/empty tag bits preserving legacy load/store semantics,
but inherit the atomic and transactional capabilities of EMS.
The syntax allows incrementally adding EMS operations to legacy programs.
```javascript
emsData["foo"] = 123 // Equivalent to emsData.write("foo", 123)
Expand All @@ -205,3 +206,26 @@ emsData.readFE("foo") // 123, read "foo" when full and atomically mark empty
emsData.writeEF("foo", "one two three") // Write "foo" when empty and mark full
emsData["foo"] // "one two three", Full/empty tags not used
```
## Inter-language Programming
The programs `interlanguage.js` and `interlanguage.py` demonstrate sharing
objects between Javascript and Python.
A variety of synchronization and execution models are shown.
### Possible Orders for Starting Processes
| Persistent EMS Array File | (Re-)Initialize | Use |
| :------------- |:-------------:| :-----:|
| Already exists | A one-time initialization process truncates and creates the EMS array. The first program to start must create the EMS file with the ```useExisting : false``` attribute | Any number of processes may attach to the EMS array in any order using the attribute `useExisting : true` |
| Does not yet exist | Subsequent programs attach to the new EMS file with the `useExisting : true` attribute | N/A |
### Synchronizing Processes
EMS collective operations like barriers and parallel loops are
not available in `user` parallelism modes like inter-language programs.
An example of a simple two process barrier is shown in the example,
a more scalable and robust implementation can be found in the EMS
source code.
209 changes: 209 additions & 0 deletions Examples/interlanguage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
/*-----------------------------------------------------------------------------+
| Extended Memory Semantics (EMS) Version 1.4.4 |
| 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
*/
"use strict";
let ems = require("ems")(1, false, "user"); // User mode parallelism -- no EMS parallel intrinsics

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": "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

// 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
Loading

0 comments on commit b2efeb2

Please sign in to comment.