Welcome to Scribd, the world's digital library. Read, publish, and share books and documents. See more
Download
Standard view
Full view
of .
Look up keyword
Like this
1Activity
0 of .
Results for:
No results containing your search query
P. 1
Parallel Gc

Parallel Gc

Ratings: (0)|Views: 25 |Likes:

More info:

Published by: captain_recruiter_robot on Nov 22, 2009
Copyright:Attribution Non-commercial

Availability:

Read on Scribd mobile: iPhone, iPad and Android.
download as PDF, TXT or read online from Scribd
See more
See less

11/15/2013

pdf

text

original

Parallel Generational-Copying Garbage Collection with a
Block-Structured Heap
Simon Marlow
Microsoft Research
simonmar@microsoft.com
Tim Harris
Microsoft Research
tharris@microsoft.com
Roshan P. James
Indiana University
rpjames@indiana.edu
Simon Peyton Jones
Microsoft Research
simonpj@microsoft.com
Abstract

We present a parallel generational-copying garbage collector im- plemented for the Glasgow Haskell Compiler. We use a block- structured memory allocator, which provides a natural granularity for dividing the work of GC between many threads, leading to a simple yet effective method for parallelising copying GC. The re- sults are encouraging: we demonstrate wall-clock speedups of on average a factor of 2 in GC time on a commodity 4-core machine with no programmer intervention, compared to our best sequential GC.

Categories and Subject DescriptorsD.3.4 [Programming Lan-
guages]: Processors\u2014Memory management (garbage collection)
General TermsLanguages, Performance
1. Introduction

Garbage collection (GC) involves traversing the live data struc- tures of a program, a process that looks parallel even in sequential programs. Like many apparently-parallel tasks, however, achieving wall-clock speedups on real workloads is trickier than it sounds.

In this paper we report on parallel GC applied to the Glasgow Haskell Compiler. This area is dense with related work, as we dis- cuss in detail in Section 6, so virtually no individual feature of our design is new. But the devil is in the details: our paper describes a tested implementation of a full-scale parallel garbage collector, integrated with a sophisticated storage manager that supports gen- erations, \ufb01nalisers, weak pointers, stable pointers, and the like. We offer the following new contributions:

\u2022We parallelise a copying collector based on ablock-structured
heap(Section 3). The use of a block-structured heap affords
great \ufb02exibility and, in particular, makes it easy to distribute
work in chunks of variable size.

Permission to make digital or hard copies of all or part of this work for personal or classroom use is granted without fee provided that copies are not made or distributed for pro\ufb01t or commercial advantage and that copies bear this notice and the full citation on the \ufb01rst page. To copy otherwise, to republish, to post on servers or to redistribute to lists, requires prior speci\ufb01c permission and/or a fee.

ISMM\u201908,June 7\u20138, 2008, Tucson, Arizona, USA.
Copyrightc
\ue0002008 ACM 978-1-60558-134-7/08/06. . . $5.00.
\u2022We extend this copying collector to agenerational scheme
(Section 4), an extension that the block-structured heap makes
quite straightforward.
\u2022We exploit the update-once property, enjoyed by thunks in a
lazy language, to reduce garbage-collection costs by using a
new policy calledeager promotion (Section 4.1).
\u2022We implement and measure our collector in the context of an

industrial-strength runtime (the Glasgow Haskell Compiler), using non-toy benchmarks, including GHC itself (Section 5). We give bottom-line wall-clock numbers, of course, but we also try to give some insight into where the speedups come from by de\ufb01ning and measuring a notion ofwork imbalance (Section 5.3).

To allay any confusion, we are using the term block-structured here to mean that the heap is divided into \ufb01xed-size blocks, not in the sense of a block-structured programming language.

In general, our use of a block-structured heap improves on ear- lier work in terms of simplicity (fewer runtime data structures) and generality (an arbitrary number of independently re-sizable gener- ations, with aging), and yet achieves good speedups on commodity multiprocessor hardware. We see speedups of between a factor of 1.5 and 3.2 on a 4-processor machine. Against this speedup must be counted a slow-down of 20-30% because of the extra locking required by parallel collection \u2014 but we also describe a promising approach for reducing this overhead (Section 7.1).

The bottom line is that on a dual-core machine our parallel GC reduces wall-clock garbage-collection time by 20% on average, while a quad-core can achieve more like 45%. These improvements are extremely worthwhile,given that they come with no program-

mer intervention whatsoever; and they can be regarded as lower

bounds, because we can already see ways to improve them. Our collector is expected to be shipped as part of a forthcoming release of GHC.

2. The challenge we address
The challenge we tackle is that ofperforming garbage collection
in parallelin a shared-memory multiprocessor; that is, employing
many processors to perform garbage collection faster than one
processor could do alone.

We focus onparallel, rather thanconcurrent, collection. In a concurrent collector the mutator and collector run at the same time, whereas we only consider garbage collecting in parallel while the mutator is paused. The mutator is free to use multiple processors

too, of course, but even if the mutator is purely sequential, parallel
garbage collection may still be able to reduce overall run-time.
2.1 Generational copying collection

Our collector is a generational, copying collector [Ung84], which we brie\ufb02y summarise here to establish terminology. The heap is divided intogenerations, with younger generations having smaller numbers. Whenever generationn is collected, so are all younger generations. Aremembered set for generationn keeps track of all pointers from generationn into younger ones. In our implementa- tion, the remembered set lists allobjects that contain pointers into a younger generation, rather than listing allobject \ufb01elds that do so. Not only does this avoid interior pointers, but it also requires less administration when a mutable object is repeatedly mutated.

To collect generations0\u2212n, copy intoto-space all heap objects in generations0\u2212n that are reachable from theroot pointers, or from the remembered sets of generations older thann. More speci\ufb01cally:

\u2022Evacuateeach root pointer and remembered-set pointer. To
evacuate a pointer, copy the object it points to into to-space,
overwrite the original object (in from-space) with aforwarding
pointerto the new copy of the object, and return the forwarding

pointer. If the object has already been evacuated, and hence has been overwritten with a forwarding pointer, just return that pointer.

\u2022Scavengeeach object in to-space; that is, evacuate each pointer

in the object, replacing the pointer with the address of the evac- uated object. When all objects in to-space have been scavenged, garbage collection is complete.

Objects arepromoted from a younger to an older generation, based on atenuring policy. Most objects die young (the \u201cweak genera- tional hypothesis\u201d), so it is desirable to avoid promoting very young objects so they have an opportunity to perish. A popular policy is therefore to promote an object from generationn ton+1 only when it has survivedkn garbage collections, for somekn chosen independently for each generation. Rather than attach an age to every object, they are commonly partitioned by address, by sub- dividing each generationn intoknsteps. Then objects from step

knof generation nare promoted to generation n+1, while objects
from younger steps remain in generationn, but with an increased
step count.

It seems obvious how to parallelise a copying collector: differ- ent processors can evacuate or scavenge different objects. All the interest is in the details. How do we distribute work among the pro- cessors, so that all are busy but the overheads are not too high? How do we balance load between processors? How do we ensure that two processors do not copy the same data? How can we avoid unnecessary cache con\ufb02icts? Garbage collection is a very memory- intensive activity, so memory-hierarchy effects are dominant.

Before we can discuss these choices, we \ufb01rst describe in more
detail the architecture of our heap.
2.2 The block-structured heap

Most early copying garbage collectors partitioned memory into two large contiguous areas of equal size, from-space and to-space, to- gether perhaps with anallocation area ornursery in which new ob- jects are allocated. Dividing the address space in this way is more awkward for generational collection, because it is not clear how big each generation should be \u2014 and indeed these sizes may change dynamically. Worse still, the steps of each generation further sub- divide the space, so that we haven\u2217 k spaces, each of unpredictable size. Matters become even more complicated if there are multiple mutator or garbage collector threads, because then we need multi- ple allocation areas and to-spaces respectively.

Info pointer
Info
table
Payload
Object type
Layout info
Entry code
... ... ...
Type-specific
fields
Figure 1.A heap object
Thus motivated, GHC\u2019s storage manager uses ablock-structured
heap. Although this is not a new idea [DEB94, Ste77], the literature
is surprisingly thin, so we pause to describe how it works.
\u2022The heap is divided into \ufb01xed-sizeB-byteblocks. Their exact

sizeB is not important, except that it must be a power of 2. GHC uses 4kbytes blocks by default, but this is just a compile- time constant and is easily changed.

\u2022Each block has an associatedblock descriptor, which describes

the generation and step of the block, among other things. Any address within a block can be mapped to its block descriptor with a handful of instructions - we discuss the details of this mapping in Section 2.3.

\u2022Blocks can be linked together, through a \ufb01eld in their descrip-

tors, to form anarea. For example, after garbage collection the mutator is provided with an allocation area of free blocks in which to allocate fresh objects.

\u2022The heap containsheap objects, whose layout is shown in Fig-

ure 1. From the point of view of this paper, the important point is that the \ufb01rst word of every heap object, itsinfo pointer, points to its statically-allocatedinfo table, which in turn contains lay- out information that guides the garbage collector.

\u2022Aheap pointer always addresses the \ufb01rst word of a heap object;
we do not support interior pointers.
\u2022A large object, whose size is greater than a block, is allocated
in ablock group of contiguous blocks.

Free heap blocks are managed by a simpleblock allocator, which allows its clients to allocate and free block groups. It does simple coalescing of free blocks, in constant time, to reduce fragmentation. If the block allocator runs out of memory, it acquires more from the operating system.

Dividing heap memory into blocks carries a modest cost in terms of implementation complexity, but it has numerous bene\ufb01ts. We consider it to be one of the best architectural decisions in the GHC runtime:

\u2022Individual regions of memory (generations, steps, allocation

areas), can be re-sized at will, and at run time. There is no need for the blocks of a region to be contiguous; indeed usually they are not.

\u2022Large objects need not be copied; each step of each generation

contains a linked list of large objects that belong to that step, and moving an object from one step to another involves removing it from one list and linking it onto another.

\u2022There are places where it is inconvenient or even impossible

to perform (accurate) garbage collection, such as deep inside a runtime system C procedure (e.g. the GMP arbitrary-precision arithmetic library). With contiguous regions we would have to

estimate how much memory is required in the worst case before making the call, but without the requirement for contiguity we can just allocate more memory on demand, and call the garbage collector at a more convenient time.

\u2022Memory can be recycled more quickly: as soon as a block has

been freed it can be re-used in any other context. The bene\ufb01t of doing so is that the contents of the block might still be in the cache.

Some of these bene\ufb01ts could be realised without a block- structured heap if the operating system gave us more control over the underlying physical-to-virtual page mapping, but such tech- niques are non-portable if they are available at all. The block- structured heap idea in contrast requires only a way to allocate memory, which enables GHC\u2019s runtime system to run on any plat- form.

2.3 Blockdescriptors
Each block descriptor contains the following \ufb01elds:

\u2022A link \ufb01eld, used for linking blocks together into an area.
\u2022A pointer to the \ufb01rst un-allocated (free) word in the block.
\u2022A pointer to the \ufb01rst pending object, used only during garbage

collection (see Section 3.4).
\u2022The generation and step of the block. Note that all objects in a
block therefore reside in the same generation and step.
\u2022If this is a block group, its size in blocks.

Where should block descriptors live? Our current design is to allocate memory from the operating system in units of anM -byte megablock, aligned on anM -byte boundary. A megablock consists of just underM/B contiguous block descriptors followed by the same number of contiguous blocks. Each descriptor is a power-of- 2 in size. Hence, to get from a block address to its descriptor, we round down (mask low-order bits) to get the start of the megablock, and add the block number (mask high-order bits) suitably shifted.

An alternative design would be to have variable-sized blocks, each a multiple ofB bytes, with the block descriptor stored in the \ufb01rst few bytes of the block. In order to make it easy to transform a heap pointer to its block descriptor, we would impose the invariant that a heap object must have its \ufb01rst word allocated in the \ufb01rstB bytes of its block.

3. Parallel Copying GC

We are now ready to describe our parallel collector. We should stress that while we have implemented and extensively tested the algorithms described here, we have not formally proven their cor- rectness.

We focus exclusively on non-generational two-space copying in
this section, leaving generational collection until Section 4.

We will suppose that GC is carried out by a set ofGC threads, typically one for each physical processor. If a heap object has been evacuated but not yet scavenged we will describe it as apending

object, and the set of (pointers to) pending objects as the pending
set. The pending set thus contains to-space objects only.

The most obvious scheme for parallelising a copying collector is as follows. Maintain a single pending set, shared between all threads. Each GC thread performs the following loop:

while (pending set non-empty) {
remove an object p from the pending set
scavenge(p)
add any newly-evacuated objects to the pending set
}
3.1 Claiming an object for evacuation

When a GC thread evacuates an object, it must answer the question \u201chas this object already been evacuated, and been overwritten with a forwarding pointer?\u201d. We must avoid the race condition in which two GC threads both evacuate the same object at the same time. So, like every other parallel copying collector known to us (e.g. [FDSZ01, ABCS01]), we use an atomic CAS instruction to claim an object when evacuating it.

The complete strategy is as follows: read the header, if the object is already forwarded then return the forwarding pointer. Otherwise claim the object by atomically writing a special value into the header, spinning if the header indicates that the object is already claimed. Having claimed the object, if the object is now a forwarding pointer, unlock it and return the pointer. Otherwise, copy it into to-space, and unlock it again by writing the forwarding pointer into the header.

There are variations on this scheme that we considered, namely:
\u2022Claim the object before testing whether it is a forwarding
pointer. This avoids having to re-do the test later, at the expense
of unnecessarily locking forwarding pointers.
\u2022Copy the object \ufb01rst, and then write the forwarding pointer

with a single CAS instruction. This avoids the need to spin, but means that we have to de-allocate the space we just allocated (or just waste a little space) in the event of a collision.

We believe the \ufb01rst option would be pessimal, as it does un- necessary locking. The second option may be an improvement, but probably not a measurable one, since as we show later the fre- quency of collisions at the object level is extremely low.

This \ufb01ne-grain per-object locking constitutes the largest single overhead on our collector (measurements in Section 5.1), which is frustrating because it is usually unnecessary since sharing is relatively rare. We discuss some promising ideas for reducing this overhead in Section 7.1.

3.2 Privatisingto-space

It is obviously sensible for each GC thread to allocate in a private to-space block, so that no inter-thread synchronisation need take place on allocation. When a GC thread \ufb01lls up a to-space block, it gets a new one from a shared pool of free blocks.

One might worry about the fragmentation arising from having one partly-\ufb01lled block for each GC thread at the end of garbage collection. However, the number of blocks in question is small compared with the total heap size and, in any case, the space in these blocks is available for use as to-space in subsequent GCs (we discuss fragmentation further in Section 5.6).

3.3 The Pending Block Set

The challenge is to represent the pending set ef\ufb01ciently, because operations on the pending set are in the inner loop of the collector. Cheney\u2019s original insight [Che70] was to allocate objects contigu- ously in to-space, so thatthe pending set is represented by to-space

itself; more precisely, the pending set is the set of objects between

the to-spaceallocation pointer and thescavenge pointer. As each object is copied into to-space, the to-space allocation pointer is in- cremented; as each object is scavenged the scavenge pointer is in- cremented. When the two coincide, the pending set is empty, and the algorithm terminates.

Cheney\u2019s neat trick sets the ef\ufb01ciency bar. We cannot rely on parallel performance making up for a signi\ufb01cant constant-factor slowdown: the parallelism available depends on the data structures and the heap, and some heaps may feature linear lists without any source of GC parallelism.

You're Reading a Free Preview

Download
scribd
/*********** DO NOT ALTER ANYTHING BELOW THIS LINE ! ************/ var s_code=s.t();if(s_code)document.write(s_code)//-->