Chapter 27 Cluster Work Queues

Similar documents
Chapter 26 Cluster Heuristic Search

Chapter 17 Parallel Work Queues

Chapter 19 Hybrid Parallel

Chapter 21 Cluster Parallel Loops

Chapter 16 Heuristic Search

Chapter 24 File Output on a Cluster

Chapter 6 Parallel Loops

Chapter 20 Tuple Space

Chapter 11 Overlapping

Chapter 31 Multi-GPU Programming

Chapter 13 Strong Scaling

Chapter 9 Reduction Variables

Chapter 25 Interacting Tasks

Chapter 36 Cluster Map-Reduce

BIG CPU, BIG DATA. Solving the World s Toughest Computational Problems with Parallel Computing. Second Edition. Alan Kaminsky

BIG CPU, BIG DATA. Solving the World s Toughest Computational Problems with Parallel Computing Second Edition. Alan Kaminsky

Chapter 3 Parallel Software

BIG CPU, BIG DATA. Solving the World s Toughest Computational Problems with Parallel Computing. Alan Kaminsky

Parallel Programming Languages COMP360

Priority Queues. 1 Introduction. 2 Naïve Implementations. CSci 335 Software Design and Analysis III Chapter 6 Priority Queues. Prof.

Total Score /15 /20 /30 /10 /5 /20 Grader

Appendix A Clash of the Titans: C vs. Java

infix expressions (review)

Maximum Clique Problem

CS 231 Data Structures and Algorithms, Fall 2016

Chapter 38 Map-Reduce Meets GIS

Note: Each loop has 5 iterations in the ThreeLoopTest program.

Draw the resulting binary search tree. Be sure to show intermediate steps for partial credit (in case your final tree is incorrect).

CSCI Lab 9 Implementing and Using a Binary Search Tree (BST)

Module 6: Binary Trees

Introduction to Programming Using Java (98-388)

Scalable GPU Graph Traversal!

Java Threads. COMP 585 Noteset #2 1

G Programming Languages Spring 2010 Lecture 13. Robert Grimm, New York University

Map-Reduce in Various Programming Languages

12 Abstract Data Types

PRINCIPLES OF SOFTWARE BIM209DESIGN AND DEVELOPMENT 10. PUTTING IT ALL TOGETHER. Are we there yet?

CHAPTER 5 GENERATING TEST SCENARIOS AND TEST CASES FROM AN EVENT-FLOW MODEL

CMPS 240 Data Structures and Algorithms Test #2 Fall 2017 November 15

CSE 332 Winter 2018 Final Exam (closed book, closed notes, no calculators)

CS140 Final Project. Nathan Crandall, Dane Pitkin, Introduction:

Concurrency & Parallelism. Threads, Concurrency, and Parallelism. Multicore Processors 11/7/17

Data Structure Advanced

What's wrong with Semaphores?

Threads, Concurrency, and Parallelism

SELF-BALANCING SEARCH TREES. Chapter 11

Recursion. What is Recursion? Simple Example. Repeatedly Reduce the Problem Into Smaller Problems to Solve the Big Problem

On the cost of managing data flow dependencies

CSE373 Winter 2014, Final Examination March 18, 2014 Please do not turn the page until the bell rings.

CS 62 Practice Final SOLUTIONS

COURSE 11 PROGRAMMING III OOP. JAVA LANGUAGE

Cost Optimal Parallel Algorithm for 0-1 Knapsack Problem

CS193k, Stanford Handout #10. HW2b ThreadBank

Contribution:javaMultithreading Multithreading Prof. Dr. Ralf Lämmel Universität Koblenz-Landau Software Languages Team

CmpSci 187: Programming with Data Structures Spring 2015

FINAL EXAMINATION. COMP-250: Introduction to Computer Science - Fall 2010

CSE373 Winter 2014, Final Examination March 18, 2014 Please do not turn the page until the bell rings.

The Singleton Pattern. Design Patterns In Java Bob Tarr

AP COMPUTER SCIENCE JAVA CONCEPTS IV: RESERVED WORDS

Interprocess Communication

Total Score /15 /20 /30 /10 /5 /20 Grader

(Refer Slide Time: 02.06)

Computer Science 62. Bruce/Mawhorter Fall 16. Midterm Examination. October 5, Question Points Score TOTAL 52 SOLUTIONS. Your name (Please print)

Options for User Input

Highlights of Last Week

Introduction IS

CSE 417 Branch & Bound (pt 4) Branch & Bound

Lecture 8: September 30

About this exam review

Linked Lists. College of Computing & Information Technology King Abdulaziz University. CPCS-204 Data Structures I

CS5314 RESEARCH PAPER ON PROGRAMMING LANGUAGES

Accelerated Library Framework for Hybrid-x86

Programming at Scale: Concurrency

Allows program to be incrementally parallelized

Encryption Key Search

C16b: Exception Handling

CS 162 Operating Systems and Systems Programming Professor: Anthony D. Joseph Spring 2004

CS1622. Semantic Analysis. The Compiler So Far. Lecture 15 Semantic Analysis. How to build symbol tables How to use them to find

1. Introduction. Lecture Content

Synchronization SPL/2010 SPL/20 1

Programming with the Service Control Engine Subscriber Application Programming Interface

Quiz on Tuesday April 13. CS 361 Concurrent programming Drexel University Fall 2004 Lecture 4. Java facts and questions. Things to try in Java

Chapter 13. Concurrency ISBN

COMP 346 WINTER Tutorial 2 SHARED DATA MANIPULATION AND SYNCHRONIZATION

ITI Introduction to Computing II

Programming with the Service Control Engine Subscriber Application Programming Interface

Practice Problems for the Final

CS 351 Design of Large Programs Threads and Concurrency

COP 3330 Final Exam Review

CSCI 200 Lab 4 Evaluating infix arithmetic expressions

Remedial Java - Excep0ons 3/09/17. (remedial) Java. Jars. Anastasia Bezerianos 1

A First Parallel Program

Remaining Contemplation Questions

CS 112 Introduction to Computing II. Wayne Snyder Computer Science Department Boston University

ITI Introduction to Computing II

SFDV3006 Concurrent Programming

DOWNLOAD PDF CORE JAVA APTITUDE QUESTIONS AND ANSWERS

Graphs. The ultimate data structure. graphs 1

Exception Handling. Sometimes when the computer tries to execute a statement something goes wrong:

Exam in TDDB84: Design Patterns,

Transcription:

Chapter 27 Cluster Work Queues Part I. Preliminaries Part II. Tightly Coupled Multicore Part III. Loosely Coupled Cluster Chapter 18. Massively Parallel Chapter 19. Hybrid Parallel Chapter 20. Tuple Space Chapter 21. Cluster Parallel Loops Chapter 22. Cluster Parallel Reduction Chapter 23. Cluster Load Balancing Chapter 24. File Output on a Cluster Chapter 25. Interacting Tasks Chapter 26. Cluster Heuristic Search Chapter 27. Cluster Work Queues Chapter 28. On-Demand Tasks Part IV. GPU Acceleration Part V. Map-Reduce Appendices 329

330 BIG CPU, BIG DATA R ecall the Hamiltonian cycle problem from Chapter 17. The problem is to decide whether a given graph has a Hamiltonian cycle. A Hamiltonian cycle is a path that visits every vertex in the graph exactly once and returns to the starting point, like the cycle 0, 4, 1, 2, 7, 5, 6, 3 in this graph: In Chapter 17 I developed a multicore parallel program to solve the Hamiltonian cycle problem by doing an exhaustive search of all paths in the graph. The program was designed to do a parallel breadth first search down to a certain threshold level in the search tree of all paths, resulting in some number of subproblems (partial paths). The program then did multiple depth first searches in parallel, each depth first search working on one subproblem in a single thread. I implemented this design using the parallel work queue pattern. There was a work queue of work items. Each parallel team thread repeatedly took a work item out of the queue and processed the item, possibly adding further items to the queue, until there were no more work items.

Chapter 27. Cluster Work Queues 331 Now I want to make this a cluster parallel program, so I can have even more cores searching for a Hamiltonian cycle in parallel. I need a version of the parallel work queue pattern that runs on a cluster of multiple nodes. The threads processing the work items will be in tasks running on the nodes. However, in a distributed memory setting, the tasks cannot have a common work queue in shared memory. Instead, the program must send work items from task to task using intertask communication, that is, via tuple space. In a cluster parallel program (a job), the parallel work queue pattern consists of three parts: Tuple space itself acts as the work queue. Each work item is packaged into a tuple and is put into tuple space. The job consists of a number of worker tasks, each task containing just one worker thread. Each thread takes a work item tuple out of tuple space, processes the work item, puts new work item tuples into tuple space if necessary, and repeats until all work items have been processed. The job main program puts the first work item tuple into tuple space to kick off the processing. In the Hamiltonian cycle cluster parallel program, the work items (tuples) are partial paths, and each task performs either a breadth first or a depth first search on the partial path, depending on the search level. The breadth first search could put additional work items into tuple space, to be processed by other tasks. The threshold level limits the number of work items; once the threshold level is reached, a task does a depth first search rather than spawning more work items. The Parallel Java 2 Library includes class edu.rit.pj2.jobworkqueue for accessing the job s work queue. There can only be one work queue in a job. Under the hood, Class JobWorkQueue handles all the putting and taking of work item tuples. To use class JobWorkQueue:

332 BIG CPU, BIG DATA In the job main program, call the getjobworkqueue() method to get a reference to the job s work queue. The arguments are the class of the work items and the number of worker tasks. The work item class must be a tuple subclass. In the worker task, call the getjobworkqueue() method to get a reference to the job s work queue. The argument is the class of the work items. In the job main program or the worker task, call the work queue s add() method, passing in the work item to add to the job s work queue. In the worker task, call the work queue s remove() method to remove and return the next work item from the job s work queue. Call remove() repeatedly until it returns null, indicating there are no more work items. As a reminder, the worker task must be single threaded. To represent the work items, the cluster parallel HamCycClu program reuses class HamCycState from Chapter 17 (Listing 17.1), which the sequential HamCycSeq program and the multicore parallel HamCycSmp program also used. Class HamCycState encapsulates a partial path in the search over all paths in the graph. To allow work items to be sent back and forth via tuple space, class HamCycState is a tuple subclass. The HamCycClu program also uses class HamCycStateClu. This class is similar to class HamCycStateSmp from Chapter 17 (Listing 17.2), except class HamCycStateClu s enqueue() method adds a work item to the job s work queue. Turning to the code for the HamCycClu program itself (Listing 27.1), the job main program obtains from the command line the constructor expression for the graph to be analyzed as well as the parallel search threshold level (lines 19 20). The program gets a reference to the job s work queue (lines 23 24). The number of worker tasks is specified by the workers= option on the pj2 command line, which the workers() method returns. Using the graph s constructor expression, the program creates an adjacency matrix for the graph (line 28) and uses that to initialize the work item class HamCyc- StateClu (lines 27 29). The program adds the first work item to the work queue (line 32). This is an instance of class HamCycStateClu containing a partial path with just vertex 0. The job sets up two rules. The first is start rule (lines 35 36) that defines a task group with instances of the search task, defined further on. The number of search tasks is specified by the workers= option on the pj2 command line. The search task group will sit in the Tracker s queue until the cluster has enough idle cores to run all the search tasks; this is necessary because the tasks will all be communicating with each other through tuple space. The graph s constructor expression and the parallel search threshold level are passed to each search task (line 36). The job s second rule is a finish rule

Chapter 27. Cluster Work Queues 333 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 package edu.rit.pj2.example; import edu.rit.pj2.job; import edu.rit.pj2.jobworkqueue; import edu.rit.pj2.task; import edu.rit.pj2.tuplelistener; import edu.rit.pj2.tuple.objecttuple; import edu.rit.util.graphspec; import edu.rit.util.instance; public class HamCycClu extends Job // Job main program. public void main (String[] args) throws Exception // Parse command line arguments. if (args.length!= 2) usage(); String ctor = args[0]; int threshold = Integer.parseInt (args[1]); // Set up job's distributed work queue. JobWorkQueue<HamCycState> queue = getjobworkqueue (HamCycState.class, workers()); // Construct graph spec, set up graph. HamCycStateClu.setGraph (new AMGraph ((GraphSpec) Instance.newInstance (ctor)), threshold, queue); // Add first work item to work queue. queue.add (new HamCycStateClu()); // Set up team of worker tasks. rule().task (workers(), SearchTask.class).args (ctor, ""+threshold); // Set up task to print results. rule().atfinish().task (ResultTask.class).runInJobProcess(); // Search task. private static class SearchTask extends Task // Search task main program. public void main (String[] args) throws Exception // Parse command line arguments. String ctor = args[0]; int threshold = Integer.parseInt (args[1]); // Set up job's distributed work queue. JobWorkQueue<HamCycState> queue = getjobworkqueue (HamCycState.class); Listing 27.1. HamCycClu.java (part 1)

334 BIG CPU, BIG DATA (lines 39 40) that spawns a new instance of class ResultTask to print the results once all the search tasks have finished. Once the job commences, each search task obtains the graph s constructor expression and the parallel search threshold level (lines 53 54). The task gets a reference to the job s work queue (lines 57 58). Using the graph s constructor expression, the task creates an adjacency matrix for the graph (line 61) and uses that to initialize the work item class HamCycStateClu (lines 60 62). Thus, all the search tasks will analyze the same graph. When some search task finds a Hamiltonian cycle, the task will put the solution into tuple space as an ObjectTuple containing a HamCycState object with the solution. This signals that the search should stop. The task detects this situation by setting up a tuple listener for the solution tuple (lines 65 73). When this tuple appears, the tuple listener tells the HamCycState class to stop the search (line 71). Following the cluster parallel work queue pattern, the search task now begins the search for a Hamiltonian cycle. The task removes a work item (partial path) from the job s work queue (line 77). The task calls the partial path s search() method to carry out the actual search (line 79). As described in Chapter 17, if the partial path is below the search threshold level, the search() method does one further level of a breadth first search, possibly adding more partial paths to the work queue. If the partial path is at or above the search threshold level, the search() method does a complete depth first search starting from the partial path. If a Hamiltonian cycle was not found, the search() method returns null, otherwise it returns a HamCycState object containing the Hamiltonian cycle. In the latter case, the task puts the solution into tuple space (line 81), thus triggering all the tasks to stop the search. The task repeats the preceding steps until all work items in the job s work queue have been processed (line 77), then the task terminates. The job work queue capability requires that each worker task consists of a single thread. Accordingly, the search task has a sequential while loop (line 77) rather than a parallel loop, and the search task s coresrequired() method is overridden to specify that the task requires one core (lines 86 89). The final piece of the program is the result task (lines 93 109), which runs when all the search tasks have finished. If at this point an ObjectTuple containing a solution exists in tuple space, the result task prints the solution. If not, the result task prints None. That s all the result task does. I ran the sequential HamCycSeq program and the cluster parallel Ham- CycClu program on the tardis cluster to find Hamiltonian cycles in several different 30-vertex graphs, which had from 72 to 82 edges. The parallel program used 120 search tasks, and the search threshold level was 4. The table below shows the running times and speedups I measured.

Chapter 27. Cluster Work Queues 335 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 // Construct graph spec, set up graph. HamCycStateClu.setGraph (new AMGraph ((GraphSpec) Instance.newInstance (ctor)), threshold, queue); // Stop the search when any task finds a Hamiltonian cycle. addtuplelistener (new TupleListener<ObjectTuple<HamCycState>> (new ObjectTuple<HamCycState>()) public void run (ObjectTuple<HamCycState> tuple) HamCycState.stop(); ); // Search for a Hamiltonian cycle. HamCycState state; while ((state = queue.remove())!= null) HamCycState cycle = state.search(); if (cycle!= null) puttuple (new ObjectTuple<HamCycState> (cycle)); // The search task requires one core. protected static int coresrequired() return 1; // Result task. private static class ResultTask extends Task // Task main program. public void main (String[] args) throws Exception ObjectTuple<HamCycState> template = new ObjectTuple<HamCycState>(); ObjectTuple<HamCycState> cycle = trytoreadtuple (template); if (cycle!= null) System.out.println (cycle.item); else System.out.println ("None"); Listing 27.1. HamCycClu.java (part 2)

336 BIG CPU, BIG DATA Running Time (msec) V E Sequential HamCycClu Speedup 30 72 170697 1222 140 30 74 111421 1492 75 30 76 44559 1510 30 30 78 395392 1081 366 30 80 151292 1036 146 30 82 234992 1109 212 The sequential program running on a single core took anywhere from 0.7 to 6.6 minutes to find a Hamiltonian cycle, depending on the graph. In all cases, running on the 120 cores of the 10 nodes of the cluster, the parallel program took less than two seconds. Like the multicore parallel HamCycSmp program in Chapter 17, the cluster parallel HamCycClu program experienced widely varying speedups, sometimes less than the number of cores, sometimes much more and for the same reason. On these graphs, with multiple cores in the cluster nodes processing multiple subproblems in parallel, it so happened that at least one of the cores went to work on a subproblem where a Hamiltonian cycle was found right away. Consequently, the program stopped early, and the running time was small. Under the Hood As mentioned previously, class JobWorkQueue manages the job s work queue in tuple space, handling all putting and getting of tuples to add and remove work items to and from the work queue. Under the hood, class JobWorkQueue uses two tuple classes. The first tuple, class WorkItemTuple, encapsulates one work item in the queue. The second tuple, class ControlTuple, encapsulates the state of the queue itself. The queue state consists of three items: the total number of worker tasks using the queue; the number of worker tasks waiting to remove a work item from the queue; and the number of work items available in the queue. When the job main program calls getjobworkqueue(), the queue state is initialized by putting a control tuple containing the number of worker tasks, zero tasks waiting to remove a work item, and zero work items available. When the job or a task calls the queue s add() method to add a work item to the queue, the add() method takes the control tuple; puts a work item tuple containing the work item; increments the number of available work items in the control tuple; and puts the control tuple back. When a task calls the queue s remove() method to remove and return a work item from the queue, the remove() method takes the control tuple; increments the number of waiting tasks; and puts the control tuple back. The

Chapter 27. Cluster Work Queues 337 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 // Print an error message and exit. private static void usage (String msg) System.err.printf ("HamCycClu: %s%n", msg); usage(); // Print a usage message and exit. private static void usage() System.err.println ("Usage: java pj2 [workers=<k>] " + "edu.rit.pj2.example.hamcycclu \"<ctor>\" <threshold>"); System.err.println ("<K> = Number of worker tasks " + "(default 1)"); System.err.println ("<ctor> = GraphSpec constructor " + "expression"); System.err.println ("<threshold> = Parallel search " + "threshold level"); terminate (1); Listing 27.1. HamCycClu.java (part 3) remove() method then takes the control tuple again, but this time it specifies a template that will cause the take to block until either there are one or more work items available or until all the workers tasks are waiting. In the former case the remove() method takes one of the available work item tuples; decrements the number of waiting tasks and decrements the number of available work items in the control tuple; puts the control tuple back; and returns the work item. In the latter case the remove() method just puts the control tuple back unchanged and returns null. Why this somewhat complicated logic for removing a work item? Why not simply try to take a work item tuple and return null if there are no work item tuples in tuple space? The reason is that if there are no work item tuples, it s not necessarily the case that processing is finished. There might still be one or more tasks processing work items, and these tasks might eventually put more work items into tuple space. We can t conclude that processing is finished until there are no work items and all the worker tasks are waiting to remove a work item, meaning that no worker task is still processing a work item. Because multiple threads in multiple worker tasks can update the work queue concurrently, thread synchronization is needed to ensure the work queue state is always consistent. Synchronization is achieved by taking the control tuple out of tuple space before manipulating the work queue. The semantics of the tuple space take operation ensure that if multiple threads try to

338 BIG CPU, BIG DATA take the control tuple, only one thread will succeed; the other threads will block inside the take method. The thread that took the control tuple can then operate on the work queue. When that thread finishes its queue operation, the thread puts the control tuple reflecting the new queue state back into tuple space. Another thread can then take the control tuple and do a queue operation. In this way, only one thread at a time operates on the work queue. Points to Remember Reiterating Chapter 17, some NP problems can be solved by traversing a search tree consisting of all possible solutions. A search tree can be traversed by a breadth first search or a depth first search. A parallel program traversing a search tree should do a parallel breadth first search down to a certain threshold level, then should do multiple depth first searches in parallel thereafter. Use the parallel work queue pattern to do a parallel loop when the number of iterations is not known before the loop starts. In a cluster parallel program, tuple space itself acts as the work queue, and the work items are encapsulated in tuples put into tuple space. To access the job s work queue, use class JobWorkQueue. Call the getjobworkqueue() method in the job or the worker task to obtain a reference to the job s work queue. Call the job work queue s add() method to add a work item to the queue. Cal the job work queue s remove() method to remove and return a work item from the queue. In the worker task, repeatedly remove a work item from the queue, process the work item, adding more work items to the queue if necessary, until the queue is empty. The worker tasks must be single threaded.