I'm learning about parallel programming in Java using fork join pool. I know how it works internally and decided to write a very simple program using it to calculate the max value for a large array. However, when I ran it I found that it would take so long that the program would hang and never complete. When I just calculate the max without using parallelism though it was much faster and finished in milliseconds! Also, when I use Runtime.getRuntime().availableProcessors() my computer returns 8, so it's not like I'm using a 1 core computer that can't handle it. Can somebody tell me if they are getting similar results on their own computer? If so, can you explain what is wrong with my code, i'm very confused. Thanks.
import java.util.concurrent.*;
public class ParralleMax {
private static int numberOfCores = Runtime.getRuntime().availableProcessors();
public static void main(String[] args) {
int[] list = new int[9_000_000];
for (int i = 0; i < list.length; i++)
list[i] = i;
System.out.println("Array created. Program now starting...");
long startTime = System.currentTimeMillis();
int max = calculateMax(list);
long endTime = System.currentTimeMillis();
System.out.println("This computer has " + numberOfCores + " cores.");
System.out.println("The max value " + max + " was calculated in " + (endTime - startTime) + "
miliseconds.");
}
public static int calculateMax(int[] list) {
RecursiveTask<Integer> maxTask = new MaxTask(list, 0, list.length);
ForkJoinPool pool = new ForkJoinPool();
return pool.invoke(maxTask);
}
private static class MaxTask extends RecursiveTask<Integer> {
private final static int THRESHOLD = 1000;
private int[] list;
private int low;
private int high;
public MaxTask(int[] list, int low, int high) {
this.list = list;
this.low = low;
this.high = high;
}
#Override
public Integer compute() {
if (high - low < THRESHOLD) {
int max = list[0];
for (int i = low; i < high; i++)
if (list[i] > max)
max = list[i];
return new Integer(max);
}
else {
int mid = (high + low) / 2;
RecursiveTask left = new MaxTask(list, 0, mid);
RecursiveTask right = new MaxTask(list, mid, high);
right.fork();
left.fork();
Integer leftMax = (Integer)left.join();
Integer rightMax = (Integer)right.join();
return new Integer(Math.max(leftMax.intValue(), rightMax.intValue()));
}
}
}
}
Related
I am trying to transform each element of an array of length 10,00,00,000. My first approach is using a single thread in a simple main method.
My next approach is using fork-join framework of java by dividing the array into chunks of 10,00,000. But the total time taken to transform the array is almost same in both the approaches.
public class SerialComputation {
public static void main(String[] args) {
Integer[] array = new Integer[100000000];
for (int i = 0; i < array.length; i++) {
array[i] = new Random().nextInt(100);
}
System.out.println("First 10 elements before transformation:");
Arrays.asList(array).stream().limit(10).forEach(d -> System.out.print(d + " "));
System.out.println();
long startTime = System.currentTimeMillis();
for (int i = 0; i < array.length; i++) {
array[i] *= 2;
}
long endTime = System.currentTimeMillis();
System.out.println("First 10 elements after transformation:");
Arrays.asList(array).stream().limit(10).forEach(d -> System.out.print(d + " "));
System.out.println();
System.out.println("Total time taken: " + (endTime - startTime));
}
}
class ParallelComputation {
public static void main(String[] args) {
Integer[] array = new Integer[100000000];
for (int i = 0; i < array.length; i++) {
array[i] = new Random().nextInt(100);
}
System.out.println("First 10 elements before transformation:");
Arrays.asList(array).stream().limit(10).forEach(d -> System.out.print(d + " "));
System.out.println();
ForkJoinTask<?> forkJoinTask = new TransformTask(0, array.length, array);
ForkJoinPool pool = new ForkJoinPool();
long startTime = System.currentTimeMillis();
pool.invoke(forkJoinTask);
long endTime = System.currentTimeMillis();
System.out.println("First 10 elements after transformation:");
Arrays.asList(array).stream().limit(10).forEach(d -> System.out.print(d + " "));
System.out.println("Total time taken: " + (endTime - startTime));
}
}
class TransformTask extends RecursiveAction {
private static final long serialVersionUID = 1L;
private int start;
private int end;
private Integer[] array;
public TransformTask(int start, int end, Integer[] array) {
this.start = start;
this.end = end;
this.array = array;
}
#Override
protected void compute() {
if (end - start <= 1000000) {
for (int i = start; i < end; i++) {
array[i] *= 2;
}
} else {
int middle = start + ((end - start) / 2);
System.out.println("start:" + start + "middle:" + middle + "end:" + end);
invokeAll(new TransformTask(start, middle, array), new TransformTask(middle, end, array));
}
}
}
I am expecting the ParallelComputation to calculate the result much quicker than the SerialComputation. But both are doing the job in almost same time.
I am using a machine with Intel core i7 processor with windows 10.
I can't comment on TransformTask implementation, but this :
static long parallelStreamComputation() {
Integer[] array = new Integer[100000000];
for (int i = 0; i < array.length; i++) {
array[i] = new Random().nextInt(100);
}
long startTime = System.currentTimeMillis();
Arrays.stream(array).parallel().mapToInt( i -> i*2).toArray();
long endTime = System.currentTimeMillis();
return endTime-startTime;
}
Was measured to be about 10 times faster.
I'm stuck on the final part of my program which involves comparing two separate sorting algorithms.
I am unable to call my other two classes within my main and I'm not entirely sure why.
import java.util.Random;
import java.util.Arrays;
import java.util.Scanner;
// Implement two separate sorting classes
// Compare both classes and see how they stack up
// based off their results.
//QuickSort + BubbleSort
public class a8main{
public static void main(String[] args)
{
Scanner userInput = new Scanner(System.in);
System.out.println("Type in array size.");
System.out.println("5,000 10,000 30,000");
int input = userInput.nextInt();
//Test smaller arrays starting at 5,000
//Test larger arrays. (30,000 or more)
byte[] c = new byte[input];
new Random().nextBytes(c);
//Begin QuickSorting + BubbleSorting
for (int j=1;j<6;j++)
{
byte[] c1 = Arrays.copyOfRange(c, 0, 5000*j);
long startTime = System.currentTimeMillis();
quickSort(c1,0,5000*j-1);
long endTime = System.currentTimeMillis();
long totalTime = endTime - startTime;
byte[] c2 = Arrays.copyOfRange(c, 0, 5000*j);
startTime = System.currentTimeMillis();
bubbleSort(c2);
long endTime = System.currentTimeMillis();
long totalTime = endTime - startTime;
System.out.println("Amount of time taken for first array "+5000*j+" elements for quickSort: "+totalTime+" miliseconds.");
System.out.println("Amount of time taken for second array "+5000*j+" elements for bubbleSort: "+totalTime+" miliseconds.");
}
}
}
I've got the main code here and I'm going to include my other two classes just to give you a clue on what I'm dealing with here.
public class bubbleSort
{
static void bubbleSorter(byte[] args)
{
int n = args.length;
byte temp = 0;
for(int i=0; i < n;i++)
{
for(int j=1; j < (n-i); j++)
{
if(args[j-1] > args[j])
{
temp = args[j-1];
args[j-1] = args[j];
args[j] = temp;
}
}
}
}
}
Bubble Sort
public class quickSort
{
static void quickSorter(byte[]args,int low,int high)
{
if (args == null || args.length == 0)
return;
if (low >= high)
return;
int center = low + (high - low) / 2;
byte pivot = args[center];
int i = low, j = high;
while (i <= j)
{
while (args[i] < pivot)
{
i++;
}
while (args[j] > pivot)
{
j--;
}
}
if (low < j)
quickSorter(args, low, j);
if (high > i)
quickSorter(args, i, high);
}
}
And QuickSort
What am I doing wrong?
I just need some clues.
You need to call methods by the class name as you defined them as static methods like this
quickSort.quickSorter(parameter1,parameter2,parameter3);
bubbleSort.bubbleSorter(parameter);
You have a few issues.
First, Java recommendations are to use PascalCase for class names; for example, BubbleSort. Method names are camel case like bubbleSorter. It's way easier to read this way.
Second, you were re-declaring endTime and totalTime in your Main main() method. Take out the long before them after your bubbleSorter() method call.
Third, you should force your static classes to have a private constructor. Some people throw an exception if anyone tries to initialize the private constructor. This can only really be done within the class, so I opt to just write a comment since people could just remove the exception anyway.
Fourth, when calling static methods of a class you need to include the class of the static method. This will be way easier to read when you do the first point in this answer.
Here's what your classes could look like.
Bubble Sort Class:
public class BubbleSort
{
private BubbleSort() {
//This is a static class - no instantiation needed
}
public static void bubbleSorter(byte[] args)
{
int n = args.length;
byte temp = 0;
for (int i = 0; i < n; i++)
{
for (int j = 1; j < (n - i); j++)
{
if (args[j - 1] > args[j])
{
temp = args[j - 1];
args[j - 1] = args[j];
args[j] = temp;
}
}
}
}
}
Quick Sort Class:
public class QuickSort
{
private QuickSort() {
//This is a static class - no instantiation needed
}
public static void quickSorter(byte[]args,int low,int high)
{
if (args == null || args.length == 0)
return;
if (low >= high)
return;
int center = low + (high - low) / 2;
byte pivot = args[center];
int i = low, j = high;
while (i <= j)
{
while (args[i] < pivot)
{
i++;
}
while (args[j] > pivot)
{
j--;
}
}
if (low < j)
quickSorter(args, low, j);
if (high > i)
quickSorter(args, i, high);
}
}
Main Class:
public class Main {
public static void main(String[] args) {
Scanner userInput = new Scanner(System.in);
System.out.println("Type in array size.");
System.out.println("5,000 10,000 30,000");
int input = userInput.nextInt();
//Test smaller arrays starting at 5,000
//Test larger arrays. (30,000 or more)
byte[] c = new byte[input];
new Random().nextBytes(c);
//Begin QuickSorting + BubbleSorting
for (int j=1;j<6;j++)
{
byte[] c1 = Arrays.copyOfRange(c, 0, 5000*j);
long startTime = System.currentTimeMillis();
QuickSort.quickSorter(c1,0,5000*j-1);
long endTime = System.currentTimeMillis();
long totalTime = endTime - startTime;
byte[] c2 = Arrays.copyOfRange(c, 0, 5000*j);
startTime = System.currentTimeMillis();
BubbleSort.bubbleSorter(c2);
endTime = System.currentTimeMillis();
totalTime = endTime - startTime;
System.out.println("Amount of time taken for first array "+5000*j+" elements for quickSort: "+totalTime+" miliseconds.");
System.out.println("Amount of time taken for second array "+5000*j+" elements for bubbleSort: "+totalTime+" miliseconds.");
}
}
}
I'm working to improve my java skills but a little unsure on how to handle this multi-threaded application. Basically, the program reads a text file and finds the largest number. I added a for loop within my search algorithm to create 10 threads but I'm not sure if it's actually creating 10 threads. The idea is to improve the execution time, or at least that's what I assume should happen. Is there anyway to check if I did it correctly and if the execution time is indeed improved?
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class ProcessDataFile {
public static void main(String[] args) throws IOException {
int max = Integer.MIN_VALUE;
int i = 0;
int[] numbers = new int[100000];
String datafile = "dataset529.txt"; //string which contains datafile
String line; //current line of text file
try (BufferedReader br = new BufferedReader(new FileReader(datafile))) { //reads in the datafile
while ((line = br.readLine()) != null) { //reads through each line
numbers[i++] = Integer.parseInt(line); //pulls out the number of each line and puts it in numbers[]
}
}
for (i = 0; i < 10000; i++){ //loop to go through each number in the file and compare it to find the largest int.
for(int j = 0; j < 10; j++) { //creates 10 threads
new Thread();
}
if (max < numbers[i]) //As max gets bigger it checks the array and keeps increasing it as it finds a larger int.
max = numbers[i]; //Sets max equal to the final highest value found.
}
System.out.println("The largest number in DataSet529 is: " + max);
}
}
This is a VERY basic example which demonstrates the basic concepts of creating and running threads which process a given range of values from a specific array. The example makes a few assumptions (only a even number of elements for example). The example is also slightly long winded and is done so deliberately, in an attempt to demonstrate the basic steps which would be needed
Start by taking a look at the Concurrency Trail for more details
import java.util.Random;
public class ThreadExample {
public static void main(String[] args) {
int[] numbers = new int[100000];
Random rnd = new Random();
for (int index = 0; index < numbers.length; index++) {
numbers[index] = rnd.nextInt();
}
Thread[] threads = new Thread[10];
Worker[] workers = new Worker[10];
int range = numbers.length / 10;
for (int index = 0; index < 10; index++) {
int startAt = index * range;
int endAt = startAt + range;
workers[index] = new Worker(startAt, endAt, numbers);
}
for (int index = 0; index < 10; index++) {
threads[index] = new Thread(workers[index]);
threads[index].start();
}
boolean isProcessing = false;
do {
isProcessing = false;
for (Thread t : threads) {
if (t.isAlive()) {
isProcessing = true;
break;
}
}
} while (isProcessing);
for (Worker worker : workers) {
System.out.println("Max = " + worker.getMax());
}
}
public static class Worker implements Runnable {
private int startAt;
private int endAt;
private int numbers[];
private int max = Integer.MIN_VALUE;
public Worker(int startAt, int endAt, int[] numbers) {
this.startAt = startAt;
this.endAt = endAt;
this.numbers = numbers;
}
#Override
public void run() {
for (int index = startAt; index < endAt; index++) {
max = Math.max(numbers[index], max);
}
}
public int getMax() {
return max;
}
}
}
A slightly simpler solution would involve the ExecutorService API, which would allow you to offer a series of Callables to the service which would then return a List of Future's. The benefit here is, the service won't return till all the Callables have completed (or have failed), so you don't need constantly check the states of the threads
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ThreadExample {
public static void main(String[] args) {
int[] numbers = new int[100000];
Random rnd = new Random();
for (int index = 0; index < numbers.length; index++) {
numbers[index] = rnd.nextInt();
}
ExecutorService executor = Executors.newFixedThreadPool(10);
Worker[] workers = new Worker[10];
int range = numbers.length / 10;
for (int index = 0; index < 10; index++) {
int startAt = index * range;
int endAt = startAt + range;
workers[index] = new Worker(startAt, endAt, numbers);
}
try {
List<Future<Integer>> results = executor.invokeAll(Arrays.asList(workers));
for (Future<Integer> future : results) {
System.out.println(future.get());
}
} catch (InterruptedException | ExecutionException ex) {
ex.printStackTrace();
}
}
public static class Worker implements Callable<Integer> {
private int startAt;
private int endAt;
private int numbers[];
public Worker(int startAt, int endAt, int[] numbers) {
this.startAt = startAt;
this.endAt = endAt;
this.numbers = numbers;
}
#Override
public Integer call() throws Exception {
int max = Integer.MIN_VALUE;
for (int index = startAt; index < endAt; index++) {
max = Math.max(numbers[index], max);
}
return max;
}
}
}
I know this is a bit late answer but you can also use lambda expressions while using ExecutorService instead of creating new class that implements Runnable.
Here is a complete example below, you can play around THREAD_SIZE and RANDOM_ARRAY_SIZE variables.
import org.apache.log4j.Logger;
import java.security.SecureRandom;
import java.util.*;
import java.util.concurrent.*;
public class ConcurrentMaximumTest {
static final int THREAD_SIZE = 10;
static final int RANDOM_ARRAY_SIZE = 8999;
static final SecureRandom RAND = new SecureRandom();
private static Logger logger = Logger.getLogger(ConcurrentMaximumTest.class);
public static void main(String[] args) throws InterruptedException, ExecutionException {
int[] array = generateRandomIntArray(RANDOM_ARRAY_SIZE);
Map<Integer, Integer> positionMap = calculatePositions(array.length, THREAD_SIZE);
ExecutorService threads = Executors.newFixedThreadPool(THREAD_SIZE);
List<Callable<Integer>> toRun = new ArrayList<>(THREAD_SIZE);
for (Map.Entry<Integer, Integer> entry : positionMap.entrySet())
toRun.add(() -> findMax(array, entry.getKey(), entry.getValue()));
int result = Integer.MIN_VALUE;
List<Future<Integer>> futures = threads.invokeAll(toRun);
for (Future<Integer> future : futures) {
Integer localMax = future.get();
if(localMax > result)
result = localMax;
}
threads.shutdownNow();
logger.info("Max value calculated with " + THREAD_SIZE + " threads:" + result);
Arrays.sort(array);
int resultCrosscheck = array[array.length - 1];
logger.info("Max value calculated with sorting: " + resultCrosscheck);
assert result != resultCrosscheck : "Crosscheck failed";
}
/* Calculates start and end positions of each chunk(for simplicity). It can also be calculated on the fly.*/
private static Map<Integer, Integer> calculatePositions(int size, int numThreads){
int lengthOfChunk = size / numThreads;
int remainder = size % numThreads;
int start = 0;
Map<Integer,Integer> result = new LinkedHashMap<>();
for(int i = 0; i < numThreads -1; i++){
result.put(start, lengthOfChunk);
start += lengthOfChunk;
}
result.put(start, lengthOfChunk+remainder);
return result;
}
/*Find maximum value of given part of an array, from start position and chunk size.*/
private static int findMax(int[] wholeArray, int position, int size){
int end = (position + size);
int max = Integer.MIN_VALUE;
logger.info("Starting read for interval [" + position + "," + end + ")");
for(int i = position; i < (position + size); i++)
if(wholeArray[i] > max)
max = wholeArray[i];
logger.info("Finishing finding maximum for interval [" + position + "," + end + ")" + ". Calculated local maximum is " + max);
return max;
}
/* Helper function for generating random int array */
private static int[] generateRandomIntArray(int size){
int[] result = new int[size];
for (int i = 0; i < size; i++)
result[i] = RAND.nextInt(Integer.MAX_VALUE);
return result;
}
}
I have a simple problem - I need to order 10 numbers. I had an idea how to do this recursively: Make an array of the 10 numbers, take the maximum of the ten numbers, take it out of the array, and repeat the same function with the nine numbers left. The problem was that I did not know how to implement that. I wrote the program, and it works, only it has a part that repeats all the time but with new arrays, because you cannot change the size of the array.
/* package whatever; // don't place package name! */
import java.util.*;
import java.lang.*;
import java.io.*;
/* Name of the class has to be "Main" only if the class is public. */
class Ideone {
public static void main (String[] args) throws java.lang.Exception {
int[] sortedArray = new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
Scanner input = new Scanner(System.in);
int in0 = input.nextInt();
int in1 = input.nextInt();
int in2 = input.nextInt();
int in3 = input.nextInt();
int in4 = input.nextInt();
int in5 = input.nextInt();
int in6 = input.nextInt();
int in7 = input.nextInt();
int in8 = input.nextInt();
int in9 = input.nextInt();
int[] numArray = new int[]{in0, in1, in2, in3, in4, in5, in6, in7, in8, in9};
int numArrayLength = numArray.length;
recursiveSort(numArray);
for (int i=0;i<numArrayLength;i++) {
System.out.print(numArray[i]+",");
}
sortedArray[0] = numArray[0];
System.out.println(" ");
int[] numArray2 = Arrays.copyOfRange(numArray, 1, numArrayLength);
int numArray2Length = numArray2.length;
recursiveSort(numArray2);
for (int j=0;j<numArray2Length;j++) {
System.out.print(numArray2[j]+",");
}
sortedArray[1] = numArray2[0];
System.out.println(" ");
int[] numArray3 = Arrays.copyOfRange(numArray2, 1, numArray2Length);
int numArray3Length = numArray3.length;
recursiveSort(numArray3);
for (int k=0;k<numArray3Length;k++) {
System.out.print(numArray3[k]+",");
}
sortedArray[2] = numArray3[0];
System.out.println(" ");
int[] numArray4 = Arrays.copyOfRange(numArray3, 1, numArray3Length);
int numArray4Length = numArray4.length;
recursiveSort(numArray4);
for (int k=0;k<numArray4Length;k++) {
System.out.print(numArray4[k]+",");
}
sortedArray[3] = numArray4[0];
System.out.println(" ");
int[] numArray5 = Arrays.copyOfRange(numArray4, 1, numArray4Length);
int numArray5Length = numArray5.length;
recursiveSort(numArray5);
for (int k=0;k<numArray5Length;k++) {
System.out.print(numArray5[k]+",");
}
sortedArray[4] = numArray5[0];
System.out.println(" ");
int[] numArray6 = Arrays.copyOfRange(numArray5, 1, numArray5Length);
int numArray6Length = numArray6.length;
recursiveSort(numArray6);
for (int k=0;k<numArray6Length;k++) {
System.out.print(numArray6[k]+",");
}
sortedArray[5] = numArray6[0];
System.out.println(" ");
int[] numArray7 = Arrays.copyOfRange(numArray6, 1, numArray6Length);
int numArray7Length = numArray7.length;
recursiveSort(numArray7);
for (int k=0;k<numArray7Length;k++) {
System.out.print(numArray7[k]+",");
}
sortedArray[6] = numArray7[0];
System.out.println(" ");
int[] numArray8 = Arrays.copyOfRange(numArray7, 1, numArray7Length);
int numArray8Length = numArray8.length;
recursiveSort(numArray8);
for (int k=0;k<numArray8Length;k++) {
System.out.print(numArray8[k]+",");
}
sortedArray[7] = numArray8[0];
System.out.println(" ");
int[] numArray9 = Arrays.copyOfRange(numArray8, 1, numArray8Length);
int numArray9Length = numArray9.length;
recursiveSort(numArray9);
for (int k=0;k<numArray9Length;k++) {
System.out.print(numArray9[k]+",");
}
sortedArray[8] = numArray9[0];
System.out.println(" ");
int[] numArray10 = Arrays.copyOfRange(numArray9, 1, numArray9Length);
int numArray10Length = numArray10.length;
recursiveSort(numArray10);
for (int k=0;k<numArray10Length;k++) {
System.out.print(numArray10[k]+",");
}
sortedArray[9] = numArray10[0];
System.out.println(" ");
sortedArray[2] = numArray3[0];
for (int dasdasd=0;dasdasd<sortedArray.length;dasdasd++) {
System.out.print(sortedArray[dasdasd]+",");
}
}
private static int[] recursiveSort(int numArray[]) {
int numArrayLength = numArray.length;
int maximum = 0;
for (int i=0;i<numArrayLength;i++) {
if (numArray[i] > maximum) {
maximum = numArray[i];
}
}
int indexOfMaximum = -1;
for (int j=0;j<numArrayLength;j++) {
if (numArray[j] == maximum) {
indexOfMaximum = j;
break;
}
}
int temporary = numArray[0];
numArray[0] = numArray[indexOfMaximum];
numArray[indexOfMaximum] = temporary;
return numArray;
}
}
As you can see, the
int[] numArray(n) = Arrays.copyOfRange(numArray(n-1), 1, numArray(n-1)Length);
int numArray(n)Length = numArray(n).length;
recursiveSort(numArray(n));
for (int k=0;k<numArray(n)Length;k++) {
System.out.print(numArray(n)[k]+",");
}
sortedArray[(n-1)] = numArray(n)[0];
System.out.println(" ");
constantly repeats, so there is probably a recursive solution that will work nicely. Maybe I can do something using ArrayLists because their size can change...
Any help will be appreciated!
Thank you!
I suggest a recursive routine that uses an explicit start index for the part that remains to be sorted:
private static void recursiveSort(int[] array, int start) {
if (start < array.length - 1) {
int maximum = array[start];
int maximumIndex = start;
for (int i = start + 1; i < array.length; ++i) {
if (array[i] > maximum) {
maximum = array[i];
maximumIndex = i;
}
}
if (maximumIndex != start) {
int tmp = array[start];
array[start] = array[maximumIndex];
array[maximumIndex] = tmp;
}
recursiveSort(array, start + 1);
}
}
This actually does recursion (unlike your code, which iterates calling a routine named "recursiveSort" but isn't recursive at all). The whole process would be started by calling:
recursiveSort(numArray, 0);
When it returns, the array will be sorted in descending order.
As a general heuristic, when you are struggling with how to make a method recursive, you should consider adding arguments to the method to help with the bookkeeping.
Is this homework or you just need to have the numbers ordered? Java has an easy way to do this if you use ArrayList() instead of array[]. You would just need to call Collections.sort(yourArrayList);
I suggest not trying to make your own sorting algorithm. Many smart people have already done that hard work for you.
The "recursive" sort that you were trying to implement (aka bubble sort which Ted has shown you how to truly make recursive) will work, but it is grossly inefficient. See a comparison of sorting algorithms here.
Below is a demo of the algorithm you were trying to implement compared to a shell sort, one of the fastest sorting algorithms available. The implementation I used was taken from here. Run it and you will see that shell sort is on average 7 to 8 times faster than bubble sort.
public class SortingDemo {
// Methods required for Shell sort
public static void shellSort(Comparable[] a) {
int N = a.length;
int h = 1;
while (h < N/3) h = 3*h + 1;
while (h >= 1) {
for (int i = h; i < N; i++) {
for (int j = i; j >= h && less(a[j], a[j-h]); j -= h) {
exch(a, j, j-h);
}
}
assert isHsorted(a, h);
h /= 3;
}
assert isSorted(a);
}
private static boolean less(Comparable v, Comparable w) {
return (v.compareTo(w) < 0);
}
private static void exch(Object[] a, int i, int j) {
Object swap = a[i];
a[i] = a[j];
a[j] = swap;
}
private static boolean isSorted(Comparable[] a) {
for (int i = 1; i < a.length; i++)
if (less(a[i], a[i-1])) return false;
return true;
}
private static boolean isHsorted(Comparable[] a, int h) {
for (int i = h; i < a.length; i++)
if (less(a[i], a[i-h])) return false;
return true;
}
// Method required for "recursive" sort
private static void recursiveSort(Integer[] array, int start) {
if (start < array.length - 1) {
int maximum = array[start];
int maximumIndex = start;
for (int i = start + 1; i < array.length; ++i) {
if (array[i] > maximum) {
maximum = array[i];
maximumIndex = i;
}
}
if (maximumIndex != start) {
int tmp = array[start];
array[start] = array[maximumIndex];
array[maximumIndex] = tmp;
}
recursiveSort(array, start + 1);
}
}
public static void main(String[] args) {
int desiredArraySize = 1000;
int minSizeOfNumberInArray = 0;
int maxSizeOfNumberInArray = 100;
Integer[] array = new Integer[desiredArraySize]; // Used Integer instead of int to utilize Comparable interface
for(int i = 0; i < array.length; i++) {
int randomInt = (int) Math.random() * (maxSizeOfNumberInArray - minSizeOfNumberInArray);
array[i] = randomInt;
}
long startTime = System.nanoTime();
recursiveSort(array, 0);
long endTime = System.nanoTime();
long recursiveSortTime = endTime - startTime;
System.out.println(String.format("\"Recursive\" sort completed in %d ns", recursiveSortTime));
startTime = System.nanoTime();
shellSort(array);
endTime = System.nanoTime();
long shellSortTime = endTime - startTime;
System.out.println(String.format("Shell sort completed in %d ns", shellSortTime));
System.out.println(String.format("\"Recursive\" sort took %f times longer", (float)recursiveSortTime / (float)shellSortTime));
}
}
When learning programming, both writing your own sorting algorithms and your own recursive algorithms are great exercises for solidifying your understanding of how things work. It's time well invested, even if someone's already done it better.
You noticed a pattern that repeats, and associated that with recursion. When evaluating whether recursion is a good fit, I would encourage you to tweak that thought process with the notion of "divide-and-conquer". If you're solving only one element with each recursion, then your stack will grow very deep, which should be avoided. If you can split your problem into roughly even chunks and process each chunk recursively, then recursion will be a good fit. Otherwise, a loop is already an excellent fit for repeating patterns.
This is a parallel implementation of Levenshtein distance that I was writing for fun. I'm disappointed in the results. I am running this on a core i7 processor, so I have plenty of available threads. However, as I increase the thread count, the performance degrades significantly. By that I mean it actually runs slower with more threads for input of the same size.
I was hoping that someone could look at the way I am using threads, and the java.util.concurrent package, and tell me if I am doing anything wrong. I'm really only interested in reasons why the parallelism is not working as I would expect. I don't expect the reader to look at the complicated indexing going on here. I believe the calculations I'm doing are correct. But even if they are not, I think I should still be seeing a close to linear speed-up as I increase the number of threads in the threadpool.
I've included the benchmarking code I used. I'm using libraries found here for benchmarking. The second code block is what I used for benchmarking.
Thanks for any help :).
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class EditDistance {
private static final int MIN_CHUNK_SIZE = 5;
private final ExecutorService threadPool;
private final int threadCount;
private final String maxStr;
private final String minStr;
private final int maxLen;
private final int minLen;
public EditDistance(String s1, String s2, ExecutorService threadPool,
int threadCount) {
this.threadCount = threadCount;
this.threadPool = threadPool;
if (s1.length() < s2.length()) {
minStr = s1;
maxStr = s2;
} else {
minStr = s2;
maxStr = s1;
}
maxLen = maxStr.length();
minLen = minStr.length();
}
public int editDist() {
int iterations = maxLen + minLen - 1;
int[] prev = new int[0];
int[] current = null;
for (int i = 0; i < iterations; i++) {
int currentLen;
if (i < minLen) {
currentLen = i + 1;
} else if (i < maxLen) {
currentLen = minLen;
} else {
currentLen = iterations - i;
}
current = new int[currentLen * 2 - 1];
parallelize(prev, current, currentLen, i);
prev = current;
}
return current[0];
}
private void parallelize(int[] prev, int[] current, int currentLen,
int iteration) {
int chunkSize = Math.max(current.length / threadCount, MIN_CHUNK_SIZE);
List<Future<?>> futures = new ArrayList<Future<?>>(currentLen);
for (int i = 0; i < currentLen; i += chunkSize) {
int stopIdx = Math.min(currentLen, i + chunkSize);
Runnable worker = new Worker(prev, current, currentLen, iteration,
i, stopIdx);
futures.add(threadPool.submit(worker));
}
for (Future<?> future : futures) {
try {
Object result = future.get();
if (result != null) {
throw new RuntimeException(result.toString());
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
// We can only finish the computation if we complete
// all subproblems
throw new RuntimeException(e);
}
}
}
private void doChunk(int[] prev, int[] current, int currentLen,
int iteration, int startIdx, int stopIdx) {
int mergeStartIdx = (iteration < minLen) ? 0 : 2;
for (int i = startIdx; i < stopIdx; i++) {
// Edit distance
int x;
int y;
int leftIdx;
int downIdx;
int diagonalIdx;
if (iteration < minLen) {
x = i;
y = currentLen - i - 1;
leftIdx = i * 2 - 2;
downIdx = i * 2;
diagonalIdx = i * 2 - 1;
} else {
x = i + iteration - minLen + 1;
y = minLen - i - 1;
leftIdx = i * 2;
downIdx = i * 2 + 2;
diagonalIdx = i * 2 + 1;
}
int left = 1 + ((leftIdx < 0) ? iteration + 1 : prev[leftIdx]);
int down = 1 + ((downIdx < prev.length) ? prev[downIdx]
: iteration + 1);
int diagonal = penalty(x, y)
+ ((diagonalIdx < 0 || diagonalIdx >= prev.length) ? iteration
: prev[diagonalIdx]);
int dist = Math.min(left, Math.min(down, diagonal));
current[i * 2] = dist;
// Merge prev
int mergeIdx = i * 2 + 1;
if (mergeIdx < current.length) {
current[mergeIdx] = prev[mergeStartIdx + i * 2];
}
}
}
private int penalty(int maxIdx, int minIdx) {
return (maxStr.charAt(maxIdx) == minStr.charAt(minIdx)) ? 0 : 1;
}
private class Worker implements Runnable {
private final int[] prev;
private final int[] current;
private final int currentLen;
private final int iteration;
private final int startIdx;
private final int stopIdx;
Worker(int[] prev, int[] current, int currentLen, int iteration,
int startIdx, int stopIdx) {
this.prev = prev;
this.current = current;
this.currentLen = currentLen;
this.iteration = iteration;
this.startIdx = startIdx;
this.stopIdx = stopIdx;
}
#Override
public void run() {
doChunk(prev, current, currentLen, iteration, startIdx, stopIdx);
}
}
public static void main(String args[]) {
int threadCount = 4;
ExecutorService threadPool = Executors.newFixedThreadPool(threadCount);
EditDistance ed = new EditDistance("Saturday", "Sunday", threadPool,
threadCount);
System.out.println(ed.editDist());
threadPool.shutdown();
}
}
There is a private inner class Worker inside EditDistance. Each worker is responsible for filling in a range of the current array using EditDistance.doChunk. EditDistance.parallelize is responsible for creating those workers, and waiting for them to finish their tasks.
And the code I am using for benchmarks:
import java.io.PrintStream;
import java.util.concurrent.*;
import org.apache.commons.lang3.RandomStringUtils;
import bb.util.Benchmark;
public class EditDistanceBenchmark {
public static void main(String[] args) {
if (args.length != 2) {
System.out.println("Usage: <string length> <thread count>");
System.exit(1);
}
PrintStream oldOut = System.out;
System.setOut(System.err);
int strLen = Integer.parseInt(args[0]);
int threadCount = Integer.parseInt(args[1]);
String s1 = RandomStringUtils.randomAlphabetic(strLen);
String s2 = RandomStringUtils.randomAlphabetic(strLen);
ExecutorService threadPool = Executors.newFixedThreadPool(threadCount);
Benchmark b = new Benchmark(new Benchmarker(s1, s2, threadPool,threadCount));
System.setOut(oldOut);
System.out.println("threadCount: " + threadCount +
" string length: "+ strLen + "\n\n" + b);
System.out.println("s1: " + s1 + "\ns2: " + s2);
threadPool.shutdown();
}
private static class Benchmarker implements Runnable {
private final String s1, s2;
private final int threadCount;
private final ExecutorService threadPool;
private Benchmarker(String s1, String s2, ExecutorService threadPool, int threadCount) {
this.s1 = s1;
this.s2 = s2;
this.threadPool = threadPool;
this.threadCount = threadCount;
}
#Override
public void run() {
EditDistance d = new EditDistance(s1, s2, threadPool, threadCount);
d.editDist();
}
}
}
It's very easy to accidentally write code that does not parallelize very well. A main culprit is when your threads compete for underlying system resources (e.g. a cache line). Since this algorithm inherently acts on things that are close to each other in physical memory, I suspect pretty strongly that may be the culprit.
I suggest you review this excellent article on False Sharing
http://www.drdobbs.com/go-parallel/article/217500206?pgno=3
and then carefully review your code for cases where threads would block one another.
Additionally, running more threads than you have CPU cores will slow down performance if your threads are CPU bound (if you're already using all cores to near 100%, adding more threads will only add overhead for the context switches).