Navigation

This module provides TripleDict and MonoDict. These are
structures similar to WeakKeyDictionary in Python’s weakref
module, and are optimized for lookup speed. The keys for TripleDict
consist of triples (k1,k2,k3) and are looked up by identity rather than
equality. The keys are stored by weakrefs if possible. If any one of the
components k1, k2, k3 gets garbage collected, then the entry is removed from
the TripleDict.

Key components that do not allow for weakrefs are stored via a normal
refcounted reference. That means that any entry stored using a triple
(k1,k2,k3) so that none of the k1,k2,k3 allows a weak reference behaves
as an entry in a normal dictionary: Its existence in TripleDict
prevents it from being garbage collected.

That container currently is used to store coercion and conversion maps between
two parents (trac ticket #715) and to store homsets of pairs of objects of a
category (trac ticket #11521). In both cases, it is essential that the parent
structures remain garbage collectable, it is essential that the data access is
faster than with a usual WeakKeyDictionary, and we enforce
the “unique parent condition” in Sage (parent structures should be identical
if they are equal).

MonoDict behaves similarly, but it takes a single item as a key. It
is used for caching the parents which allow a coercion map into a fixed other
parent (trac ticket #12313).

This is a hashtable specifically designed for (read) speed in
the coercion model.

It differs from a python WeakKeyDictionary in the following important ways:

Comparison is done using the ‘is’ rather than ‘==’ operator.

Only weak references to the keys are stored if at all possible.
Keys that do not allow for weak references are stored with a normal
refcounted reference.

The callback of the weak references is safe against recursion, see below.

There are special cdef set/get methods for faster access.
It is bare-bones in the sense that not all dictionary methods are
implemented.

IMPLEMENTATION:

It is implemented as a hash table with open addressing, similar to python’s
dict.

If ki supports weak references then ri is a weak reference to ki with a
callback to remove the entry from the dictionary if ki gets garbage
collected. If ki is does not support weak references then ri is identical to ki.
In the latter case the presence of the key in the dictionary prevents it from
being garbage collected.

INPUT:

size – unused parameter, present for backward compatibility.

data – optional iterable defining initial data.

threshold – unused parameter, present for backward compatibility.

weak_values – optional bool (default False). If it is true, weak references
to the values in this dictionary will be used, when possible.

Note that this kind of dictionary is also used for caching actions
and coerce maps. In previous versions of Sage, the cache was by
strong references and resulted in a memory leak in the following
example. However, this leak was fixed by trac ticket #715, using
weak references:

This is a hashtable specifically designed for (read) speed in
the coercion model.

It differs from a python dict in the following important ways:

All keys must be sequence of exactly three elements. All sequence
types (tuple, list, etc.) map to the same item.

Comparison is done using the ‘is’ rather than ‘==’ operator.

There are special cdef set/get methods for faster access.
It is bare-bones in the sense that not all dictionary methods are
implemented.

It is implemented as a list of lists (hereafter called buckets). The bucket is
chosen according to a very simple hash based on the object pointer, and each
bucket is of the form [id(k1), id(k2), id(k3), r1, r2, r3, value, id(k1),
id(k2), id(k3), r1, r2, r3, value, ...], on which a linear search is performed.
If a key component ki supports weak references then ri is a weak reference to
ki; otherwise ri is identical to ki.

INPUT:

size – an integer, the initial number of buckets. To spread objects
evenly, the size should ideally be a prime, and certainly not divisible
by 2.

weak_values – optional bool (default False). If it is true, weak references
to the values in this dictionary will be used, when possible.

If any of the key components k1,k2,k3 (this can happen for a key component
that supports weak references) gets garbage collected then the entire
entry disappears. In that sense this structure behaves like a nested
WeakKeyDictionary.

Note that this kind of dictionary is also used for caching actions
and coerce maps. In previous versions of Sage, the cache was by
strong references and resulted in a memory leak in the following
example. However, this leak was fixed by trac ticket #715, using weak
references:

Now, T holds a strong reference to a, namely in T[k,k,k]. Hence,
when we delete a, all items of T survive:

sage: delasage: _=gc.collect()sage: len(T)4

Only when we remove the strong reference, the items become collectable:

sage: delT[k,k,k]sage: _=gc.collect()sage: len(T)0

The situation is different for TW, since it only holds weak
references to a. Therefore, all items become collectable after
deleting a:

sage: delbsage: _=gc.collect()sage: len(TW)0

Note

The index \(h\) corresponding to the key [k1, k2, k3] is computed as a
value of unsigned type size_t as follows:

\[h = id(k1) + 13*id(k2) xor 503 id(k3)\]

The natural type for this quantity is Py_ssize_t, which is a signed
quantity with the same length as size_t. Storing it in a signed way gives the most
efficient storage into PyInt, while preserving sign information.

In previous situations there were some problems with ending up with negative
indices, which required casting to an unsigned type, i.e.,
(<size_t> h)% N
since C has a sign-preserving % operation This caused problems on 32 bits systems,
see trac ticket #715 for details. This is irrelevant for the current implementation.