Chapel Tutorial for Programmers
This tutorial is a quick introduction to Chapel for programmers. (It expects you to already be comfortable programming in another language.) This code is currently compatible with the version 1.8.0 Chapel compiler. (Last Updated Nov. 2013.) If you are waiting for it to work for a new version, it is okay to let me know I'm being slow!
test.chpl
in your favorite text editor. Then, let's declare our first variable. Add the following line to your code:var maximum: real = 0.0;
var maximum = 0.0;
writeln("maximum: ", maximum);
var n: int = 10;
var realArray: [0..n-1] real;
realArray[0] = 1.0;
realArray[1] = 2.0;
realArray[2] = 3.0;
realArray[3] = 4.0;
realArray[4] = 5.0;
realArray[5] = 4.0;
realArray[6] = 3.0;
realArray[7] = 2.0;
realArray[8] = 1.0;
realArray[9] = 0.0;
writeln("realArray: ", realArray);
$ chpl test.chpl
$ ./a.out
maximum: 0.0
realArray: 1.0 2.0 3.0 4.0 5.0 4.0 3.0 2.0 1.0 0.0
for realNumber in realArray {
if (realNumber > maximum) {
maximum = realNumber;
}
}
writeln("Max: ", maximum);
proc findMax(array) {
var arrayMax = array[0];
for number in array {
if (number > arrayMax) {
arrayMax = number;
}
}
return arrayMax;
}
writeln("Max of realArray is: ", findMax(realArray));
maximum
variable, compile, and run the code to make sure it's still working.Time
package. Let's do that next! Add this to the top of your code (first line):use Time;
Timer
objects inside the code. Add lines before and after the printing line like so: var timer: Timer;
timer.start();
writeln("Max of realArray is: ", findMax(realArray));
timer.stop();
writeln("That took ", timer.elapsed(), " seconds.");
var timer: Timer;
timer.start();
maximum = findMax(realArray);
timer.stop();
writeln("Max of realArray is: ", maximum);
writeln("That took ", timer.elapsed(), " seconds.");
n
. The main problem is that we don't want to have to explicitly initialize each element of realArray
in the code. So, next, let's learn how to create arrays of random values. We will use the Random
package. Just like before, add a use
statement to the top of your code:use Time;
use Random;
realArray
to the following:var n = 10;
var realArray : [0..n-1] real;
var randomStream = new RandomStream(SeedGenerator.currentTime);
randomStream.fillRandom(realArray);
writeln("realArray: ", realArray);
n
to be larger:var n = 500;
n
? Absolutely! Let's make that happen using configurable constants. Change the initialization of n
to the following:config const n = 500;
n
different from the code, you declare the new value like so:./a.out --n=5000
proc findMaxInRange(array, lower, upper) {
var arrayMax = array[lower];
while (lower <= upper) {
if (array[lower] > arrayMax) {
arrayMax = array[lower];
}
lower += 1;
}
return arrayMax;
}
error: non-lvalue actual passed to ref argument
". This happens because Chapel treats passed integers as constants; you can't modify them. You can fix this by changing the function signature:proc findMaxInRange(array, in lower, upper) {
in
tells Chapel not to treat that variable as a constant. Another option to fix this problem is to create a separate index variable. Instead of using in
, I will continue from the following example, using index i
, which might make the code easier to read:proc findMaxInRange(array, lower, upper) {
var i = lower;
var arrayMax = array[i];
while (i <= upper) {
if (array[i] > arrayMax) {
arrayMax = array[i];
}
i += 1;
}
return arrayMax;
}
findMax
to use that new function:proc findMax(array) {
var middle = array.numElements/2;
var lowerMax, upperMax: real;
lowerMax = findMaxInRange(array, 0, middle);
upperMax = findMaxInRange(array, middle+1, array.numElements - 1);
if (lowerMax > upperMax) { return lowerMax; }
return upperMax;
}
max
function:proc findMax(array) {
var middle = array.numElements/2;
var lowerMax, upperMax: real;
lowerMax = findMaxInRange(array, 0, middle);
upperMax = findMaxInRange(array, middle+1, array.numElements - 1);
return max(lowerMax, upperMax);
}
cobegin
:proc findMax(array) {
var middle = array.numElements/2;
var lowerMax, upperMax: real;
cobegin ref(lowerMax, upperMax) {
lowerMax = findMaxInRange(array, 0, middle);
upperMax = findMaxInRange(array, middle+1, array.numElements - 1);
}
return max(lowerMax, upperMax);
}
cobegin
creates a new task for each line (with the hope they will get assigned to their own hardware thread) then waits for all of them to finish before continuing with the rest of the code. The ref(lowerMax, upperMax)
tells Chapel it can modify those variables that were declared outside the cobegin
. (Without this statement, it treats most of those "outside" variables as constants.) Awesome! Compile and run your code again.fastFindMaxInRange
, a recursive version of findMaxInRange
. Important: Chapel doesn't automatically determine the return type of recursive functions, so your signature needs to declare this: proc fastFindMaxInRange(array, lower, upper) : real {
cobegin
. If you get stuck, my solution is below. proc fastFindMaxInRange(array, lower, upper) : real {
if (lower == upper) { return array[lower]; }
var lowerMax, upperMax: real;
var middle = ((upper - lower) / 2) + lower;
cobegin ref(lowerMax, upperMax) {
lowerMax = fastFindMaxInRange(array, lower, middle);
upperMax = fastFindMaxInRange(array, middle + 1, upper);
}
return max(lowerMax, upperMax);
}
proc serialFindMax(array) {
return findMaxInRange(array, 0, array.numElements - 1);
}
proc parallelFindMax(array) {
return fastFindMaxInRange(array, 0, array.numElements - 1);
}
timer.clear();
timer.start();
var serialMax = serialFindMax(realArray);
timer.stop();
var serialSeconds = timer.elapsed();
timer.clear();
timer.start();
var parallelMax = parallelFindMax(realArray);
timer.stop();
var parallelSeconds = timer.elapsed();
timer.clear();
writeln("SERIAL test: found the max to be ", serialMax, " in ", serialSeconds, " seconds.");
writeln("PARALLEL test: found the max to be ", parallelMax, " in ", parallelSeconds, " seconds.");
var speedup = serialSeconds / parallelSeconds;
if (speedup > 1) {
writeln(" The parallel trial achieved a ", speedup, "x speedup over the serial!");
} else {
writeln(" No speedup attained!");
}
n = 5000
on my 8-CPU machine gives me the following output:$ ./a.out --n=5000
...
Serial test: found the max to be 0.999846 in 0.0008 seconds.
Parallel test: found the max to be 0.999846 in 0.55384 seconds.
Achieved a 0.00144446x speedup!
hybridFindMaxInRange
, similar to fastFindMaxInRange
, except with an extra parameter, numTasks
, which specifies the maximum number of tasks to use in this function call. Try to write this yourself; my solution is below if you get stuck. proc hybridFindMaxInRange(array, lower, upper, numTasks) : real {
if (lower == upper) { return array[lower]; }
if (numTasks == 1) {
return findMaxInRange(array, lower, upper);
}
var lowerMax, upperMax: real;
var middle = ((upper - lower) / 2) + lower;
var lowerTasks = numTasks / 2;
var upperTasks = numTasks - lowerTasks;
cobegin ref(lowerMax, upperMax) {
lowerMax = hybridFindMaxInRange(array, lower, middle, lowerTasks);
upperMax = hybridFindMaxInRange(array, middle+1, upper, upperTasks);
}
return max(lowerMax, upperMax);
}
proc hybridFindMax(array, numTasks) {
return hybridFindMaxInRange(array, 0, array.numElements-1, numTasks);
}
$ ./a.out --n=100000000 --threads=8
...
SERIAL test: found the max to be 1.0 in 6.5324 seconds.
HYBRID test: found the max to be 1.0 in 0.816864 seconds.
The hybrid achieved a 7.99692x speedup over the serial trial!
maximum = max reduce realArray;