OpenCores
URL https://opencores.org/ocsvn/openrisc/openrisc/trunk

Subversion Repositories openrisc

[/] [openrisc/] [trunk/] [gnu-dev/] [or1k-gcc/] [boehm-gc/] [doc/] [scale.html] - Blame information for rev 861

Go to most recent revision | Details | Compare with Previous | View Log

Line No. Rev Author Line
1 721 jeremybenn
<HTML>
2
<HEAD>
3
<TITLE>Garbage collector scalability</TITLE>
4
</HEAD>
5
<BODY>
6
<H1>Garbage collector scalability</h1>
7
In its default configuration, the Boehm-Demers-Weiser garbage collector
8
is not thread-safe.  It can be made thread-safe for a number of environments
9
by building the collector with the appropriate
10
<TT>-D</tt><I>XXX</i><TT>-THREADS</tt> compilation
11
flag.  This has primarily two effects:
12
<OL>
13
<LI> It causes the garbage collector to stop all other threads when
14
it needs to see a consistent memory state.
15
<LI> It causes the collector to acquire a lock around essentially all
16
allocation and garbage collection activity.
17
</ol>
18
Since a single lock is used for all allocation-related activity, only one
19
thread can be allocating or collecting at one point.  This inherently
20
limits performance of multi-threaded applications on multiprocessors.
21
<P>
22
On most platforms, the allocator/collector lock is implemented as a
23
spin lock with exponential back-off.  Longer wait times are implemented
24
by yielding and/or sleeping.  If a collection is in progress, the pure
25
spinning stage is skipped.  This has the advantage that uncontested and
26
thus most uniprocessor lock acquisitions are very cheap.  It has the
27
disadvantage that the application may sleep for small periods of time
28
even when there is work to be done.  And threads may be unnecessarily
29
woken up for short periods.  Nonetheless, this scheme empirically
30
outperforms native queue-based mutual exclusion implementations in most
31
cases, sometimes drastically so.
32
<H2>Options for enhanced scalability</h2>
33
Version 6.0 of the collector adds two facilities to enhance collector
34
scalability on multiprocessors.  As of 6.0alpha1, these are supported
35
only under Linux on X86 and IA64 processors, though ports to other
36
otherwise supported Pthreads platforms should be straightforward.
37
They are intended to be used together.
38
<UL>
39
<LI>
40
Building the collector with <TT>-DPARALLEL_MARK</tt> allows the collector to
41
run the mark phase in parallel in multiple threads, and thus on multiple
42
processors.  The mark phase typically consumes the large majority of the
43
collection time.  Thus this largely parallelizes the garbage collector
44
itself, though not the allocation process.  Currently the marking is
45
performed by the thread that triggered the collection, together with
46
<I>N</i>-1 dedicated
47
threads, where <I>N</i> is the number of processors detected by the collector.
48
The dedicated threads are created once at initialization time.
49
<P>
50
A second effect of this flag is to switch to a more concurrent
51
implementation of <TT>GC_malloc_many</tt>, so that free lists can be
52
built, and memory can be cleared, by more than one thread concurrently.
53
<LI>
54
Building the collector with -DTHREAD_LOCAL_ALLOC adds support for thread
55
local allocation.  It does not, by itself, cause thread local allocation
56
to be used.  It simply allows the use of the interface in
57
<TT>gc_local_alloc.h</tt>.
58
<P>
59
Memory returned from thread-local allocators is completely interchangeable
60
with that returned by the standard allocators.  It may be used by other
61
threads.  The only difference is that, if the thread allocates enough
62
memory of a certain kind, it will build a thread-local free list for
63
objects of that kind, and allocate from that.  This greatly reduces
64
locking.  The thread-local free lists are refilled using
65
<TT>GC_malloc_many</tt>.
66
<P>
67
An important side effect of this flag is to replace the default
68
spin-then-sleep lock to be replace by a spin-then-queue based implementation.
69
This <I>reduces performance</i> for the standard allocation functions,
70
though it usually improves performance when thread-local allocation is
71
used heavily, and thus the number of short-duration lock acquisitions
72
is greatly reduced.
73
</ul>
74
<P>
75
The easiest way to switch an application to thread-local allocation is to
76
<OL>
77
<LI> Define the macro <TT>GC_REDIRECT_TO_LOCAL</tt>,
78
and then include the <TT>gc.h</tt>
79
header in each client source file.
80
<LI> Invoke <TT>GC_thr_init()</tt> before any allocation.
81
<LI> Allocate using <TT>GC_MALLOC</tt>, <TT>GC_MALLOC_ATOMIC</tt>,
82
and/or <TT>GC_GCJ_MALLOC</tt>.
83
</ol>
84
<H2>The Parallel Marking Algorithm</h2>
85
We use an algorithm similar to
86
<A HREF="http://www.yl.is.s.u-tokyo.ac.jp/gc/">that developed by
87
Endo, Taura, and Yonezawa</a> at the University of Tokyo.
88
However, the data structures and implementation are different,
89
and represent a smaller change to the original collector source,
90
probably at the expense of extreme scalability.  Some of
91
the refinements they suggest, <I>e.g.</i> splitting large
92
objects, were also incorporated into out approach.
93
<P>
94
The global mark stack is transformed into a global work queue.
95
Unlike the usual case, it never shrinks during a mark phase.
96
The mark threads remove objects from the queue by copying them to a
97
local mark stack and changing the global descriptor to zero, indicating
98
that there is no more work to be done for this entry.
99
This removal
100
is done with no synchronization.  Thus it is possible for more than
101
one worker to remove the same entry, resulting in some work duplication.
102
<P>
103
The global work queue grows only if a marker thread decides to
104
return some of its local mark stack to the global one.  This
105
is done if the global queue appears to be running low, or if
106
the local stack is in danger of overflowing.  It does require
107
synchronization, but should be relatively rare.
108
<P>
109
The sequential marking code is reused to process local mark stacks.
110
Hence the amount of additional code required for parallel marking
111
is minimal.
112
<P>
113
It should be possible to use generational collection in the presence of the
114
parallel collector, by calling <TT>GC_enable_incremental()</tt>.
115
This does not result in fully incremental collection, since parallel mark
116
phases cannot currently be interrupted, and doing so may be too
117
expensive.
118
<P>
119
Gcj-style mark descriptors do not currently mix with the combination
120
of local allocation and incremental collection.  They should work correctly
121
with one or the other, but not both.
122
<P>
123
The number of marker threads is set on startup to the number of
124
available processors (or to the value of the <TT>GC_NPROCS</tt>
125
environment variable).  If only a single processor is detected,
126
parallel marking is disabled.
127
<P>
128
Note that setting GC_NPROCS to 1 also causes some lock acquisitions inside
129
the collector to immediately yield the processor instead of busy waiting
130
first.  In the case of a multiprocessor and a client with multiple
131
simultaneously runnable threads, this may have disastrous performance
132
consequences (e.g. a factor of 10 slowdown).
133
<H2>Performance</h2>
134
We conducted some simple experiments with a version of
135
<A HREF="gc_bench.html">our GC benchmark</a> that was slightly modified to
136
run multiple concurrent client threads in the same address space.
137
Each client thread does the same work as the original benchmark, but they share
138
a heap.
139
This benchmark involves very little work outside of memory allocation.
140
This was run with GC 6.0alpha3 on a dual processor Pentium III/500 machine
141
under Linux 2.2.12.
142
<P>
143
Running with a thread-unsafe collector,  the benchmark ran in 9
144
seconds.  With the simple thread-safe collector,
145
built with <TT>-DLINUX_THREADS</tt>, the execution time
146
increased to 10.3 seconds, or 23.5 elapsed seconds with two clients.
147
(The times for the <TT>malloc</tt>/i<TT>free</tt> version
148
with glibc <TT>malloc</tt>
149
are 10.51 (standard library, pthreads not linked),
150
20.90 (one thread, pthreads linked),
151
and 24.55 seconds respectively. The benchmark favors a
152
garbage collector, since most objects are small.)
153
<P>
154
The following table gives execution times for the collector built
155
with parallel marking and thread-local allocation support
156
(<TT>-DGC_LINUX_THREADS -DPARALLEL_MARK -DTHREAD_LOCAL_ALLOC</tt>).  We tested
157
the client using either one or two marker threads, and running
158
one or two client threads.  Note that the client uses thread local
159
allocation exclusively.  With -DTHREAD_LOCAL_ALLOC the collector
160
switches to a locking strategy that is better tuned to less frequent
161
lock acquisition.  The standard allocation primitives thus peform
162
slightly worse than without -DTHREAD_LOCAL_ALLOC, and should be
163
avoided in time-critical code.
164
<P>
165
(The results using <TT>pthread_mutex_lock</tt>
166
directly for allocation locking would have been worse still, at
167
least for older versions of linuxthreads.
168
With THREAD_LOCAL_ALLOC, we first repeatedly try to acquire the
169
lock with pthread_mutex_try_lock(), busy_waiting between attempts.
170
After a fixed number of attempts, we use pthread_mutex_lock().)
171
<P>
172
These measurements do not use incremental collection, nor was prefetching
173
enabled in the marker.  We used the C version of the benchmark.
174
All measurements are in elapsed seconds on an unloaded machine.
175
<P>
176
<TABLE BORDER ALIGN="CENTER">
177
<TR><TH>Number of threads</th><TH>1 marker thread (secs.)</th>
178
<TH>2 marker threads (secs.)</th></tr>
179
<TR><TD>1 client</td><TD ALIGN="CENTER">10.45</td><TD ALIGN="CENTER">7.85</td>
180
<TR><TD>2 clients</td><TD ALIGN="CENTER">19.95</td><TD ALIGN="CENTER">12.3</td>
181
</table>
182
<PP>
183
The execution time for the single threaded case is slightly worse than with
184
simple locking.  However, even the single-threaded benchmark runs faster than
185
even the thread-unsafe version if a second processor is available.
186
The execution time for two clients with thread local allocation time is
187
only 1.4 times the sequential execution time for a single thread in a
188
thread-unsafe environment, even though it involves twice the client work.
189
That represents close to a
190
factor of 2 improvement over the 2 client case with the old collector.
191
The old collector clearly
192
still suffered from some contention overhead, in spite of the fact that the
193
locking scheme had been fairly well tuned.
194
<P>
195
Full linear speedup (i.e. the same execution time for 1 client on one
196
processor as 2 clients on 2 processors)
197
is probably not achievable on this kind of
198
hardware even with such a small number of processors,
199
since the memory system is
200
a major constraint for the garbage collector,
201
the processors usually share a single memory bus, and thus
202
the aggregate memory bandwidth does not increase in
203
proportion to the number of processors.
204
<P>
205
These results are likely to be very sensitive to both hardware and OS
206
issues.  Preliminary experiments with an older Pentium Pro machine running
207
an older kernel were far less encouraging.
208
 
209
</body>
210
</html>

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.