Search This Blog

Showing posts with label parallel. Show all posts
Showing posts with label parallel. Show all posts

Sunday, January 26, 2020

Using arrays with parallel

OpenAF is a mix of Javascript and Java, but "pure" javascript isn't "thread-safe" in the Java world. Nevertheless being able to use Java threads with Javascript is very useful starting with performance.

One common pitfall is not paying attention to what is "thread-safe" and what isn't. Meaning, what is "aware" that it can be running on a multi-thread environment where several threads might be competing for access to a resource and what isn't.

Let's see a pratical example of a common pitfall with arrays.

Example of what should NOT be done

In this example we will create a simple array and push new elements in parallel. So we would expect that it would have the same number of elements always:

var targetArray = [];
// making up a source array with 10 elements to push to targetArray
var sourceArray = repeat(9, '-').split(/-/);
parallel4Array(source, v => {
    targetArray.push(v);
    return v;
});

print(sourceArray.length);  // 10
print(targetArray.length);  // 10

Great, so for a source array and target array are equal. It works, right? Let's increase the "thread competition" to 10000 and compare again:

var targetArray = [];
var sourceArray = repeat(9999, '-').split(/-/);
parallel4Array(source, v => {
    targetArray.push(v);
    return v;
});

print(sourceArray.length);  // 10000
print(targetArray.length);  // 9961

There are no longer equal. And if you run it again and increase the number of elements on the source array the difference will increase.

This is what happens when you forget that a javascript array is not thread-safe (and it's actually a good thing because being thread-safe is usually slower when you are not using threads).

Examples of what should be done

Using sync

The first immediate way to solve this is to ensure that just one thread will access the targetArray at a time. You can do this in OpenAF with the sync function:

var targetArray = [];
var sourceArray = repeat(9999, '-').split(/-/);
parallel4Array(source, v => {
    sync(() => {
        targetArray.push(v);
    }, targetArray);
    return v;
});

print(sourceArray.length);  // 10000
print(targetArray.length);  // 10000

The sync function will use Java underneath (synchronize) to ensure that only one thread at a time accessing targetArray will execute the provided function.

So the problem is solved, right? Well if you measure the performance of using sync and not using sync you will notice that it can be up to twice as bad.

Why? Because when one thread is inserting a value into targetArray all other threads have to stop and wait. That will slow down everything and probably make it not so much as effective as running it sequentially (depending on the processing outside the sync function).

Using syncArray

On the OpenAF library ow.obj there is a wrapper for you to use a "thread-safe" java array: ow.obj.syncArray

ow.loadObj();
var targetArray = new ow.obj.syncArray();
var sourceArray = repeat(99999, '-').split(/-/);
parallel4Array(source, v => {
    targetArray.add(v);
    return v;
});

print(sourceArray.length);            // 100000
print(targetArray.toArray().length);  // 100000

The java array version is optimized to be faster in these conditions. So the bigger is the sourceArray the bigger the benefits with ow.obj.syncArray.

The ow.obj.syncArray has more methods that you can explore from the OpenAF's help including: addAll, clear, get, indexOf, length, remove and even getJavaObject that will let you iteract with the original java object directly.

Comparison

Changing the above examples to perform something "harder" for each array element like calculating the Math.sin of each value and then comparing the performance you would get something similar to:

Strategy Source size Target size Average time
Parallel 100000 <100000 2.11s
Sync 100000 100000 2.44s
ow.obj.syncArray 100000 100000 2.06s

But what's the time if it's done sequentially? In this simple example: a lot better (~1.1s). Keep in mind that you only gain a performance advantage when the time spent dealing with threads and concurrent access is lot lower relatively to the time spent processing each array element.

So, in conclusion, there is no right or wrong answer. You need to test to get the best for your case.

Wednesday, October 2, 2019

Using the function parallel4Array

OpenAF allows you to code in javascript while using Java functionality underneath. But there are obvious differences between the two languages.

Example

Let's take a simple example for parallel processing. First you create an array with the information of several files:

var files = listFilesRecursive("/usr");

Running it sequentially

Next you want to capture the result of executing a "not so fast" operation of performing an external shell command and capture the result:

var res = [];
for(var i = 0; i < files.length; i++) {
    res.push(sh("ls " + files[i]));
}

Using this simple for cycle the time elapsed, on a specific environment, was 1 second and 686 ms.

Running it in parallel

But this was a rather sequential processing in nature. OpenAF includes a function, parallel4Array, that wlil automate much of the work of using Java threads to perform the same exact functionality:

var res = parallel4Array(files, (v) => {
    return sh("ls " + v.filename);
})

Changing the for cycle by parallel4Array, and testing on the same environment, the result was a exactly equal res array in around 842ms. So what happenend?

Breaking it down:

  • The number of cpu cores was detected.
  • The files array was split evenly between the number of cpu cores detected.
  • Each "splitted array" was executed sequentially on separate executing thread (Java thread).
  • OpenAF kept an eye on the operating system reported machine load introducing delays when cpu cores got "overloaded".

This last step is helpful when executing on machines with a large number of cpu cores.

"Can I tune it?"

Yes, of course. A third optional argument of parallel4Array let's you override the cpu detection and "machine load" control. A fourth argument of parallel4Array gives you direct access to the Java threads objects, unique ids, etc… Check the help information in openaf-console: "help parallel4Array".

For advanced cases you can use the function parallelArray which let's you control how the end result will be built letting you provide a function to "unify" all the threads results.

There is even the function parallel that will launch multiple threads to execute the same provided function without the need to provide an array.

Monday, July 29, 2019

Executing code asynchronously (promises)

The execution of an OpenAF script is inherently sequential. Although it's easy to understand the execution flow (e.g. after a executes b) it might not have the best performance since it needs to wait for the end of the execution of the previous instruction to execute the next. Sometimes it makes sense because there is a dependency but in other cases it could be already executing something else asynchronously. OpenAF, through the Threads plugin, allows you access to the underneath great Java Threads functionality but the code may become hard to understand. An easy way to keep it simple but run asynchronously code is to use "promises". If you know how to use promises in other languages you will find it similar after checking the help information ("help $do"). For those that are not so familiar OpenAF tries to provide a easy and simple implementation.

You start be creating a promise for the code you want to run asynchronously:
var promise = $do(() => { 
   // my code
});

The difference is that no matter what "my code" is OpenAF will return you immediately a promise object. "my code" will execute asynchronously a.s.a.p. and the promise object will be updated accordingly. But once "my code" executes you usually want to chain other actions. To chain more code you can use ".then":
var promise = $do(() => { 
                 // my code
                 
                 return aResult;
              }).then((someResult) => {
                 // process the result

                 return processSuccess; 
              }).then((processStatus) => {
                 // etc... you get the point.
              });

What if any of these chained pieces of code throws an exception? At that point the promise won't be "fulfilled" but you can add ".catch" to handle it as you would do with a "try..catch":
var promise = $do(() => { 
                 // my code
                 
                 return aResult;
              }).then((someResult) => {
                 // process the result

                 return processSuccess; 
              }).then((processStatus) => {
                 // etc... you get the point.
              }).catch((error) => {
                 // handle the error
              });
So, what can you do with a promise object?
  • You can wait for the end of the execution at any point blocking the current execution with $doWait(promise).
  • If you have an array of promise objects you can use $doFirst (waits for one of the promises to be "fulfilled") and $doAll (waits for all of the promises to be "fulfilled"). Both will return a single promise object that once "fulfilled" resumes the current execution.
How does that look like:
var arrayOfPromises = [];
for (let idx in something) {
   arrayOfPromises.push($do( /* ... */ ));
}

// Yes, doFirst returns a promise, so you can chain more
arrayOfPromises.push($doFirst(arrayOfPromises)
                     .then(() => { 
                        print("One it's done!"); 
                     }));

var allPromises = $doAll(arrayOfPromises)
                  .then(() => {
                     print("Everything is done now!");
                  }));

/* do something else */

// Wait for all of them to finish
$doWait(allPromises);

Ok. But won't that just lunch a ton of threads "burning" down my machine? No. You don't need to worry about that. Underneath it will create a set of threads giving the number of identified compute cores and reuse them. So some promises will actually be waiting for others to finish and to have a thread available for them to execute.
In OpenAF there is also the function parallel4array and others to make it easy (and smarter) to asynchronously process an array since a promise for each might not actually give the best performance. But that will be the topic of another post.

Using arrays with parallel

OpenAF is a mix of Javascript and Java, but "pure" javascript isn't "thread-safe" in the Java world. Nevertheless be...