Ticket #715: trac715_weak_coercion_cache.patch

File trac715_weak_coercion_cache.patch, 12.2 KB (added by SimonKing, 8 years ago)

Use weak references in the coercion cache

  • sage/rings/polynomial/polynomial_ring_constructor.py

    # HG changeset patch
    # User Simon King <simon.king@uni-jena.de>
    # Date 1324507434 -3600
    # Node ID fe449548c332ff96864b32da0fe6dd1bb285a947
    # Parent  8883449f439a6a078ec685b24f0ddd5b9323fb31
    #715: Use weak references for caching coerce maps and polynomial rings.
    
    diff --git a/sage/rings/polynomial/polynomial_ring_constructor.py b/sage/rings/polynomial/polynomial_ring_constructor.py
    a b  
    5353from sage.categories.commutative_rings import CommutativeRings
    5454_CommutativeRings = CommutativeRings()
    5555
    56 _cache = {}
     56import weakref
     57_cache = weakref.WeakValueDictionary()
    5758
    5859def PolynomialRing(base_ring, arg1=None, arg2=None,
    5960                   sparse=False, order='degrevlex',
  • sage/structure/coerce_dict.pxd

    diff --git a/sage/structure/coerce_dict.pxd b/sage/structure/coerce_dict.pxd
    a b  
     1cdef class TripleDictEraser
     2
    13cdef class TripleDict:
    24    cdef Py_ssize_t _size
    3     cdef buckets
     5    cdef list buckets
    46    cdef double threshold
     7    cdef TripleDictEraser eraser
    58    cdef get(self, k1, k2, k3)
    69    cdef set(self, k1, k2, k3, value)
    710   
    811cdef class TripleDictIter:
    912    cdef TripleDict pairs
    1013    cdef buckets, bucket_iter
     14
     15cdef class TripleDictEraser:
     16    cdef TripleDict D
  • sage/structure/coerce_dict.pyx

    diff --git a/sage/structure/coerce_dict.pyx b/sage/structure/coerce_dict.pyx
    a b  
    11#*****************************************************************************
    22#       Copyright (C) 2007 Robert Bradshaw <robertwb@math.washington.edu>
     3#                     2011 Simon King <simon.king@uni-jena.de>
    34#
    45#  Distributed under the terms of the GNU General Public License (GPL)
    56#
     
    910
    1011include "../ext/python_list.pxi"
    1112
     13from sage.misc.constant_function import ConstantFunction
     14
     15############################################
     16# The following code is responsible for
     17# removing dead references from the cache
     18############################################
     19
     20cdef class TripleDictEraser:
     21    """
     22    Erases items from a :class:`TripleDict` when a weak reference becomes invalid.
     23
     24    This is of internal use only. Instances of this class will be passed as a callback
     25    function when creating a weak reference.
     26
     27    EXAMPLES::
     28
     29        sage: from sage.structure.coerce_dict import TripleDict
     30        sage: class A: pass
     31        sage: a = A()
     32        sage: T = TripleDict(11)
     33        sage: T[a,ZZ,None] = 1
     34        sage: T[ZZ,a,1] = 2
     35        sage: T[a,a,ZZ] = 3
     36        sage: len(T)
     37        3
     38        sage: del a
     39        sage: import gc
     40        sage: n = gc.collect()
     41        sage: len(T)
     42        0
     43
     44    AUTHOR:
     45
     46    Simon King (2011-12)
     47    """
     48
     49    def __init__(self, D):
     50        """
     51        INPUT:
     52
     53        A :class:`TripleDict`.
     54
     55        EXAMPLES::
     56
     57            sage: from sage.structure.coerce_dict import TripleDict, TripleDictEraser
     58            sage: D = TripleDict(11)
     59            sage: TripleDictEraser(D)
     60            <sage.structure.coerce_dict.TripleDictEraser object at ...>
     61
     62        """
     63        self.D = D
     64    def __call__(self, r):
     65        """
     66        INPUT:
     67
     68        A weak reference
     69
     70        When this is called with a weak reference ``r``, then each item containing ``r``
     71        is removed from the associated :class:`TripleDict`. Normally, this only happens
     72        when a weak reference becomes invalid.
     73
     74        EXAMPLES::
     75
     76            sage: from sage.structure.coerce_dict import TripleDict
     77            sage: class A: pass
     78            sage: a = A()
     79            sage: T = TripleDict(11)
     80            sage: T[a,ZZ,None] = 1
     81            sage: T[ZZ,a,1] = 2
     82            sage: T[a,a,ZZ] = 3
     83            sage: len(T)
     84            3
     85            sage: del a
     86            sage: import gc
     87            sage: n = gc.collect()
     88            sage: len(T)    # indirect doctest
     89            0
     90
     91        """
     92        # r is a (weak) reference (typically to a parent),
     93        # and we remove that parent from the cache self.D
     94        # Each weak reference can only occur once in the list.
     95        cdef list bucket
     96        for bucket in self.D.buckets:
     97            for i from 0 <= i < PyList_GET_SIZE(bucket) by 4:
     98                if bucket[i] is r or bucket[i+1] is r:
     99                    del bucket[i:i+4]
     100                    self.D._size -= 1
     101                    return
     102
     103from weakref import ref
    12104
    13105cdef class TripleDict:
    14106    """
     
    20112       - All keys must be sequence of exactly three elements. All sequence
    21113         types (tuple, list, etc.) map to the same item.
    22114       - Comparison is done using the 'is' rather than '==' operator.
    23        
     115
     116    By trac ticket #715, it uses weak references on the first two
     117    items of the key, if they are weakly referenceable. Otherwise, a
     118    :class:`~sage.misc.constant_function.ConstantFunction` is used.
     119    In addition, the items of the key will not be tested for equality
     120    (`==`) but for identity (`is`).
     121
    24122    There are special cdef set/get methods for faster access.
    25123    It is bare-bones in the sense that not all dictionary methods are
    26124    implemented.
     
    34132    not divisible by 2.
    35133   
    36134   
    37     EXAMPLES: 
     135    EXAMPLES::
    38136   
    39137        sage: from sage.structure.coerce_dict import TripleDict
    40138        sage: L = TripleDict(31)
     
    83181        ...
    84182        KeyError: 'a'
    85183       
    86     The following illustrates why even sizes are bad.
     184    The following illustrates why even sizes are bad.
     185    ::
     186
    87187        sage: L = TripleDict(4, L)
    88188        sage: L.stats()
    89189        (0, 250.25, 1001)
    90190        sage: L.bucket_lens()
    91191        [1001, 0, 0, 0]
    92192
     193    Here, we show that weak references are indeed in use::
    93194
    94     AUTHOR:
    95        -- Robert Bradshaw, 2007-08
     195        sage: class A: pass
     196        sage: a = A()
     197        sage: T = TripleDict(11)
     198        sage: T[a,ZZ,None] = 1
     199        sage: T[ZZ,a,1] = 2
     200        sage: T[a,a,ZZ] = 3
     201        sage: len(T)
     202        3
     203        sage: del a
     204        sage: import gc
     205        sage: n = gc.collect()
     206        sage: len(T)
     207        0
     208
     209    We encourage to use objects for the first two constituents of the key
     210    that allow for weak references. However, other objects still work.
     211    Note that the keys are tested for identity, not for equality::
     212
     213        sage: T[1,1,1] = 1
     214        sage: T[1,1,1]
     215        Traceback (most recent call last):
     216        ...
     217        KeyError: (1, 1, 1)
     218        sage: x = 1
     219        sage: T[x,x,x] = 1
     220        sage: T[x,x,x]
     221        1
     222
     223    AUTHORS:
     224
     225    - Robert Bradshaw, 2007-08
     226    - Simon King, 2011-12
    96227    """
    97228   
    98229    def __init__(self, size, data=None, threshold=0):
    99230        """
    100231        Create a special dict using triples for keys.
    101232       
    102         EXAMPLES:
     233        EXAMPLES::
     234
    103235            sage: from sage.structure.coerce_dict import TripleDict
    104236            sage: L = TripleDict(31)
    105237            sage: a = 'a'; b = 'b'; c = 'c'
     
    111243        self.threshold = threshold
    112244        self.buckets = [[] for i from 0 <= i <  size]
    113245        self._size = 0
     246        self.eraser = TripleDictEraser(self)
    114247        if data is not None:
    115248            for k, v in data.iteritems():
    116249                self[k] = v
     
    119252        """
    120253        The number of items in self.
    121254       
    122         EXAMPLES:
     255        EXAMPLES::
     256
    123257            sage: from sage.structure.coerce_dict import TripleDict
    124258            sage: L = TripleDict(37)
    125259            sage: a = 'a'; b = 'b'; c = 'c'
     
    136270        """
    137271        The distribution of items in buckets.
    138272       
    139         OUTPUT:
    140             (min, avg, max)
     273        OUTPUT:
     274
     275        `(min, avg, max)`
    141276       
    142         EXAMPLES:
     277        EXAMPLES::
     278
    143279            sage: from sage.structure.coerce_dict import TripleDict
    144280            sage: L = TripleDict(37)
    145281            sage: for i in range(100): L[i,i,i] = None
     
    171307        """
    172308        The distribution of items in buckets.
    173309       
    174         OUTPUT:
    175             A list of how many items are in each bucket.
     310        OUTPUT:
     311
     312        A list of how many items are in each bucket.
    176313       
    177         EXAMPLES:
     314        EXAMPLES::
     315
    178316            sage: from sage.structure.coerce_dict import TripleDict
    179317            sage: L = TripleDict(37)
    180318            sage: for i in range(100): L[i,i,i] = None
     
    194332        """
    195333        The actual buckets of self, for debugging.
    196334       
    197         EXAMPLE:
     335        EXAMPLES::
     336
    198337            sage: from sage.structure.coerce_dict import TripleDict
    199338            sage: L = TripleDict(3)
    200339            sage: L[0,0,0] = None
     
    205344       
    206345    def __getitem__(self, k):
    207346        """
    208         EXAMPLES:
     347        EXAMPLES::
     348
    209349            sage: from sage.structure.coerce_dict import TripleDict
    210350            sage: L = TripleDict(31)
    211351            sage: a = 'a'; b = 'b'; c = 'c'
     
    223363        cdef Py_ssize_t h = (<Py_ssize_t><void *>k1 + 13*<Py_ssize_t><void *>k2 ^ 503*<Py_ssize_t><void *>k3)
    224364        if h < 0: h = -h
    225365        cdef Py_ssize_t i
    226         bucket = <object>PyList_GET_ITEM(self.buckets, h % PyList_GET_SIZE(self.buckets))
     366        cdef list bucket = self.buckets[h % PyList_GET_SIZE(self.buckets)]
    227367        for i from 0 <= i < PyList_GET_SIZE(bucket) by 4:
    228             if PyList_GET_ITEM(bucket, i) == <PyObject*>k1 and \
    229                PyList_GET_ITEM(bucket, i+1) == <PyObject*>k2 and \
    230                PyList_GET_ITEM(bucket, i+2) == <PyObject*>k3:
    231                 return <object>PyList_GET_ITEM(bucket, i+3)
     368            if bucket[i]() is k1 and \
     369               bucket[i+1]() is k2 and \
     370               bucket[i+2] is k3:
     371                return bucket[i+3]
    232372        raise KeyError, (k1, k2, k3)
    233373       
    234374    def __setitem__(self, k, value):
    235375        """
    236         EXAMPLES:
     376        EXAMPLES::
     377
    237378            sage: from sage.structure.coerce_dict import TripleDict
    238379            sage: L = TripleDict(31)
    239380            sage: a = 'a'; b = 'b'; c = 'c'
     
    253394        cdef Py_ssize_t h = (<Py_ssize_t><void *>k1 + 13*<Py_ssize_t><void *>k2 ^ 503*<Py_ssize_t><void *>k3)
    254395        if h < 0: h = -h
    255396        cdef Py_ssize_t i
    256         bucket = <object>PyList_GET_ITEM(self.buckets, h % PyList_GET_SIZE(self.buckets))
     397        cdef list bucket = self.buckets[h % PyList_GET_SIZE(self.buckets)]
    257398        for i from 0 <= i < PyList_GET_SIZE(bucket) by 4:
    258             if PyList_GET_ITEM(bucket, i) == <PyObject*>k1 and \
    259                PyList_GET_ITEM(bucket, i+1) == <PyObject*>k2 and \
    260                PyList_GET_ITEM(bucket, i+2) == <PyObject*>k3:
     399            if bucket[i]() is k1 and \
     400               bucket[i+1]() is k2 and \
     401               bucket[i+2] is k3:
    261402                bucket[i+3] = value
    262403                return
    263         bucket += [k1, k2, k3, value]
     404        try:
     405            r1 = ref(k1,self.eraser)
     406        except TypeError:
     407            r1 = ConstantFunction(k1)
     408        try:
     409            r2 = ref(k2,self.eraser)
     410        except TypeError:
     411            r2 = ConstantFunction(k2)
     412        bucket += [r1, r2, k3, value]
    264413        self._size += 1
    265414           
    266415    def __delitem__(self, k):
     
    281430        cdef Py_ssize_t h = (<Py_ssize_t><void *>k1 + 13*<Py_ssize_t><void *>k2 ^ 503*<Py_ssize_t><void *>k3)
    282431        if h < 0: h = -h
    283432        cdef Py_ssize_t i
    284         bucket = <object>PyList_GET_ITEM(self.buckets, h % PyList_GET_SIZE(self.buckets))
     433        cdef list bucket = self.buckets[h % PyList_GET_SIZE(self.buckets)]
    285434        for i from 0 <= i < PyList_GET_SIZE(bucket) by 4:
    286             if PyList_GET_ITEM(bucket, i) == <PyObject*>k1 and \
    287                PyList_GET_ITEM(bucket, i+1) == <PyObject*>k2 and \
    288                PyList_GET_ITEM(bucket, i+2) == <PyObject*>k3:
     435            if bucket[i]() is k1 and \
     436               bucket[i+1]() is k2 and \
     437               bucket[i+2] is k3:
    289438                del bucket[i:i+4]
    290439                self._size -= 1
    291440                return
     
    383532            k2 = self.bucket_iter.next()
    384533            k3 = self.bucket_iter.next()
    385534            value = self.bucket_iter.next()
    386             return ((k1, k2, k3), value)
     535            return ((k1(), k2(), k3), value)
    387536        except StopIteration:
    388537            self.bucket_iter = None
    389538            return self.next()