mirror of
git://git.sv.gnu.org/emacs.git
synced 2026-04-19 20:40:41 -07:00
New chapter of the guide discusses the "stretchy vector" problem.
Copied from Perforce Change: 186305 ServerID: perforce.ravenbrook.com
This commit is contained in:
parent
060394b167
commit
e9dddf19de
4 changed files with 176 additions and 2 deletions
|
|
@ -515,6 +515,7 @@ Memory Management Glossary
|
|||
:term:`storage management <memory management>`
|
||||
:term:`store (1)`
|
||||
:term:`store (2) <memory (1)>`
|
||||
:term:`stretchy vector`
|
||||
:term:`strict segregated fit`
|
||||
:term:`strong reference`
|
||||
:term:`strong root`
|
||||
|
|
|
|||
|
|
@ -785,6 +785,26 @@ Memory Management Glossary: S
|
|||
|
||||
.. see:: :term:`memory (1)`.
|
||||
|
||||
stretchy vector
|
||||
|
||||
A data structure consisting of a vector that may grow or
|
||||
shrink to accommodate adding or removing elements. Named after
|
||||
the ``<stretchy-vector>`` abstract class in Dylan).
|
||||
|
||||
.. relevance::
|
||||
|
||||
In the presence of an :term:`asynchronous garbage
|
||||
collector`, the vector and its size may need to be updated
|
||||
atomically.
|
||||
|
||||
.. link::
|
||||
|
||||
`Dylan Reference Manual: Collections <http://opendylan.org/books/drm/Collection_Classes>`_.
|
||||
|
||||
.. mps:specific::
|
||||
|
||||
See :ref:`guide-stretchy-vector`.
|
||||
|
||||
strict segregated fit
|
||||
|
||||
A :term:`segregated fit` :term:`allocation mechanism` which
|
||||
|
|
@ -806,7 +826,7 @@ Memory Management Glossary: S
|
|||
collection>`, a strong reference is a :term:`reference` that
|
||||
keeps the :term:`object` it refers to :term:`alive <live>`.
|
||||
|
||||
A strong reference is the usual sort of reference; The term is
|
||||
A strong reference is the usual sort of reference: the term is
|
||||
usually used to draw a contrast with :term:`weak reference
|
||||
(1)`.
|
||||
|
||||
|
|
@ -819,7 +839,7 @@ Memory Management Glossary: S
|
|||
A strong root is a :term:`root` such that all
|
||||
:term:`references` in it are :term:`strong references`.
|
||||
|
||||
A strong root is the usual sort of root. The term is usually
|
||||
A strong root is the usual sort of root: the term is usually
|
||||
used to draw a contrast with :term:`weak root`.
|
||||
|
||||
.. opposite:: :term:`weak root`.
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ Guide
|
|||
overview
|
||||
build
|
||||
lang
|
||||
vector
|
||||
debug
|
||||
perf
|
||||
advanced
|
||||
|
|
|
|||
152
mps/manual/source/guide/vector.rst
Normal file
152
mps/manual/source/guide/vector.rst
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
.. index::
|
||||
single: stretchy vectors
|
||||
single: atomic updates
|
||||
|
||||
.. _guide-stretchy-vector:
|
||||
|
||||
The stretchy vector problem
|
||||
============================
|
||||
|
||||
The :ref:`previous chapter <guide-lang-root>` pointed out that:
|
||||
|
||||
Because the MPS is :term:`asynchronous <asynchronous garbage
|
||||
collector>`, it might be scanning, moving, or collecting, at any
|
||||
point in time.
|
||||
|
||||
The consequences of this can take a while to sink in, so this chapter
|
||||
discusses a particular instance that catches people out: the *stretchy
|
||||
vector* problem (named after the |stretchy-vector|_ abstract class in
|
||||
Dylan).
|
||||
|
||||
.. |stretchy-vector| replace:: ``<stretchy-vector>``
|
||||
.. _stretchy-vector: http://opendylan.org/books/drm/Collection_Classes#stretchy-vector
|
||||
|
||||
A *stretchy vector* is a vector that can change length dynamically.
|
||||
Such a vector is often implemented using two objects: an array, and a
|
||||
header object that stores the length and a pointer to an array.
|
||||
Stretching (or shrinking) such a vector involves five steps:
|
||||
|
||||
1. allocate a new array;
|
||||
2. copy elements from the old array to the new array;
|
||||
3. clear unused elements in the new array (if stretching);
|
||||
4. update the pointer to the array in the header;
|
||||
5. update the length in the header.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
typedef struct vector_s {
|
||||
type_t type; /* TYPE_VECTOR */
|
||||
size_t length; /* number of elements */
|
||||
obj_t *array; /* array of elements */
|
||||
} vector_s, *vector_t;
|
||||
|
||||
void resize_vector(vector_t vector, size_t new_length) {
|
||||
obj_t *new_array = realloc(vector->array, new_length * sizeof(obj_t));
|
||||
if (new_array == NULL)
|
||||
error("out of memory in resize_vector");
|
||||
if (vector->length < new_length) {
|
||||
memset(&vector->array[vector->length], 0,
|
||||
(new_length - vector->length) * sizeof(obj_t));
|
||||
}
|
||||
vector->array = new_array;
|
||||
vector->length = new_length;
|
||||
}
|
||||
|
||||
When adapting this code to the MPS, the following problems must be
|
||||
solved:
|
||||
|
||||
1. During step 2, the new array must be :term:`reachable` from the
|
||||
roots, and :term:`scannable <scan>`. (If it's not reachable, then
|
||||
it may be collected; if it's not scannable, then references it
|
||||
contains will not be updated when they are moved by the collector.)
|
||||
|
||||
This can solved by storing the new array in a :term:`root` until
|
||||
the header has been updated. If the thread's stack has been
|
||||
registered as a root by calling :c:func:`mps_root_create_reg` then
|
||||
any local variable will do.
|
||||
|
||||
2. References in the new array must not be scanned until they have been
|
||||
copied or cleared. (Otherwise they will be invalid.)
|
||||
|
||||
This can be solved by clearing the new array before calling
|
||||
:c:func:`mps_commit`.
|
||||
|
||||
3. The old array must be scanned at the old length (otherwise the scan
|
||||
may run off the end of the old array when the vector grows), and
|
||||
the new array must be scanned at the new length (otherwise the scan
|
||||
may run off the end of the old array when the vector shrinks).
|
||||
|
||||
4. The array object must be scannable without referring to the header
|
||||
object. (Because the header object may have been protected by the
|
||||
MPS: see :ref:`topic-format-cautions`.)
|
||||
|
||||
Problems 3 and 4 can be solved by storing the length in the array. The
|
||||
revised data structures and resizing code might look like this:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
typedef struct vector_s {
|
||||
type_t type; /* TYPE_VECTOR */
|
||||
obj_t array; /* TYPE_ARRAY object */
|
||||
} vector_s, *vector_t;
|
||||
|
||||
typedef struct array_s {
|
||||
type_t type; /* TYPE_ARRAY */
|
||||
size_t length; /* number of elements */
|
||||
obj_t array[0]; /* array of elements */
|
||||
} array_s, *array_t;
|
||||
|
||||
void resize_vector(vector_t vector, size_t new_length) {
|
||||
size_t size = ALIGN_OBJ(offsetof(array_s, array) + new_length * sizeof(obj_t));
|
||||
mps_addr_t addr;
|
||||
array_t array;
|
||||
|
||||
do {
|
||||
mps_res_t res = mps_reserve(&addr, ap, size);
|
||||
if (res != MPS_RES_OK) error("out of memory in resize_vector");
|
||||
array = addr;
|
||||
array->type = TYPE_ARRAY;
|
||||
array->length = new_length;
|
||||
memset(array->array, 0, new_length * sizeof(obj_t));
|
||||
/* Now the new array is scannable, and it is reachable via the
|
||||
* local variable 'array', so it is safe to commit it. */
|
||||
} while(!mps_commit(ap, addr, size));
|
||||
|
||||
/* Copy elements after committing, so that the collector will
|
||||
* update them if they move. */
|
||||
memcpy(array->array, vector->array->array,
|
||||
min(vector->array->length, new_length) * sizeof(obj_t));
|
||||
vector->array = array;
|
||||
}
|
||||
|
||||
Similar difficulties can arise even when adapting code written for
|
||||
other garbage collectors. For example, here's the function
|
||||
|setarrayvector|_ from Lua_:
|
||||
|
||||
.. |setarrayvector| replace:: ``setarrayvector()``
|
||||
.. _setarrayvector: http://www.lua.org/source/5.2/ltable.c.html#setarrayvector
|
||||
.. _Lua: http://www.lua.org
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
static void setarrayvector (lua_State *L, Table *t, int size) {
|
||||
int i;
|
||||
luaM_reallocvector(L, t->array, t->sizearray, size, TValue);
|
||||
for (i=t->sizearray; i<size; i++)
|
||||
setnilvalue(&t->array[i]);
|
||||
t->sizearray = size;
|
||||
}
|
||||
|
||||
Lua's garbage collector is :term:`synchronous <synchronous garbage
|
||||
collector>`, so it can be assumed that there cannot be a garbage
|
||||
collection between the assignment to ``t->array`` (resulting from the
|
||||
expansion of the |luaM_reallocvector|_ macro) and the assignment to
|
||||
``t->sizearray``, and so the collector will always consistently see
|
||||
either the old array or the new array, with the correct size. This
|
||||
assumption will no longer be correct if this code is adapted to the
|
||||
MPS.
|
||||
|
||||
.. |luaM_reallocvector| replace:: ``luaM_reallocvector()``
|
||||
.. _luaM_reallocvector: http://www.lua.org/source/5.2/lmem.h.html#luaM_reallocvector
|
||||
Loading…
Add table
Add a link
Reference in a new issue