Skip to content

Commit 68f74a5

Browse files
committed
reduce contention on page metadata lists during the sweeping phase
1 parent fb2d946 commit 68f74a5

File tree

4 files changed

+140
-12
lines changed

4 files changed

+140
-12
lines changed

src/gc.c

Lines changed: 97 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1541,7 +1541,8 @@ static void gc_sweep_page(gc_page_profiler_serializer_t *s, jl_gc_pool_t *p, jl_
15411541

15421542
done:
15431543
if (re_use_page) {
1544-
push_lf_back(allocd, pg);
1544+
// we're pushing into a local page stack to reduce contention
1545+
push_lf_back_nosync(allocd, pg);
15451546
}
15461547
else {
15471548
gc_alloc_map_set(pg->data, GC_PAGE_LAZILY_FREED);
@@ -1596,8 +1597,49 @@ static void gc_pool_sync_nfree(jl_gc_pagemeta_t *pg, jl_taggedvalue_t *last) JL_
15961597
pg->nfree = nfree;
15971598
}
15981599

1600+
int gc_sweep_prescan(void)
1601+
{
1602+
int n_pages_to_scan = 0;
1603+
// 4MB worth of pages is worth parallelizing
1604+
const int n_pages_worth_parallel_sweep = (int)(4 * (1 << 20) / GC_PAGE_SZ);
1605+
for (int t_i = 0; t_i < gc_n_threads; t_i++) {
1606+
jl_ptls_t ptls2 = gc_all_tls_states[t_i];
1607+
if (ptls2 == NULL) {
1608+
continue;
1609+
}
1610+
jl_gc_pagemeta_t *pg = jl_atomic_load_relaxed(&ptls2->page_metadata_allocd.bottom);
1611+
while (pg != NULL) {
1612+
int should_scan = 1;
1613+
if (!pg->has_marked) {
1614+
should_scan = 0;
1615+
}
1616+
if (!current_sweep_full && !pg->has_young) {
1617+
assert(!prev_sweep_full || pg->prev_nold >= pg->nold);
1618+
if (!prev_sweep_full || pg->prev_nold == pg->nold) {
1619+
should_scan = 0;
1620+
}
1621+
}
1622+
if (should_scan) {
1623+
n_pages_to_scan++;
1624+
}
1625+
if (n_pages_to_scan >= n_pages_worth_parallel_sweep) {
1626+
break;
1627+
}
1628+
pg = pg->next;
1629+
}
1630+
if (n_pages_to_scan >= n_pages_worth_parallel_sweep) {
1631+
break;
1632+
}
1633+
}
1634+
return n_pages_to_scan >= n_pages_worth_parallel_sweep;
1635+
}
1636+
15991637
void gc_sweep_wake_all(void)
16001638
{
1639+
int parallel_sweep_worthwhile = gc_sweep_prescan();
1640+
if (!parallel_sweep_worthwhile) {
1641+
return;
1642+
}
16011643
uv_mutex_lock(&gc_threads_lock);
16021644
for (int i = gc_first_tid; i < gc_first_tid + jl_n_markthreads; i++) {
16031645
jl_ptls_t ptls2 = gc_all_tls_states[i];
@@ -1615,30 +1657,52 @@ void gc_sweep_wait_for_all(void)
16151657
}
16161658
}
16171659

1618-
void gc_sweep_pool_parallel(void)
1660+
void gc_sweep_pool_parallel(jl_ptls_t ptls)
16191661
{
16201662
jl_atomic_fetch_add(&gc_n_threads_sweeping, 1);
16211663
jl_gc_page_stack_t *allocd_scratch = jl_atomic_load(&gc_allocd_scratch);
16221664
if (allocd_scratch != NULL) {
1665+
// push into local page stack to reduce contention
1666+
// we'll merge them later...
1667+
jl_gc_page_stack_t *dest = &allocd_scratch[ptls->tid];
16231668
gc_page_profiler_serializer_t serializer = gc_page_serializer_create();
16241669
while (1) {
16251670
int found_pg = 0;
1671+
// sequentially walk the threads and sweep the pages
16261672
for (int t_i = 0; t_i < gc_n_threads; t_i++) {
16271673
jl_ptls_t ptls2 = gc_all_tls_states[t_i];
1674+
// skip foreign threads that already exited
16281675
if (ptls2 == NULL) {
16291676
continue;
16301677
}
1631-
jl_gc_page_stack_t *allocd = &allocd_scratch[t_i];
1632-
jl_gc_pagemeta_t *pg = pop_lf_back(&ptls2->page_metadata_allocd);
1678+
jl_gc_pagemeta_t *pg = try_pop_lf_back(&ptls2->page_metadata_allocd);
1679+
// failed steal attempt
16331680
if (pg == NULL) {
16341681
continue;
16351682
}
1636-
gc_sweep_pool_page(&serializer, allocd, &ptls2->page_metadata_buffered, pg);
1683+
gc_sweep_pool_page(&serializer, dest, &ptls2->page_metadata_buffered, pg);
16371684
found_pg = 1;
16381685
}
16391686
if (!found_pg) {
1640-
break;
1687+
// check for termination
1688+
int no_more_work = 1;
1689+
for (int t_i = 0; t_i < gc_n_threads; t_i++) {
1690+
jl_ptls_t ptls2 = gc_all_tls_states[t_i];
1691+
// skip foreign threads that already exited
1692+
if (ptls2 == NULL) {
1693+
continue;
1694+
}
1695+
jl_gc_pagemeta_t *pg = jl_atomic_load_relaxed(&ptls2->page_metadata_allocd.bottom);
1696+
if (pg != NULL) {
1697+
no_more_work = 0;
1698+
break;
1699+
}
1700+
}
1701+
if (no_more_work) {
1702+
break;
1703+
}
16411704
}
1705+
jl_cpu_pause();
16421706
}
16431707
gc_page_serializer_destroy(&serializer);
16441708
}
@@ -1669,7 +1733,7 @@ static void gc_sweep_pool(void)
16691733

16701734
// allocate enough space to hold the end of the free list chain
16711735
// for every thread and pool size
1672-
jl_taggedvalue_t ***pfl = (jl_taggedvalue_t ***) alloca(n_threads * JL_GC_N_POOLS * sizeof(jl_taggedvalue_t**));
1736+
jl_taggedvalue_t ***pfl = (jl_taggedvalue_t ***) malloc_s(n_threads * JL_GC_N_POOLS * sizeof(jl_taggedvalue_t**));
16731737

16741738
// update metadata of pages that were pointed to by freelist or newpages from a pool
16751739
// i.e. pages being the current allocation target
@@ -1711,17 +1775,36 @@ static void gc_sweep_pool(void)
17111775
}
17121776

17131777
// the actual sweeping
1714-
jl_gc_page_stack_t *tmp = (jl_gc_page_stack_t *)alloca(n_threads * sizeof(jl_gc_page_stack_t));
1778+
jl_gc_page_stack_t *tmp = (jl_gc_page_stack_t *)jl_malloc_aligned(n_threads * sizeof(jl_gc_page_stack_t), 128);
1779+
if (tmp == NULL) {
1780+
abort();
1781+
}
17151782
memset(tmp, 0, n_threads * sizeof(jl_gc_page_stack_t));
17161783
jl_atomic_store(&gc_allocd_scratch, tmp);
17171784
gc_sweep_wake_all();
1718-
gc_sweep_pool_parallel();
1785+
gc_sweep_pool_parallel(jl_current_task->ptls);
17191786
gc_sweep_wait_for_all();
17201787

1788+
// merge the page metadata lists
1789+
for (int t_i = 0; t_i < n_threads; t_i++) {
1790+
jl_ptls_t ptls2 = gc_all_tls_states[t_i];
1791+
if (ptls2 == NULL) {
1792+
continue;
1793+
}
1794+
while (1) {
1795+
jl_gc_pagemeta_t *pg = pop_lf_back_nosync(&tmp[t_i]);
1796+
if (pg == NULL) {
1797+
break;
1798+
}
1799+
jl_ptls_t ptls3 = gc_all_tls_states[pg->thread_n];
1800+
push_lf_back_nosync(&ptls3->page_metadata_allocd, pg);
1801+
}
1802+
}
1803+
1804+
// reset half-pages pointers
17211805
for (int t_i = 0; t_i < n_threads; t_i++) {
17221806
jl_ptls_t ptls2 = gc_all_tls_states[t_i];
17231807
if (ptls2 != NULL) {
1724-
ptls2->page_metadata_allocd = tmp[t_i];
17251808
for (int i = 0; i < JL_GC_N_POOLS; i++) {
17261809
jl_gc_pool_t *p = &ptls2->heap.norm_pools[i];
17271810
p->newpages = NULL;
@@ -1759,6 +1842,10 @@ static void gc_sweep_pool(void)
17591842
}
17601843
}
17611844

1845+
// cleanup
1846+
free(pfl);
1847+
free(tmp);
1848+
17621849
#ifdef _P64 // only enable concurrent sweeping on 64bit
17631850
// wake thread up to sweep concurrently
17641851
if (jl_n_sweepthreads > 0) {

src/gc.h

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,24 @@ extern jl_gc_page_stack_t global_page_pool_freed;
199199
// in the sweeping phase, which also doesn't push a node into the
200200
// same stack after it's popped
201201

202+
STATIC_INLINE void push_lf_back_nosync(jl_gc_page_stack_t *pool, jl_gc_pagemeta_t *elt) JL_NOTSAFEPOINT
203+
{
204+
jl_gc_pagemeta_t *old_back = jl_atomic_load_relaxed(&pool->bottom);
205+
elt->next = old_back;
206+
jl_atomic_store_relaxed(&pool->bottom, elt);
207+
}
208+
209+
STATIC_INLINE jl_gc_pagemeta_t *pop_lf_back_nosync(jl_gc_page_stack_t *pool) JL_NOTSAFEPOINT
210+
{
211+
jl_gc_pagemeta_t *old_back = jl_atomic_load_relaxed(&pool->bottom);
212+
if (old_back == NULL) {
213+
return NULL;
214+
}
215+
jl_gc_pagemeta_t *new_back = old_back->next;
216+
jl_atomic_store_relaxed(&pool->bottom, new_back);
217+
return old_back;
218+
}
219+
202220
STATIC_INLINE void push_lf_back(jl_gc_page_stack_t *pool, jl_gc_pagemeta_t *elt) JL_NOTSAFEPOINT
203221
{
204222
while (1) {
@@ -211,6 +229,23 @@ STATIC_INLINE void push_lf_back(jl_gc_page_stack_t *pool, jl_gc_pagemeta_t *elt)
211229
}
212230
}
213231

232+
#define MAX_POP_ATTEMPTS (1 << 10)
233+
234+
STATIC_INLINE jl_gc_pagemeta_t *try_pop_lf_back(jl_gc_page_stack_t *pool) JL_NOTSAFEPOINT
235+
{
236+
for (int i = 0; i < MAX_POP_ATTEMPTS; i++) {
237+
jl_gc_pagemeta_t *old_back = jl_atomic_load_relaxed(&pool->bottom);
238+
if (old_back == NULL) {
239+
return NULL;
240+
}
241+
if (jl_atomic_cmpswap(&pool->bottom, &old_back, old_back->next)) {
242+
return old_back;
243+
}
244+
jl_cpu_pause();
245+
}
246+
return NULL;
247+
}
248+
214249
STATIC_INLINE jl_gc_pagemeta_t *pop_lf_back(jl_gc_page_stack_t *pool) JL_NOTSAFEPOINT
215250
{
216251
while (1) {
@@ -473,7 +508,7 @@ void gc_mark_finlist(jl_gc_markqueue_t *mq, arraylist_t *list, size_t start) JL_
473508
void gc_mark_loop_serial_(jl_ptls_t ptls, jl_gc_markqueue_t *mq);
474509
void gc_mark_loop_serial(jl_ptls_t ptls);
475510
void gc_mark_loop_parallel(jl_ptls_t ptls, int master);
476-
void gc_sweep_pool_parallel(void);
511+
void gc_sweep_pool_parallel(jl_ptls_t ptls);
477512
void gc_free_pages(void);
478513
void sweep_stack_pools(void);
479514
void jl_gc_debug_init(void);

src/julia_threads.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,12 @@ struct _jl_gc_pagemeta_t;
197197

198198
typedef struct {
199199
_Atomic(struct _jl_gc_pagemeta_t *) bottom;
200+
// pad to 128 bytes to avoid false-sharing
201+
#ifdef _P64
202+
void *_pad[15];
203+
#else
204+
void *_pad[31];
205+
#endif
200206
} jl_gc_page_stack_t;
201207

202208
// This includes all the thread local states we care about for a thread.

src/scheduler.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ void jl_parallel_gc_threadfun(void *arg)
147147
gc_mark_loop_parallel(ptls, 0);
148148
}
149149
if (may_sweep(ptls)) { // not an else!
150-
gc_sweep_pool_parallel();
150+
gc_sweep_pool_parallel(ptls);
151151
jl_atomic_fetch_add(&ptls->gc_sweeps_requested, -1);
152152
}
153153
}

0 commit comments

Comments
 (0)