mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-12 20:34:19 +00:00
This is a problem for the following reasons:
The work stealing code must take a lock to access the resizeable array of
work stealing dequeues. This adds global contention that can be avoided if
this array has a fixed size.
If a context is blocked on a future then that engine cannot execute the
sparks from that context, instead it tries to find global work, this is
more expensive than necessary.
If there are a few dozen contexts then there may be just as many work
stealing queues to take work from, the density of these queues will be
higher if they are fewer. Therefore work stealing will be more successful
on average.
This change associates spark deques with Mercury Engines rather than Contexts
to avoid these problems.
This has invalidated some invariants that allowed the runtime system to make
some worth-while optimisations. These optimisations have been maintained.
Mercury's idle loop has been reimplemented to allow for this. This
re-implementation has allowed for a number of other improvements:
Polling was used to check for new global sparks. This has been removed and
each engine now sleeps using it's own semaphore.
Checks for work can be done in different orders depending on how an engine
joins the idle loop.
When global work becomes available a particular engine can be woken up
rather than any arbitrary engine. We take advantage of this when making
contexts runnable, we try to schedule them on the engine that last executed
them.
When an engine is woken up it can be instructed with what it should do upon
waking up.
When a engine looks for a context to run, it will try to pick a context
that was last executed on it. This may avoid cache misses when the context
begins to run.
In the future we should consider:
Experiment with telling engines which context to run.
Improve the selection of which engine work should be scheduled on to be
hardware and memory-hierarchy aware.
Things that need doing next (probably next week):
./configure should check for POSIX semaphore support.
Profiling times have been broken by this change, they will need fixing.
The threadscope event long now breaks an invariants that the threadscope
graphical tool requires.
Semaphores are setup but never released, this is not a big problem but the
manual page says that some implementations may leak resources.
runtime/mercury_context.h:
runtime/mercury_context.c:
Remove the spark deque field from the MR_Context structure.
Export the new array of spark deques so that other modules may fill in
elements as engines are setup.
Modify the resume_owner_thread field of the MR_Context structure, this was
used to ensure that a context returning through C code would be resumed on
the engine with the correct C stack and depth. This field is now an engine
id and has been renamed to resume_owner_engine, it is advisory unless
resume_engine_required is also set. This way it is used to advise which
engine most recently executed this context and therefore may have a warm
cache.
Remove code that dynamically resized the array of spark deques. Including
the lock that protected against updating this array while it was being read
from other thread.
Introduce code that initialises the statically sized array of spark deques.
Reimplement the idle loop. This replaces MR_runnext and MR_do_runnext with
MR_idle and MR_do_idle respectively. There are also two new entry points
into the idle loop. Which one to use depends on the state of the engine.
Introduce new mechanisms for waking a particular engine. For example the
engine that last executed a context that is now runnable.
Change the algorithm for selecting which context to run, try to select
contexts that where last used on the current engine to avoid cache misses.
Use an engine's victim counter rather than a global victim counter when
trying to steal work.
Introduce some conditionally-compiled code that can be used to profile how
quickly new contexts can be created.
Rename MR_init_thread_stuff and MR_finalize_thread_stuff. The term thread
has been replaced with context since they're in mercury_context.c. This
allows the creation of a new function MR_init_thread_stuff() in
mercury_thread.c I also found the mismatch between the function names and
file name confusing. Move some of the code from MR_init_context_stuff to
the new MR_init_thread_stuff function where it belongs.
Refactor the thread pinning code so that even when thread pinning is
disabled it can be used to allocate each thread to a CPU but not actually
pin them.
Fix some whitespace errors.
runtime/mercury_thread.h:
runtime/mercury_thread.c:
In MR_init_engine():
Allocate an engine id for each engine.
A number of arrays had one slot per engine and where setup using a
lock. Now engine ids are used to index each array and setup is done
without a lock, each engine simply sets up its own slot.
Setup the new per-engine work stealing deques.
The MR_all_engine_bases array has been moved to this file.
Implement a new MR_init_thread_stuff function which initialises some global
variables and locks. Some of MR_init_thread_stuff has been moved from
mercury_context.c
Pin threads as part of MR_init_thread, excluding the primordial thread
which must be pinned before threadscope is initialised.
Add functions for debugging the use of semaphores.
Add corresponding macros that can be used to redirect semaphore calls to
debugging functions as above.
Improved thread debugging code, ensured that stderr is flushed after every
use, and that logging is done after calls return as well as before they're
called.
Conform to changes in mercury_context.h
runtime/mercury_engine.h:
runtime/mercury_engine.c:
Add spark deque and victim counter fields to the MercuryEngine structure.
Make the MR_eng_id field of the MercuryEngine structure available in all
thread safe grades, formerly it was used in only threadscope grades.
Move the MR_all_engine_bases variable to mercury_thread.[ch]
Put a reference to the engine's spark queue into the global array. This is
done here, so that it is after thread pinning because the original plan was
to have this array sorted by CPU rather then engine - we may yet do this in
the future.
Initialise an engine's spark deque when an engine is initialised.
Setup the engine specific threadscope data in mercury_thread.c
Conform to changes in mercury_context.h
runtime/mercury_wrapper.c:
The engine base array is no longer setup here, that code has been moved to
mercury_thread.c
Conform to changes in mercury_context.h and mercury_thread.h
runtime/mercury_wsdeque.h:
runtime/mercury_wsdeque.c:
The original implementation allocated an array for a spark queue only if
one wasn't already allocated, which could happen when a context was reused.
Now that spark queues are associated with engines arrays are always
allocated.
Replaced two macros with a single macro since there's no-longer a
distinction between global and local work queues, all work queues are
local.
runtime/mercury_wsdeque.c:
runtime/mercury_wsdeque.h:
Remove the --worksteal-max-attempts and --worksteal-sleep-msecs options as
they are no-longer used.
runtime/mercury_threadscope.h:
runtime/mercury_threadscope.c:
The MR_EngineId type has been moved to mercury_types.h
Engine IDs are no-longer allocated here, this is done in mercury_thread.c
The run spark and steal spark messages now write 0xFFFFFFFF for the context
id if there is no current context. Previously this would dereference a
null pointer.
runtime/mercury_memory_zones.c:
When checking for an existing memory zone check the free_zones_list
variable before taking a lock. This can prevent taking the lock in cases
where there are no free zones.
Introduce some conditionally-compiled code that can be used to profile how
quickly new contexts can be created.
runtime/mercury_bootstrap.h:
Remove macros that no-longer resolve to functions due to changes in the
runtime system.
runtime/mercury_types.h:
Move the MR_EngineId type from mercury_threadscope.h to mercury_types.h
runtime/mercury_grade.h:
Introduce a parallel grade version number, this change brakes binary
compatibility with existing parallel code.
runtime/mercury_backjump.c:
runtime/mercury_par_builtin.c:
runtime/mercury_mm_own_stacks.c:
library/stm_builtin.m:
library/thread.m:
library/thread.semaphore.m:
Conform to changes in mercury_context.h.
library/io.m:
Make this module compatible with MR_debug_threads.
doc/user_guide.texi
Remove the documentation for the --worksteal-max-attempts and
--worksteal-sleep-msecs options. The documentation was already commented
out.
183 lines
4.9 KiB
C
183 lines
4.9 KiB
C
/*
|
|
** vim: ts=4 sw=4 expandtab
|
|
*/
|
|
/*
|
|
** Copyright (C) 2007, 2009-2011 The University of Melbourne.
|
|
** This file may only be copied under the terms of the GNU Library General
|
|
** Public License - see the file COPYING.LIB in the Mercury distribution.
|
|
*/
|
|
|
|
#ifndef MERCURY_WSDEQUE_H
|
|
#define MERCURY_WSDEQUE_H
|
|
|
|
#ifdef MR_LL_PARALLEL_CONJ
|
|
|
|
#include "mercury_atomic_ops.h"
|
|
|
|
/* XXX should experiment with this, perhaps it should be configurable. */
|
|
#define MR_INITIAL_SPARK_DEQUE_SIZE 8
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/* See mercury_context.h for the definition of MR_SparkDeque. */
|
|
|
|
struct MR_SparkArray_Struct {
|
|
MR_Integer MR_sa_max; /* power of two - 1 */
|
|
volatile MR_Spark MR_sa_segment[1]; /* really MR_sa_max + 1 */
|
|
};
|
|
|
|
/*
|
|
** MR_sa_element(Array, Pos)
|
|
** Index into Array modulo its size, i.e. treating it as a circular array.
|
|
**
|
|
** MR_sa_max is a power of two - 1 so that we can use a bitwise AND operation
|
|
** operation instead of modulo when indexing into the array, which makes a
|
|
** significant difference.
|
|
*/
|
|
#define MR_sa_element(arr, pos) \
|
|
((arr)->MR_sa_segment[(pos) & (arr)->MR_sa_max])
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/*
|
|
** Initialise a spark deque. A new circular array underlying the deque will
|
|
** only be allocated if deque->MR_sd_active_array is NULL, otherwise only the
|
|
** indices into the array will be reset. `size' must be a power of two.
|
|
*/
|
|
extern void MR_init_wsdeque(MR_SparkDeque *dq, MR_Integer size);
|
|
|
|
/*
|
|
** Return true if the deque is empty.
|
|
*/
|
|
extern MR_bool MR_wsdeque_is_empty(const MR_SparkDeque *dq);
|
|
|
|
/*
|
|
** Push a spark on the bottom of the deque. Must only be called by the owner
|
|
** of the deque. The deque may grow as necessary.
|
|
*/
|
|
MR_INLINE void
|
|
MR_wsdeque_push_bottom(MR_SparkDeque *dq, const MR_Spark *spark);
|
|
|
|
/*
|
|
** Same as MR_wsdeque_push_bottom but assume that there is enough space
|
|
** in the deque. Should only be called after a successful pop.
|
|
*/
|
|
extern void MR_wsdeque_putback_bottom(MR_SparkDeque *dq,
|
|
const MR_Spark *spark);
|
|
|
|
/*
|
|
** Pop a spark off the bottom of the deque. Must only be called by the owner
|
|
** of the deque. The pointer returned here can be used until the next call to
|
|
** a MR_wsdeque function, at which point it's memory may have been overwritten.
|
|
*/
|
|
MR_INLINE volatile MR_Spark*
|
|
MR_wsdeque_pop_bottom(MR_SparkDeque *dq);
|
|
|
|
/*
|
|
** Attempt to steal a spark from the top of the deque.
|
|
**
|
|
** Returns:
|
|
** 1 on success,
|
|
** 0 if the deque is empty or
|
|
** -1 if the steal was aborted due to a concurrent steal or pop_bottom.
|
|
*/
|
|
extern int
|
|
MR_wsdeque_steal_top(MR_SparkDeque *dq, MR_Spark *ret_spark);
|
|
|
|
/*
|
|
** Take a spark from the top of the deque, assuming there are no concurrent
|
|
** operations on the deque. Returns true on success.
|
|
*/
|
|
extern int MR_wsdeque_take_top(MR_SparkDeque *dq, MR_Spark *ret_spark);
|
|
|
|
/*
|
|
** Return a new circular array with double the capacity of the old array.
|
|
** The valid elements of the old array are copied to the new array.
|
|
*/
|
|
extern MR_SparkArray * MR_grow_spark_array(const MR_SparkArray *old_arr,
|
|
MR_Integer bot, MR_Integer top);
|
|
|
|
/*
|
|
** Return the current length of the dequeue.
|
|
**
|
|
** This is safe from the owner's perspective.
|
|
*/
|
|
MR_INLINE int
|
|
MR_wsdeque_length(MR_SparkDeque *dq);
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
MR_INLINE void
|
|
MR_wsdeque_push_bottom(MR_SparkDeque *dq, const MR_Spark *spark)
|
|
{
|
|
MR_Integer bot;
|
|
MR_Integer top;
|
|
volatile MR_SparkArray *arr;
|
|
MR_Integer size;
|
|
|
|
bot = dq->MR_sd_bottom;
|
|
top = dq->MR_sd_top;
|
|
arr = dq->MR_sd_active_array;
|
|
size = bot - top;
|
|
|
|
if (size >= arr->MR_sa_max) {
|
|
arr = MR_grow_spark_array((MR_SparkArray *) arr, bot, top);
|
|
dq->MR_sd_active_array = arr;
|
|
}
|
|
|
|
MR_sa_element(arr, bot) = *spark;
|
|
dq->MR_sd_bottom = bot + 1;
|
|
}
|
|
|
|
MR_INLINE volatile MR_Spark*
|
|
MR_wsdeque_pop_bottom(MR_SparkDeque *dq)
|
|
{
|
|
MR_Integer bot;
|
|
MR_Integer top;
|
|
MR_Integer size;
|
|
volatile MR_SparkArray *arr;
|
|
MR_bool success;
|
|
volatile MR_Spark *spark;
|
|
|
|
bot = dq->MR_sd_bottom;
|
|
arr = dq->MR_sd_active_array;
|
|
bot--;
|
|
dq->MR_sd_bottom = bot;
|
|
|
|
top = dq->MR_sd_top;
|
|
size = bot - top;
|
|
|
|
if (size < 0) {
|
|
dq->MR_sd_bottom = top;
|
|
return NULL;
|
|
}
|
|
|
|
spark = &MR_sa_element(arr, bot);
|
|
if (size > 0) {
|
|
return spark;
|
|
}
|
|
|
|
/* size = 0 */
|
|
success = MR_compare_and_swap_int(&dq->MR_sd_top, top, top + 1);
|
|
dq->MR_sd_bottom = top + 1;
|
|
return success ? spark : NULL;
|
|
}
|
|
|
|
MR_INLINE int
|
|
MR_wsdeque_length(MR_SparkDeque *dq)
|
|
{
|
|
int length;
|
|
int top;
|
|
int bot;
|
|
|
|
top = dq->MR_sd_top;
|
|
bot = dq->MR_sd_bottom;
|
|
length = bot - top;
|
|
|
|
return length;
|
|
}
|
|
|
|
#endif /* !MR_LL_PARALLEL_CONJ */
|
|
|
|
#endif /* !MERCURY_WSDEQUE_H */
|