def _add(self, key): mm = self._p_mm khash = fixed_hash(key) result = ADD_RESULT_RESTART with mm.transaction(): while result == ADD_RESULT_RESTART: index, result = self._get_available_entry_slot(key, khash) if result == ADD_RESULT_FOUND_UNUSED or \ result == ADD_RESULT_FOUND_DUMMY: table_data = ffi.cast('PSetEntry *', mm.direct(self._body.table)) mm.snapshot_range(ffi.addressof(table_data, index), ffi.sizeof('PSetEntry')) oid = mm.persist(key) mm.incref(oid) p_obj = ffi.cast('PObject *', mm.direct(oid)) table_data[index].key = oid table_data[index].hash = khash mm.snapshot_range( ffi.addressof(self._body, 'fill'), ffi.sizeof('PSetObject') - ffi.sizeof('PObject')) self._body.used += 1 if result == ADD_RESULT_FOUND_UNUSED: self._body.fill += 1 if self._body.fill * 3 >= self._body.mask * 2: self._table_resize(self._body.used)
def _insertion_resize(self): # This is modeled on CPython's insertion_resize/dictresize, but # assuming we always have a combined dict. We copy the keys and values # into a new dict structure and free the old one. We don't touch the # refcounts. mm = self._p_mm minused = self._growth_rate() newsize = MIN_SIZE_COMBINED while newsize <= minused and newsize > 0: newsize = newsize << 1 oldkeys = self._keys oldkeys_oid = mm.otuple(self._body.ma_keys) with mm.transaction(): mm.snapshot_range(ffi.addressof(self._body, 'ma_keys'), ffi.sizeof('PObjPtr')) self._body.ma_keys = self._new_keys_object(newsize) oldsize = oldkeys.dk_size old_ep0 = ffi.cast('PDictKeyEntry *', ffi.addressof(oldkeys.dk_entries[0])) for i in range(oldsize): old_ep = old_ep0[i] me_value = mm.otuple(old_ep.me_value) if me_value != mm.OID_NULL: me_key = mm.otuple(old_ep.me_key) assert me_key != DUMMY me_hash = old_ep.me_hash new_ep = self._find_empty_slot(me_key, me_hash) new_ep.me_key = me_key new_ep.me_hash = me_hash new_ep.me_value = me_value self._keys.dk_usable -= self._body.ma_used mm.free(oldkeys_oid)
def _find_empty_slot(self, key, khash): # Find slot from hash when key is not in dict. mm = self._p_mm keys = self._keys mask = keys.dk_size - 1 ep0 = ffi.cast('PDictKeyEntry *', ffi.addressof(keys.dk_entries[0])) i = khash & mask ep = ffi.addressof(ep0[i]) perturb = khash while mm.otuple(ep.me_key) != mm.OID_NULL: i = (i << 2) + i + perturb + 1 ep = ep0[i & mask] perturb = perturb >> PERTURB_SHIFT assert mm.otuple(ep.me_key) == mm.OID_NULL return ep
def _resize(self, newsize): # Note that resize does *not* set self._size. That needs to be done by # the caller such that that the we never expose invalid item cells. # The size field is covered by a snapshot done here, though. mm = self._p_mm allocated = self._allocated # Only realloc if we don't have enough space already. if (allocated >= newsize and newsize >= allocated >> 1): assert self._items != None or newsize == 0 with mm.transaction(): ob = ffi.cast('PVarObject *', self._body) mm.snapshot_range(ffi.addressof(ob, 'ob_size'), ffi.sizeof('size_t')) ob.ob_size = newsize return # We use CPython's overallocation algorithm. new_allocated = (newsize >> 3) + (3 if newsize < 9 else 6) + newsize if newsize == 0: new_allocated = 0 items = self._items with mm.transaction(): if items is None: items = mm.zalloc(new_allocated * ffi.sizeof('PObjPtr'), type_num=LIST_POBJPTR_ARRAY_TYPE_NUM) else: items = mm.zrealloc(self._body.ob_items, new_allocated * ffi.sizeof('PObjPtr'), LIST_POBJPTR_ARRAY_TYPE_NUM) mm.snapshot_range(self._body, ffi.sizeof('PListObject')) self._body.ob_items = items self._body.allocated = new_allocated
def _resize(self, newsize): mm = self.__manager__ allocated = self._allocated # Only realloc if we don't have enough space already. if (allocated >= newsize and newsize >= allocated >> 1): assert self._items != None or newsize == 0 with mm.transaction(): ob = ffi.cast('PVarObject *', self._body) mm.snapshot_range(ffi.addressof(ob, 'ob_size'), ffi.sizeof('size_t')) ob.ob_size = newsize return # We use CPython's overallocation algorithm. new_allocated = (newsize >> 3) + (3 if newsize < 9 else 6) + newsize if newsize == 0: new_allocated = 0 items = self._items with mm.transaction(): if items is None: items = mm.malloc(new_allocated * ffi.sizeof('PObjPtr'), type_num=LIST_POBJPTR_ARRAY_TYPE_NUM) else: items = mm.realloc(self._body.ob_items, new_allocated * ffi.sizeof('PObjPtr'), LIST_POBJPTR_ARRAY_TYPE_NUM) mm.snapshot_range(self._body, ffi.sizeof('PListObject')) self._body.ob_items = items self._body.allocated = new_allocated ffi.cast('PVarObject *', self._body).ob_size = newsize
def _insert_clean(self, table, mask, key_oid, khash): mm = self._p_mm perturb = khash i = khash & mask table_data = ffi.cast('PSetEntry *', mm.direct(table)) found_index = -1 while True: if table_data[i].hash == HASH_UNUSED: found_index = i break for j in range(i + 1, min(i + LINEAR_PROBES, mask) + 1): if table_data[j].hash == HASH_UNUSED: found_index = j break if found_index != -1: break perturb >>= PERTURB_SHIFT i = (i * 5 + 1 + perturb) & mask with mm.transaction(): mm.snapshot_range(ffi.addressof(table_data, found_index), ffi.sizeof('PSetEntry')) table_data[found_index].hash = khash table_data[found_index].key = key_oid
def _free_keys_object(self, oid): mm = self._p_mm dk = ffi.cast('PDictKeysObject *', mm.direct(oid)) ep = ffi.cast('PDictKeyEntry *', ffi.addressof(dk.dk_entries[0])) with mm.transaction(): for i in range(dk.dk_size): mm.xdecref(ep[i].me_key) mm.xdecref(ep[i].me_value) mm.free(oid)
def root(self, value): log.debug("setting 'root' to %r (discarding %s)", value, self.root) with self.mm.transaction(), self.lock: oid = self.mm.persist(value) self.mm.snapshot_range(ffi.addressof(self._pmem_root.root_object), ffi.sizeof('PObjPtr')) self.mm.xdecref(self._pmem_root.root_object) self._pmem_root.root_object = oid self.mm.incref(oid)
def _lookdict(self, key, khash): # Generalized key lookup method. mm = self._p_mm while True: keys_oid = mm.otuple(self._body.ma_keys) keys = ffi.cast('PDictKeysObject *', mm.direct(keys_oid)) mask = keys.dk_size - 1 ep0 = ffi.cast('PDictKeyEntry *', ffi.addressof(keys.dk_entries[0])) i = khash & mask ep = ffi.addressof(ep0[i]) me_key = mm.otuple(ep.me_key) if me_key == mm.OID_NULL: return ep if me_key == DUMMY: freeslot = ep else: if ep.me_hash == khash: match = mm.resurrect(me_key) == key # dict could mutate if (mm.otuple(self._body.ma_keys) == keys_oid and mm.otuple(ep.me_key) == me_key): if match: return ep else: continue # mutatation, start over from the top. freeslot = None perturb = khash while True: i = (i << 2) + i + perturb + 1 ep = ep0[i & mask] me_key = mm.otuple(ep.me_key) if me_key == mm.OID_NULL: return ep if freeslot is None else freeslot if ep.me_hash == khash and me_key != DUMMY: match = mm.resurrect(me_key) == key # dict could mutate if (mm.otuple(self._body.ma_keys) == keys_oid and mm.otuple(ep.me_key) == me_key): if match: return ep else: break # mutation, start over from the top. elif me_key == DUMMY and freeslot is None: freeslot = ep perturb = perturb >> PERTURB_SHIFT
def __iter__(self): mm = self._p_mm keys = self._keys ep0 = ffi.cast('PDictKeyEntry *', ffi.addressof(keys.dk_entries[0])) for i in range(keys.dk_size): ep = ep0[i] if (ep.me_hash == ffi.NULL or mm.otuple(ep.me_key) in (mm.OID_NULL, DUMMY)): continue yield mm.resurrect(ep.me_key)
def root(self, value): log.debug("setting 'root' to %r (discarding %s)", value, self.root) with self.mm.transaction(), self.lock: oid = self.mm.persist(value) self.mm.snapshot_range( ffi.addressof(self._pmem_root.root_object), ffi.sizeof('PObjPtr')) self.mm.xdecref(self._pmem_root.root_object) self._pmem_root.root_object = oid self.mm.incref(oid)
def _dumpdict(self): # This is for debugging. mm = self._p_mm keys = self._keys ep0 = ffi.cast('PDictKeyEntry *', ffi.addressof(keys.dk_entries[0])) log.debug('size: %s', keys.dk_size) for i in range(keys.dk_size): ep = ep0[i] log.debug('hash: %s, key oid: %s, value oid: %s', ep.me_hash, mm.otuple(ep.me_key), mm.otuple(ep.me_value))
def __setitem__(self, index, value): mm = self._p_mm index = self._normalize_index(index) items = self._items with mm.transaction(): v_oid = mm.persist(value) mm.snapshot_range(ffi.addressof(items, index), ffi.sizeof('PObjPtr *')) mm.xdecref(items[index]) items[index] = v_oid mm.incref(v_oid)
def incref(self, oid): """Increment the reference count of oid.""" oid = self.otuple(oid) if oid == OID_NULL: # Unlike CPython, we don't ref-track our constants. return p_obj = ffi.cast('PObject *', self.direct(oid)) log.debug('incref %r %r', oid, p_obj.ob_refcnt + 1) with self.transaction(): self.snapshot_range(ffi.addressof(p_obj, 'ob_refcnt'), ffi.sizeof('size_t')) p_obj.ob_refcnt += 1
def __init__(self, *args, **kw): if not args: return if len(args) != 1: raise TypeError("PersistentTuple takes at most 1" " argument, {} given".format(len(args))) item_count = len(args[0]) mm = self._p_mm with mm.transaction(): mm.snapshot_range(ffi.addressof(self._body, 'ob_items'), ffi.sizeof('PObjPtr')) self._body.ob_items = mm.zalloc( item_count * ffi.sizeof('PObjPtr'), type_num=TUPLE_POBJPTR_ARRAY_TYPE_NUM) ob = ffi.cast('PVarObject *', self._body) mm.snapshot_range(ffi.addressof(ob, 'ob_size'), ffi.sizeof('size_t')) ob.ob_size = item_count for index, value in enumerate(args[0]): super(self.__class__, self).__setitem__(index, value)
def _discard(self, key): mm = self._p_mm with mm.transaction(): keyindex = self._lookkey(key, fixed_hash(key)) if keyindex != -1: table_data = ffi.cast('PSetEntry *', mm.direct(self._body.table)) mm.snapshot_range(ffi.addressof(table_data, keyindex), ffi.sizeof('PSetEntry')) mm.decref(table_data[keyindex].key) table_data[keyindex].key = mm.OID_NULL table_data[keyindex].hash = HASH_DUMMY self._body.used -= 1
def __delitem__(self, index): mm = self.__manager__ index = self._normalize_index(index) size = self._size newsize = size - 1 items = self._items with mm.transaction(): mm.snapshot_range(ffi.addressof(items, index), ffi.offsetof('PObjPtr *', size)) mm.decref(items[index]) for i in range(index, newsize): items[i] = items[i+1] self._resize(newsize)
def decref(self, oid): """Decrement the reference count of oid, and free it if zero.""" oid = self.otuple(oid) p_obj = ffi.cast('PObject *', self.direct(oid)) log.debug('decref %r %r', oid, p_obj.ob_refcnt - 1) with self.transaction(): self.snapshot_range(ffi.addressof(p_obj, 'ob_refcnt'), ffi.sizeof('size_t')) assert p_obj.ob_refcnt > 0, "{} oid refcount {}".format( oid, p_obj.ob_refcnt) p_obj.ob_refcnt -= 1 if p_obj.ob_refcnt < 1: self._deallocate(oid)
def incref(self, oid): """Increment the reference count of oid if it is not a singleton""" oid = self.otuple(oid) assert oid != self.OID_NULL if not oid[0]: # Unlike CPython, we don't ref-track our constants. log.debug('not increfing %s', oid) return p_obj = ffi.cast('PObject *', self.direct(oid)) log.debug('incref %r %r', oid, p_obj.ob_refcnt + 1) with self.transaction(): self.snapshot_range(ffi.addressof(p_obj, 'ob_refcnt'), ffi.sizeof('size_t')) p_obj.ob_refcnt += 1
def decref(self, oid): """Decrement the reference count of oid, and free it if zero.""" oid = self.otuple(oid) if not oid[0]: # Unlike CPython we do not ref-track our constants. log.debug('not decrefing %s', oid) return p_obj = ffi.cast('PObject *', self.direct(oid)) log.debug('decref %r %r', oid, p_obj.ob_refcnt - 1) with self.transaction(): self.snapshot_range(ffi.addressof(p_obj, 'ob_refcnt'), ffi.sizeof('size_t')) assert p_obj.ob_refcnt > 0, "{} oid refcount {}".format( oid, p_obj.ob_refcnt) p_obj.ob_refcnt -= 1 if p_obj.ob_refcnt < 1: self._deallocate(oid)
def __delitem__(self, index): mm = self._p_mm index = self._normalize_index(index) size = self._size newsize = size - 1 items = self._items with mm.transaction(): ffi.cast('PVarObject *', self._body).ob_size = newsize # We can't completely hide the process of transformation...this # really needs a lock (or translation to GIL-locked C). mm.snapshot_range(ffi.addressof(items, index), ffi.offsetof('PObjPtr *', size)) oid = mm.otuple(items[index]) for i in range(index, newsize): items[i] = items[i+1] mm.decref(oid) self._resize(newsize)
def __delitem__(self, index): mm = self._p_mm index = self._normalize_index(index) size = self._size newsize = size - 1 items = self._items with mm.transaction(): ffi.cast('PVarObject *', self._body).ob_size = newsize # We can't completely hide the process of transformation...this # really needs a lock (or translation to GIL-locked C). mm.snapshot_range(ffi.addressof(items, index), ffi.offsetof('PObjPtr *', size)) oid = mm.otuple(items[index]) for i in range(index, newsize): items[i] = items[i + 1] mm.decref(oid) self._resize(newsize)
def _table_resize(self, minused): mm = self._p_mm if minused > 50000: minused = (minused << 1) else: minused = (minused << 2) newsize = PERM_SET_MINSIZE while(newsize <= minused): newsize = (newsize << 1) newsize = ffi.cast('size_t', newsize) if newsize == 0: raise MemoryError("Out of memory") newsize = int(newsize) with mm.transaction(): oldtable = mm.otuple(self._body.table) oldtable_data = ffi.cast('PSetEntry *', mm.direct(oldtable)) newtable = self._alloc_empty_table(newsize) newmask = newsize - 1 for i in range(0, self._body.mask + 1): if oldtable_data[i].hash == HASH_UNUSED or \ oldtable_data[i].hash == HASH_DUMMY: continue self._insert_clean(newtable, newmask, oldtable_data[i].key, oldtable_data[i].hash) mm.snapshot_range(ffi.addressof(self._body, 'fill'), ffi.sizeof('PSetObject') - ffi.sizeof('PObject')) self._body.mask = newmask self._body.fill = self._body.used self._body.table = newtable mm.free(oldtable)
def _new_keys_object(self, size): assert size >= MIN_SIZE_SPLIT mm = self._p_mm with mm.transaction(): dk_oid = mm.zalloc(ffi.sizeof('PDictKeysObject') + ffi.sizeof('PDictKeyEntry') * (size - 1), type_num=PDICTKEYSOBJECT_TYPE_NUM) dk = ffi.cast('PDictKeysObject *', mm.direct(dk_oid)) dk.dk_refcnt = 1 dk.dk_size = size dk.dk_usable = _usable_fraction(size) ep = ffi.cast('PDictKeyEntry *', ffi.addressof(dk.dk_entries[0])) # Hash value of slot 0 is used by popitem, so it must be initizlied ep[0].me_hash = 0 for i in range(size): ep[i].me_key = mm.OID_NULL ep[i].me_value = mm.OID_NULL # XXX Set dk_lookup to lookdict_unicode_nodummy if we end up using it. return dk_oid
def _table_resize(self, minused): mm = self._p_mm if minused > 50000: minused = (minused << 1) else: minused = (minused << 2) newsize = PERM_SET_MINSIZE while (newsize <= minused): newsize = (newsize << 1) newsize = ffi.cast('size_t', newsize) if newsize == 0: raise MemoryError("Out of memory") newsize = int(newsize) with mm.transaction(): oldtable = mm.otuple(self._body.table) oldtable_data = ffi.cast('PSetEntry *', mm.direct(oldtable)) newtable = self._alloc_empty_table(newsize) newmask = newsize - 1 for i in range(0, self._body.mask + 1): if oldtable_data[i].hash == HASH_UNUSED or \ oldtable_data[i].hash == HASH_DUMMY: continue self._insert_clean(newtable, newmask, oldtable_data[i].key, oldtable_data[i].hash) mm.snapshot_range(ffi.addressof(self._body, 'fill'), ffi.sizeof('PSetObject') - ffi.sizeof('PObject')) self._body.mask = newmask self._body.fill = self._body.used self._body.table = newtable mm.free(oldtable)