Reference Page

From CSE231 Wiki
Jump to navigation Jump to search

This is a collection of all the the different tools we use in this course and their syntax.

While this page is here to assist you whenever you forget how to create a lambda or register a phaser, try not to become too reliant on it. If you learn and understand the syntax for the tools we build in this class, it will make the assignments easier and faster to get through. Also, this page will not be available during exams, and while there is always leniency with written code, there must be some demonstration of understanding how to use our parallel constructs!

Java Basics

Sometimes the problem isn't with doing things in parallel, but just getting Java to cooperate with what you want it to do. Here's the syntax for some of Java's base constructs and classes that you'll use throughout the semester.

This page mainly just covers syntax though. For a better understanding of how some of these tools actually function and why we use them, check out some of the following pages:

Object Constructors

There will be a lot of classes for students to make/complete throughout the semester. Many times, you will be responsible for filling out the constructor. If given something like this:

private int value;

public Foo(int parameter){
	//TO DO
}

While all sorts of code and commands may be used in the constructor, before it is over, all of the class's private variables should have been assigned.

public Foo(int parameter){
	this.value = parameter;
}

Then, outside of this class, you can create an object of type Foo by doing the following:

Foo userMadeObject = new Foo(1);

Lists

Many times you will need to make some kind of list to keep information in. The two we recommend using are Linked Lists and Array Lists.

List<T> listA = new LinkedList<>();
List<T> listB = new ArrayList<>();

Maps

Many times you will need to make some kind of map to store information in. Depending on the race conditions of the assignment, you usually will use Hash Maps or Concurrent Hash Maps.

Map<K,V> map = new HashMap<>();

Compute

A very important application of a map is its compute method. This allows us to change a value in a map rather than having to remove it and add it back. Here is the code for the default implementation of the compute method, taken from the Map JavaDocs.

V oldValue = map.get(key);
V newValue = remappingFunction.apply(key, oldValue);
if (oldValue != null ) {
	if (newValue != null)
		map.put(key, newValue);
	else
		map.remove(key);
} else {
	if (newValue != null)
		map.put(key, newValue);
	else
		return null;
}

Most students tend to get tripped up on the use of a BiFunction. This BiFunction is essential giving the map a formula to use in order to get the new value it needs to set in the map. If we wanted to call compute on a key and increase its value by one (assuming the value is an integer), it would look like this:

map.compute(keyName, (key, value) -> {
	return value + 1;
}

Note that the BiFunction is simply given as a lambda with two parameters passed in. You can learn more about this in the lambda section of this page. Also important is that this lambda has a return value. Whatever is returned is what the new value in the map will become.

Other methods in Map that you may find helpful are computeIfAbsent, merge, and replace. Check out the Map JavaDocs for more detailed information on how those methods work.


Ternary Operators

A handy and quick way to do things based on a boolean operation. The two blocks of code are equivalent.

if(testCondition){
	int finalResult = valueIfTrue;
} else{
	int finalResult = valueIfFalse;
}
int finalResult = (testCondition) ? valueIfTrue : valueIfFalse

Runtime

In order to get the number of processors your computer has:

int numProcessors = Runtime.getRuntime().availableProcessors();

Lambda

Lambdas are very intricate things, and they are also incredibly important when it comes to understanding how our code works in this course. If you are looking for more information on what lambdas actually are, go check out the Lambdas wiki page, or the lambdas section in the Rundown on Everything Java page!

The syntax for a lambda can be seen as:

(T parameter) -> {
	doSomething();
}

Within the curly braces, you will have access to that parameter variable. Most times you use lambdas though, there are no parameters to pass through, so you can just do

() -> {
	doSomething();
}

It's strongly encouraged to check out the Eclipse tip on how to use content assist lambdas.

Runnable

Callable

V5/Habanero

Habanero is Rice's collection of parallel tools. When you watch the RiceX videos, it is the Habanero version of things you will be seeing. For this course, we've designed V5. It is similar to Habanero in most ways, but has small changes to hopefully make your lives easier when it comes to using the tools in this class. Listed below is the syntax for V5's version, but the differences between the two versions are noted to help clarify.

Async

Asyncs are our main tool in doing work in parallel. The execution of an async will spawn a new task, and any code written within the braces of an async will be done in that new task. The actual async method is called like this: async(Runnable body)

But as discussed above in the Runnable section, a Runnable can be defined just using a lambda. For the runnables in asyncs, there are no parameters passed in, so an async with the lambda is as follows:

async( () -> {
	doSomething();
});

It is also worth noting that with the V5 version of async, we can call it as async(Boolean isParallelDesired, Runnable body), where the boolean determines whether or not the body is run in a new thread or not. While you'll never need to use this for the assignments, it is a possibility.

Finish

Finish blocks will wait until everything within the brackets (no matter the number of tasks) executes and completes before moving on to any of the code after it. This is essential in preventing data races and will be used frequently in the course.

finish(Runnable body)

finish( () -> {
	async( () -> {
		doSomething();
	});
	asynch( () -> {
		doSomethingElse();
	});
});

Parallel Loops

Forall

A forall loop is a simplified way to do the following:

finish((), -> {
	for(int i = minInclusive; i < maxExclusive; i++){
		async( () -> {
			doSomething(i);
		});
	}
});

To do that same code with a forall loop, we simply do

forall(minInclusive, maxExclusive, (i) -> {
	doSomething(i);
});

Note how this time in the lambda, we are passing a parameter through in the parentheses. This 'i' passed through is the variable that is being incremented. You can also do a forall loop iterating through an iterable object, such as an array or list. That would look like this:

T[] array = new T[n];
forall(array, (item) -> {
	doSomething(item);
});

In this case, item is something of type T from the array. This loop will iterate through all the indicies in the array and spawn a new task for each one.

Forall Chunked

Sometimes, you don't want to make a task for each any everything in the iteratable object, as the overhead of spawning tasks outweighs the benefits from parallelism. Chunking allows the creation of the tasks to be manually decided (either by the user or the computer). To use chunking, just pass a ChunkedOption before the object/values to iterate over.

forall(chunked(), array, (item) -> {
	doSomething(item);
});

Forall 2d

Instead of doing two forall loops, we can achieve the same result using a single forall2d loop.

forall2d(1stMinInclusive, 1stMaxExclusive, 2ndMinInclusive, 2ndMaxExclusive, (i, j) -> {
	array[i][j] = doSomething();
});

Chunking can also be done on forall2d loops.

forall2d(chunked(), minA, maxExclusiveA, minB, maxExclusiveB, (i, j) -> {
	array[i][j] = doSomething();
});

Forasync

Functions similarly to a forall loop, but does not have the finish block surrounding the for loop. So in order to do this:

for(int i = minInclusive; i < maxExclusive; i++){
	async( () -> {
		doSomething(i);
	});
}

We use the forasync:

forasync(minInclusive, maxExclusive, (i) -> {
	doSomething(i);
});
T[] array = new T[n];
forasync(array, (item) -> {
	doSomething(item);
});

This style of loop does have a 2d option (forasync2d), and it is possible to use chunking options with it.

Forseq

Forseq functions just like a regular for loop (nothing is done in parallel), but it looks similar in structure to a forall loop. Use this when you want to easily switch back and forth between the parallel and sequential versions for testing purposes.

forseq(minInclusive, maxExclusive, (i) -> {
	doSomething(i);
});
T[] array = new T[n];
forseq(array, (item) -> {
	doSomething(item);
});

There also is a forseq2d loop that works just like its parallel counterpart. It is also possible to pass in a chunking option in a forseq, but it does not affect the performance of the code.

Future

A future is a parallel tool that has a return value. This means that once the work is completed (in another thread), we can call the future and get its returned value. The basic structure of a future is future(Callable<R> body). Note that this is similar to an async, but uses a Callable rather than a Runnable. In practice, making a future and storing it so the value can be gotten later looks like this:

Future<R> futureWithTypeR = future(() -> {
	R item = doSomething();
	return item;
});

Then, get the value later on by calling .get() on the future.

R result = futureWithTypeR.get();

Finish Accumulators

A finish accumulator allows you to do operations (such as counting) within a finish block and make them thread-safe. Then the results can be accessed outside of the finish block. First you have to make the finish accumulator, and then you have to register it with your finish.

FinishAccumulator<Integer> acc = newIntegerFinishAccumulator(NumberReductionOperator.SUM);
finish(register(acc), () -> {
	int value = doSomething();
	acc.put(value);
});
Integer result = acc.get();

Some important notes:

  • register() is a V5 method that was created for this course to make setting up accumulators a bit easier.
  • Some other NumberReductionOperators you can use are PRODUCT, MIN, and MAX.
  • Some other types of accumulators are newDoubleFinishAccumulator() and newReducerFinishAccumulator


Java X10

Others

Entries

In a lot of assignments, you will need to make entries of different types. Almost always, the KeyValuePair<K,V> or KeyMutableValuePair<K,V> will be your best options.

KeyValuePair<String, int> newEntry = new KeyValuePair<>("A", 1);