Embracing Explicit Communication in Work-Stealing Runtime Systems

Von der Universität Bayreuth zur Erlangung des Grades eines Doktors der Naturwissenschaften (Dr. rer. nat.) genehmigte Abhandlung

von Andreas Prell aus Kronach

1. Gutachter: Prof. Dr. Thomas Rauber 2. Gutachter: Prof. Dr. Claudia Fohry

Tag der Einreichung: 14. Juli 2016 Tag des Kolloquiums: 20. September 2016

Abstract

Parallel computers are commonplace. The trend of increasing the number of processor cores highlights the importance of : a single- threaded program uses a fraction of a modern processor’s resources and potential, and that fraction will only decrease over the coming processor generations. Existing abstractions for writing parallel programs, such as threads and mu- tual exclusion locks, are difficult to understand, use, and reason about, making them a poor choice for mainstream parallel programming. Higher-level abstrac- tions aim to achieve a more favorable division of labor between programmers and /runtime systems, with programmers expressing and exposing par- allelism and compilers/runtime systems managing parallel execution. A popular and effective abstraction is that of a task, a piece of work, usu- ally a function or a closure, that is safe to execute in parallel with other tasks. decisions, including the mapping of tasks to threads, are made by the runtime system and are not imposed on the programmer. Tasks are well-suited to express fine-grained parallelism, but whether fine- grained parallelism brings performance gains depends on the runtime system and its implementation. State-of-the-art runtime systems employ the scheduling and load balancing technique of work stealing, which is known to be efficient, both in theory and practice. In work stealing, idle workers, called thieves, re- quest tasks from busy workers, called victims, thereby balancing the load. Most implementations of work stealing take advantage of shared memory by letting thieves “steal” tasks from the double-ended queues (deques) of their victims. Modern multiprocessors feature increasingly complex architectures that make it challenging to implement efficient yet flexible work-stealing schedulers. Future manycore processors may have limited support for shared memory, or may rely on message passing for scalable inter-core communication, such as Intel’s SCC research processor, a recent example of a “cluster-on-a-chip”. This thesis aims to put work stealing based on message passing on a bet- ter, more practical foundation, developing techniques to rival the performance of concurrent deque-based implementations, while remaining more flexible. Work stealing based on message passing has been studied before, notably in the context of distributed systems, where MPI still dominates. We present a work-stealing scheduler in which workers communicate with each other through channels, a lightweight message passing abstraction that goes back to Hoare’s Communicat- ing Sequential Processes (CSP). Channels feature prominently in modern pro- gramming languages such as Go and Rust, which advocate messages to commu- nicate, synchronize, and share state between threads. The advantage of using channels, as opposed to using low-level synchronization primitives, is that chan- nels decouple the scheduler from processor-specific features, thereby increasing its flexibility. Large parts of this thesis are dedicated to making channel-based work stealing perform well on modern shared-memory multiprocessors. We describe an implementation in which workers exchange asynchronous steal requests and tasks by passing messages over channels. Termination is detected as a consequence of forwarding steal requests instead of requiring additional control messages to be passed between workers. Dependencies between tasks, most importantly, between parent and child tasks, are expressed with futures, which can be implemented efficiently in terms of channels. Private task queues are more flexible than concurrent ones. We show a simple extension that provides support for adaptive stealing—the ability to switch the stealing strategy at runtime. Fine-grained parallelism requires not only efficient work stealing, but also granularity control to overcome the overhead of task creation and scheduling. Similar tasks, such as iterations of a parallel loop, can be combined into a single task ready to split whenever parallelism is needed. We extend previous work on lazy splitting, integrate it with channel-based work stealing, and demonstrate performance comparable to dedicated loop schedulers in OpenMP. Finally, we provide experimental evidence that channel-based work stealing performs on par with runtime systems based on concurrent deques. Zusammenfassung

Parallelrechner auf Basis von Mehrkernprozessoren sind heutzutage allgegen- wärtig. Da anzunehmen ist, dass die Anzahl der Prozessorkerne, die auf einem Chip Platz finden, weiter steigen wird, besteht Handlungsbedarf. Ohne Paral- lelverarbeitung bleibt das Potenzial eines modernen Rechners zunehmend unge- nutzt. Aus diesem Grund gewinnen Techniken der parallelen Programmierung mehr und mehr an Relevanz. Die klassische -Programmierung gilt als zu diffizil, um ein geeignetes Programmiermodell zu bieten, das auch von Nicht-Experten effektiv eingesetzt werden kann. Eine vielversprechende Alternative ist die Benutzung von Tasks anstelle von Threads. Ein Task bezeichnet eine beliebige Berechnung innerhalb eines Programms, die unabhängig von anderen Berechnungen und damit parallel ausgeführt werden kann. Der Programmierer hat die Aufgabe, Tasks zu spezifizie- ren, während das Laufzeitsystem für deren Ausführung sorgt. Dabei übernimmt das Laufzeitsystem viele kritische Funktionen einschließlich der Verwaltung von Threads, der Zuweisung von Tasks an Threads und der Lastverteilung. Tasks sind leichtgewichtiger als Threads und somit einfach zu erzeugen, selbst für relativ feingranulare Aufgaben. Task-parallele Programme generieren in der Regel eine Vielzahl von Tasks, mit dem Ziel, diese möglichst gleichmäßig an die ausführenden Worker Threads zu verteilen. Wie feingranular die Tasks dabei sein dürfen, hängt von der Effizienz des Laufzeitsystems ab. Von besonderer Bedeutung ist das Work Stealing, eine Scheduling-Technik, bei der Worker Threads, die keine Tasks mehr haben, anderen Worker Threads durch Stehlen Arbeit abnehmen, wodurch eine dynamische Lastverteilung erzielt wird. Üblicherweise besitzt jeder Worker Thread eine eigene Queue-Datenstruktur (Deque), in die Tasks abgelegt und zur Ausführung entnommen werden, und die von anderen Worker Threads zugegriffen werden kann, um Stehlen zu ermögli- chen. Solche Implementierungen sind oft aus Effizienzgründen auf eine bestimmte Hardware-Architektur zugeschnitten. Da die Zukunft Cluster-ähnlichen Vielkern- prozessoren gehören dürfte, ist davon auszugehen, dass Work Stealing Scheduler an diesen Umstand angepasst werden müssen. Eine zu starke Plattformabhän- gigkeit, was zum Beispiel das Vorhandensein bestimmter Synchronisationsopera- tionen betrifft, kann auf lange Sicht eine Portierung erschweren. Die vorliegende Arbeit verfolgt das Ziel, eine effiziente und gleichzeitig flexi- ble Alternative zum klassischen Work Stealing im gemeinsamen Adressraum zu entwickeln. Zu diesem Zweck wird ein Laufzeitsystem entworfen, in dem Wor- ker Threads ausschließlich über Channels miteinander kommunizieren. Direktes Stehlen ist nicht mehr möglich: Worker Threads senden Steal Requests, die mit Tasks beantwortet oder abgelehnt werden. Channels bieten ein einfaches Interface für den Nachrichtenaustausch zwi- schen Threads, das von der Hardware-Architektur abstrahiert und effizient im- plementiert werden kann. Gepufferte Channels ermöglichen asynchrone Kommu- nikation, so dass Worker Threads in der Lage sind, Steal Requests untereinan