• No results found

6. Evaluation: The OMOP as a Unifying Substrate

6.2. Case Studies

6.2.2. Software Transactional Memory

Introduction Software Transactional Memory (STM) (cf.Sec. 2.4.3) promises

a solution to the engineering problems of threads and locks. This program- ming model enables a programmer to reason about all code as if it were exe- cuted in some sequential order. The runtime tries however to execute threads in parallel while giving the strong correctness guarantees implied by ensuring sequential semantics. While common consensus [Cascaval et al.,2008] seems

to be that the performance overhead of STM systems is too high for many practical applications, the idea continues to be appealing.4 Thus, it is used as one of the case studies.

This evaluation uses LRSTM (Lukas Renggli’s STM), which is based on the implementation described byRenggli and Nierstrasz[2007] (cf.Sec. 7.1.3).

The ad hoc version of LRSTM is a port of the original implementation to the current version of Squeak and Pharo. It uses the original approach, i. e., it uses abstract syntax tree (AST) transformation to add the tracking of state access and to adapt the use of primitives to enable full state access tracking. The implementation details are discussed below.

4The performance of an STM depends also on the programming language it is applied to.

Languages such as Haskell and Clojure restrict mutation and thereby experience lower overhead from an STM than imperative languages with unrestricted mutation, where the STM needs to track all state access.

Important for this evaluation is that both, the ad hoc as well as the OMOP- based implementation provide the same guarantees.

General Implementation The general idea behind the implemented STM is

the same in both cases. To be precise, both of our implementations share the main code parts, only the code that realizes the capturing of field accesses is different.

For the sake of brevity, the evaluation disregards LRSTM’s support for nested transactions, error handling, and other advanced features. The imple- mentation discussed here is a sketch of a minimal STM system that enables atomic execution of transactions.

The implementation sketch is given inLst. 6.4. It builds on top of the ability to change all state access operations to work on a workingCopy of an object, instead of working on the object directly. To this end, in the context of a trans- action each object can obtain a working copy for itself. A transaction main- tains a set of changes, represented by Change objects. These change objects maintain the connection between the working copy and the original object, as well as a copy of the original object. Thus, all operations in the scope of a transaction result in read and write operations to the working copy of an object instead of changing the original.

When a transaction is to be committed, all threads are stopped and the #commit operation checks for conflicting operations. #hasConflict checks whether the original object has changed compared to when the current trans- action accessed it the first time. If no conflicts are found, the changes made during the transaction are applied, and the commit succeeds.

Managing State Access The main mechanism both implementations need to

provide is an interception of all state access operations. Thus, all field reads and writes need to be adapted to operate on a working copy instead of the original object. Furthermore, all primitives that read from or write to objects need to be adapted in a similar way.

Ad Hoc Solutions Renggli and Nierstrasz [2007] proposed to use program

transformation to weave in the necessary operations. Instead of using an AST transformation as done here (cf. Sec. 7.1), such adaptions could be ap- plied with higher-level approaches such as aspect-oriented programming (cf.

1 Object = ( | t r a n s a c t i o n | " ... " 2 w o r k i n g C o p y = (

3 " Answer a s u r r o g a t e for use within current t r a n s a c t i o n . " 4 | t r a n s a c t i o n |

5 t r a n s a c t i o n := P r o c e s s o r a c t i v e P r o c e s s c u r r e n t T r a n s a c t i o n

6 ifNil : [ self error : ’ No active t r a n s a c t i o n ’ ].

7 ^ ( t r a n s a c t i o n c h a n g e F o r : self ) working ) ) 8 9 Change = ( 10 | o r i g i n a l working p r e v i o u s | 11 i n i t i a l i z e O n : obj = ( 12 o r i g i n a l := obj . 13 p r e v i o u s := obj s h a l l o w C o p y . 14 working := obj s h a l l o w C o p y ) 15

16 apply = ( o r i g i n a l c o p y F r o m : working " copies all fields " ) 17

18 " c o m p a r e s all field p o i n t e r s "

19 h a s C o n f l i c t = ( ( o r i g i n a l i s I d e n t i c a l : p r e v i o u s ) not ) 20

21 - - - - " class side i n i t i a l i z e r "

22 on : obj = ( ^ self new i n i t i a l i z e O n : obj ) ) 23

24 T r a n s a c t i o n = (

25 | changes domain | " T r a n s a c t i o n s run in a give domain " 26 " ... "

27 begin = ( changes := I d e n t i t y D i c t i o n a r y new )

28 c h a n g e F o r : obj = ( ^ changes at : obj

29 i f A b s e n t P u t : [ Change on : obj ] ) 30 31 commit = u n e n f o r c e d ( 32 " Commit a t r a n s a c t i o n a t o m i c a l l y " 33 domain s t o p T h e W o r l d E x c e p t : P r o c e s s o r a c t i v e P r o c e s s . 34 35 changes do : [ : each | 36 each h a s C o n f l i c t ifTrue : [

37 Error signal : ’ T r a n s a c t i o n aborted ’ ] ].

38

39 " No conflicts , do commit "

40 changes do : [ : each | each apply ].

41

42 domain r e s u m e T h e W o r l d E x c e p t : P r o c e s s o r a c t i v e P r o c e s s ) )

The primitives require two different solutions for simple and for more com- plex functionality. For simple primitives, it is sufficient to ensure that the object they are applied to is the working copy. For more complex primitives that for instance modify collections and their elements, this solution is not sufficient, because they could obtain object references to the original objects and access those. Therefore, a solution needs to be able to provide substi- tutes for primitives.Renggli and Nierstraszuse Smalltalk reimplementations that emulate the primitives and thus were subject to the changes that adapt state access. Note that this approach works for primitives that are solely used for performance reasons. It does not work for functionality that can not be expressed in Smalltalk.

OMOP-base Solution The implementation of LRSTM on top of the OMOP

defines the STMDomain inLst. 6.5. It customizes the domain’s intercession han- dlers #readField:of: and #write:toField:of: to redirect state access to the working copy. Furthermore, it intercepts all primitives that access state, in- cluding reflection primitives, to redirect them to the working copy as well.

As mentioned some primitives require a reimplementation because they perform complex operations on the given object. One example is the primi- tive for #nextPut:, which operates on writeable streams, i. e., collections that store sequences of elements and can grow if required.Lst. 6.5implements the intercession handler for this primitive atline 20. Instead of merely obtaining the working copy of the stream, as is done by the other primitives, it uses a reimplementation of the primitive and executes it with enforcement enabled.

Lst. 6.6shows the reimplementation of the primitive in Smalltalk. This reim-

plementation enables the OMOP to track all state access as required for the STM.

A minor difference with the ad hoc version is that the OMOP-based im- plementation customizes #requestThreadResume: to register all threads that start executing inside the STM domain. Lst. 6.5 shows at line 25 how it is used to add threads to a set. The ad hoc implementation just assume that all threads are required to be stopped and iterates over all instances of the Smalltalk Process class.

Handled Challenges The main challenges handled by the OMOP are to pro-

vide custom execution policies for primitives, which allows them to be reimple- mented, and to provide custom state access policies. Both need to be enforced against reflective operations to enable safe use of reflective field updates as

1 S T M D o m a i n = Domain ( | p r o c e s s e s | 2 r e a d F i e l d : idx of : obj = u n e n f o r c e d ( 3 ^ obj w o r k i n g C o p y i n s t V a r A t : idx ) 4

5 write : val toField : idx of : obj = u n e n f o r c e d (

6 ^ obj w o r k i n g C o p y i n s t V a r A t : idx put : val ) 7

8 primat : idx on : obj = u n e n f o r c e d (

9 ^ obj w o r k i n g C o p y at : idx )

10

11 primat : idx put : val on : obj = u n e n f o r c e d (

12 ^ obj w o r k i n g C o p y at : idx put : val )

13

14 p r i m i n s t V a r A t : idx on : obj = u n e n f o r c e d ( 15 ^ obj w o r k i n g C o p y i n s t V a r A t : idx ) 16

17 p r i m i n s t V a r A t : idx put : val on : obj = u n e n f o r c e d ( 18 ^ obj w o r k i n g C o p y i n s t V a r A t : idx put : val ) 19

20 p r i m N e x t : stream put : val = u n e n f o r c e d (

21 ^ self e v a l u a t e E n f o r c e d : [ stream n o p r i m N e x t P u t : val ] ) 22

23 " ... and all other p r i m i t i v e s that access state ... " 24 25 r e q u e s t T h r e a d R e s u m e : process = u n e n f o r c e d ( 26 p r o c e s s e s add : process . 27 ^ process resume ) 28 29 i n i t i a l i z e = ( p r o c e s s e s := W e a k I d e n t i t y S e t new ) 30 31 s t o p T h e W o r l d E x c e p t : proc = (

32 ( p r o c e s s e s c o p y W i t h o u t : proc ) do : [: each | proc suspend ] ) 33

34 r e s u m e T h e W o r l d E x c e p t : proc = (

35 ( p r o c e s s e s c o p y W i t h o u t : proc ) do : [: each | proc resume ] ) )

1 W r i t e S t r e a m = Stream ( | p o s i t i o n w r i t e L i m i t c o l l e c t i o n | 2 n o p r i m N e x t P u t : obj = (

3 p o s i t i o n >= w r i t e L i m i t

4 ifTrue : [ ^ self p a s t E n d P u t : obj ]

5 ifFalse : [ p o s i t i o n := p o s i t i o n + 1.

6 ^ c o l l e c t i o n at : p o s i t i o n put : a n O b j e c t ] ) )

Listing 6.6: Primitive Reimplemented in Smalltalk to enable State Access Tracking

for STM

part of a transaction. The OMOP provides the corresponding intercession han- dlers to enable the necessary customization without the need for using for instance AST transformations directly. The intercession handlers used for the STM satisfy the requirements for managed state and managed execu- tion. In addition, the mechanisms to satisfy controlled enforcement provide the necessary flexibility to reimplement primitives in Smalltalk for instance.

Related documents