1.2 Challenges in TM-based programming
1.2.1 Run-time challenges
The run-time level support corresponds to TM implementations that provide transactional behavior. At this level we see a variety of TM implementations that provide different transactional guarantees but, unfortunately, there is still not a consensus on which of the possible transactional guarantees is the best to support application requirements. Even when the transactional guarantee is determined, the inherent complexity of providing a transaction abstraction leads to sophisticated TM implementations and makes it hard to specify clearly the semantic properties of a given TM implementation. The same complexity also introduces challenges in testing TM implementations.
Another aspect of a TM implementation that requires attention is the performance it delivers. This aspect is even the mostly studied aspect of TMs because without acceptable performance, it is very hard for TM-based programming to be considered as a widespread programming paradigm. In that respect, the large set of proposed TM implementations are classified broadly on the basis of whether they are software-based (Software TM, STM for short), hardware-based (Hardware TM, HTM for short) or a hybrid solution which has both software and hardware components. STMs introduce a significant overhead since each data access of a code section running under transactional semantics corresponds to a large number of instructions instead of a simple memory access instruction. While this hampers the performance of an STM, its flexibility allows (i) transactions of any size and of any type to be supported (hence it is attractive to be used at least as a fallback alternative) (ii) exploring different design alternatives. Conversely, HTM solutions deliver good performance but are limited by transaction size or type.
When designing a TM runtime neither the semantic nor the performance aspects can be overlooked and they need to be considered together for an acceptable solution. This makes the TM design even more challenging.
1.2.2 Compile-time challenges
The compile-time support required for TM-based programming mainly consists of the lan- guage support required to provide the transaction abstraction. This language support has two main components: the specification of the language constructs required for transaction abstraction and the transformation of the code enclosed in transactions to code that uses TM implementations through TM specific function calls (this transformation is also called transactification).
For the specification of language constructs, the ideal language construct for a transac- tion is a transaction block that encloses arbitrary program statements and executes them under transactional semantics. However, it is not straightforward to use such ideal trans- action blocks in programming languages. There are two reasons for this:
The transaction abstraction is not a solution for all concurrent programming issues [73, 167]. It offers simplified data synchronization, while it cannot be used with the same ease for synchronizing tasks for coordination. In this sense, the language constructs that provide transaction abstraction need to co-exist with task synchro- nization mechanisms without harming the correctness of the application and keeping the simplicity they offer for data synchronization.
The ability to use some language features in a transaction block prevents us to use a simple transactional semantics [190,167,59]. For example, the use of irrevocable language constructs (such as I/O accesses, systems calls, external library calls) dis- allows the rollback-and-restart behavior of transaction constructs. Another example is the necessity to support different types of exceptions in a transaction: some ex- ceptions can be handled only if the transaction commits its changes before raising the exception (the recovery of such exceptions can be performed only if the program state at the moment of exception raise is known), while for some others it is better to roll the transaction back in order to preserve application consistency.
In short, the required language constructs to support TM should be flexible enough to cover all the necessary semantic properties discussed above while keeping the simplicity of the ideal transaction block as much as possible.
The transactification process, compared to specification, is a mechanical process. The main challenge in the transactification is to generate code in all possible allowed situations and keeping the semantics of the generated code as required by the specification.
STM Libraries Bytecode instrumentation tool
TMJava (Chapter 6) Synchro- nization Transactional Control Flow Exception Handling Concurrent Exception Hand. TMunit (Chapter 4) Applications (e.g., STAMP, STMBench7, LeeTM, etc.)
Atomic Boxes (Chapter 7) TM-based programming API (Chapter 5) SW stack for language integration of TMs
Standard Java compiler (e.g., Sun javac)
Transactification
compiler frontend
Binary instrumentation
Figure 1.1: An overview of the novel tools constituting part of the contributions of the thesis. The figure illustrates the locations of each tool (the shaded components) on the software stack. Each shaded element is labeled with the name of the tool to which it corresponds and its location in the thesis text.
1.3
Thesis contributions
This thesis contributes to the development of the TM support for programming languages at both the run-time and the compile-time levels. The major part of the contributions have been embodied in tools which are depicted by shaded components in Figure 1.1. Although the contributions of the thesis apply most of the time also for HTMs, we mainly focused on STMs for their availability and their ability to offer diverse designs.
For the run-time level of TM support, our first contribution is a qualitative classifica- tion of STM designs (Chapter 3). In this work, we first point that many STM designs abort more often than necessary (i.e., STMs abort even in some cases where application correctness is preserved). We find different classes of aborts STM designs perform un- necessarily and classify designs accordingly. The classification sheds light on the tradeoff between the simplicity and the effectiveness of an STM. This work has been published in a conference publication ”Toward a Theory of Input Acceptance for Transactional Memo- ries” (OPODIS’08) [69] and a follow-up journal publication ”On the Input Acceptance of Transactional Memory” (PPL’10) [70].
Our second contribution for the run-time level (appearing in Chapter 4) is a domain- specific language and an associated framework, TMunit, which allows testing both semantic and performance aspects of TM implementations (work on TMunit has been published in the workshop publication ”TMUNIT: Testing Transactional Memories” (TRANSACT’09) [85], and in two journal publications ”Extensible Transactional Memory Testbed” (JPDC’10) [87] and ”The VELOX Transactional Memory Stack” (MICRO’10) [14]). The language is unique in that it allows specification of deterministic scenarios for TMs, which we call semantic tests, with which it is possible to test exactly the desired semantic property of a TM. The language is simple and concise, leading to a rapid specification of such deterministic scenarios. The language also offers the specification of synthetic workloads for TMs (we call these performance tests), to test TMs under desired workload conditions. This way, it helps TM designers to evaluate the performance of TM implementations. The fact that the language permits the specification of both semantic and performance tests is an important asset since a TM designer needs to consider both aspects together to provide the best possible design. The TMunit framework (appearing at the top level of the software stack in Figure1.1and directly driving the STM libraries) interprets this domain- specific language and offers an abstract interface for TMs so that theoretically any TM can be tested with the specified semantic and performance tests. Last but not least, the deterministic scenarios designed for TMs can be used to test and debug TM implementations by generating deterministic interleavings of TM library function executions.
For the compile-time support, our first contribution (appearing in Chapter 5) is the specification of a language extension (denoted as the TM-based programming API on Fig- ure1.1) for supporting a transactional construct in Java. Although part of the specification reuses the existing specification for C++ [12], our specification goes beyond the features proposed by the C++ specification. The focus of the C++ specification is only the data synchronization aspect that can be offered by TMs. However, we argue that transaction abstraction can also be useful in other aspects of programming and we describe novel transactional control flow and exception handling constructs (both for single-threaded and multi-threaded exception handling) that are not present in the C++ specification.
An implementation of the language specification is also made available to the program- mers via a compiler framework, TMJava (Chapter6). As can be noticed from Figure 1.1 the transactification that we have performed is split into two layers in the software stack that we have used for integrating TM to the Java language. In this setting, TMJava appears only on the layer above the traditional Java compiler and operates in coordination with a bytecode instrumentation tool that performs (only) the transactification of class methods annotated specifically to be executed in transactional context. The role of TMJava in this
design is to transform the source code expressed according to our language specification to an equivalent Java code where transactional regions are mapped to annotated class methods (that are to be transactified by bytecode instrumentation tool).
As part of our Java language specification, we also propose an original extension, Atomic Boxes (presented in Chapter 7), that allows programmers to handle exceptions among multiple threads in a coordinated manner (see also the API and compiler frontend layer of Figure 1.1 where this extension is made explicit). The extension makes use of the transaction abstraction to keep a multi-threaded application always in a consistent state even when exceptions are raised. On the basis of this feature, the extension is capable of reverting the complete application to a consistent state upon an exception and provides recovery actions that can be coordinated among multiple threads. This is particularly useful to handle exceptions having implications on threads other than the one raising the exception. Our work on Atomic Boxes has been presented in the conference ECOOP’11 under the title ”Atomic Boxes: Coordinated Exception Handling with Transactional Memory” [86].
1.4
Thesis organization
The content of the thesis is organized in two major parts. The first part comprises an analysis of TM from different aspects. Background material on TM as well as semantic issues on TM and associated transactions are presented in Chapter 2. This is followed by Chapter3that qualitatively classifies STM designs with respect to their abort performance. Chapter 4, the last chapter of the first part, presents our testing framework, TMunit and an associated testbed, that allows experimenting both semantic and performance properties of TM implementations.
The second part of the thesis explains our Java language support for TM. Chapter 5 introduces the language extension syntax and its associated semantics. This language extension includes the language constructs we propose to provide automated data synchro- nization as well as transactional control flow and coordinated exception handling. While Chapter 6 explains the implementation details of data synchronization and transactional control flow constructs specified in Chapter 5, Chapter 7 discusses the semantics of the proposed construct for coordinated exception handling, presents its implementation details and evaluates the performance of the construct.
The thesis is concluded by Chapter 8 where we summarize the main findings of our work and present possible future research directions.
Background
2.1
Introduction
A software system can be described using two complementary approaches; one data-centric and the other activity-centric [114]. The data-centric approach describes the system based on data structures that get involved in the execution. In this approach i) data is grouped into data structures that represent components or subsystems ii) data structures interact with each other (as components or subsystems) to perform the required functionality. The activity-centric approach describes the system in terms of possible activities. An activity can involve sets of function call-chains, event sequences, tasks, sessions, transactions and threads. Some activities can even span multiple threads and may even correspond to system-wide use cases.
Neither the data-centric nor the activity-centric approach provide a complete under- standing of the whole software system, since a data structure can be involved in multiple activities and an activity can span multiple data structures. However, these two approaches lead to two complementary group of properties for software systems [114]:
Safety: Nothing ever bad happens. This group of properties are data-centric and deal with correct manipulation of data structures. Failure of a safety property leads to unintended and unexpected execution behavior.
Liveness: Something eventually happens within an activity. This group of properties are activity-centric and deal with progress of activities. Failure in ensuring a liveness property can lead to lack of progress of some activities towards completion, which can even bring the system to a halt.
Some of the safety and liveness properties of systems are related more to the quality of the software and this further leads to two groups of quality concerns [114]:
Reusability: The utility of objects and classes across multiple contexts. Performance: The extent to which activities execute soon and quickly.
Even without considering quality concerns, ensuring safety and liveness properties can sometimes be contradictory in designing programs, i.e., improving or introducing a safety property may destroy some liveness property and vice versa. Obviously, all safety properties can be guaranteed such that no activity can ever terminate, but of course this does not produce a useful program. Hence, designing a software system requires a programmer to care both the safety and liveness properties expected from a program.
Taking both types of properties into account is already not easy for sequential programs. In concurrent programs, however, an additional difficulty in ensuring safety and liveness properties is the need for correct synchronization among threads. A particular type of synchronization that introduces difficulties in ensuring safety and liveness properties of multi-threaded programs is data synchronization.
The work presented in this thesis mainly focuses on a recent data synchronization mechanism, transactional memory (TM) and in particular the semantics and performance associated to it. Hence, in this chapter, we present fundamental terms and issues related to semantics, performance and design of both TMs and TM-based programs. We start by describing the difficulties of programming with locks in Section 2.2 to motivate the use of TMs for concurrent programming. Then, we present TM and its fundamental semantics in Section 2.3. Section 2.4 discusses semantic issues specific to the transaction abstrac- tion that should be supported by TM implementations and presents a spectrum of possible transaction guarantees that can be provided by TMs. Semantic issues specific to a lan- guage construct that provides the transaction abstraction and existing solutions to solve the related problems are presented in Section2.5. Section2.6discusses fundamental notions in evaluating TM performance and Section 2.7 presents different STM implementations focusing on their distinguishing design properties. The chapter is concluded by Section2.8 explaining how the content of the chapter is related to the work presented in other chapters of the thesis.