Author: Armin Rigo <[email protected]>
Branch: dict-move-to-end
Changeset: r89928:6d1fd75ac17f
Date: 2017-02-04 21:46 +0100
http://bitbucket.org/pypy/pypy/changeset/6d1fd75ac17f/
Log: move to beginning
diff --git a/rpython/rtyper/lltypesystem/rordereddict.py
b/rpython/rtyper/lltypesystem/rordereddict.py
--- a/rpython/rtyper/lltypesystem/rordereddict.py
+++ b/rpython/rtyper/lltypesystem/rordereddict.py
@@ -821,9 +821,7 @@
raise KeyError
_ll_dict_del(d, hash, index)
[email protected]_inside_iff(lambda d, h, i: jit.isvirtual(d) and jit.isconstant(i))
-def _ll_dict_del(d, hash, index):
- ll_call_delete_by_entry_index(d, hash, index, DELETED)
+def _ll_dict_del_entry(d, index):
d.entries.mark_deleted(index)
d.num_live_items -= 1
# clear the key and the value if they are GC pointers
@@ -835,6 +833,11 @@
if ENTRIES.must_clear_value:
entry.value = lltype.nullptr(ENTRY.value.TO)
[email protected]_inside_iff(lambda d, h, i: jit.isvirtual(d) and jit.isconstant(i))
+def _ll_dict_del(d, hash, index):
+ ll_call_delete_by_entry_index(d, hash, index, DELETED)
+ _ll_dict_del_entry(d, index)
+
if d.num_live_items == 0:
# Dict is now empty. Reset these fields.
d.num_ever_used_items = 0
@@ -1427,8 +1430,12 @@
return value
def ll_dict_move_to_end(d, key, last):
- assert last #XXX
+ if last:
+ ll_dict_move_to_end_really(d, key)
+ else:
+ ll_dict_move_to_beginning(d, key)
+def ll_dict_move_to_end_really(d, key):
hash = d.keyhash(key)
old_index = d.lookup_function(d, key, hash, FLAG_LOOKUP)
if old_index < 0:
@@ -1438,17 +1445,10 @@
return
# remove the entry at the old position
- ENTRIES = lltype.typeOf(d.entries).TO
- ENTRY = ENTRIES.OF
old_entry = d.entries[old_index]
key = old_entry.key
value = old_entry.value
- d.entries.mark_deleted(old_index)
- d.num_live_items -= 1
- if ENTRIES.must_clear_key:
- old_entry.key = lltype.nullptr(ENTRY.key.TO)
- if ENTRIES.must_clear_value:
- old_entry.value = lltype.nullptr(ENTRY.value.TO)
+ _ll_dict_del_entry(d, old_index)
# note a corner case: it is possible that 'replace_with' is just too
# large to fit in the type T used so far for the index. But in that
@@ -1458,3 +1458,89 @@
ll_call_delete_by_entry_index(d, hash, old_index,
replace_with = VALID_OFFSET + d.num_ever_used_items)
_ll_dict_setitem_lookup_done(d, key, value, hash, -1)
+
+def ll_dict_move_to_beginning(d, key):
+ # In this function, we might do a bit more than the strict minimum
+ # of walks over parts of the array, trying to keep the code at least
+ # semi-reasonable, while the goal is still amortized constant-time
+ # over many calls.
+
+ # Call ll_dict_remove_deleted_items() first if there are too many
+ # deleted items. Not a perfect solution, because lookup_function()
+ # might do random things with the dict and create many new deleted
+ # items. Still, should be fine, because nothing crucially depends
+ # on this: the goal is to avoid the dictionary's list growing
+ # forever.
+ if d.num_live_items < len(d.entries) // 2 - 16:
+ ll_dict_remove_deleted_items(d)
+
+ hash = d.keyhash(key)
+ old_index = d.lookup_function(d, key, hash, FLAG_LOOKUP)
+ if old_index <= 0:
+ if old_index < 0:
+ raise KeyError
+ else:
+ return
+
+ # the goal of the following is to set 'idst' to the number of
+ # deleted entries at the beginning, ensuring 'idst > 0'
+ must_reindex = False
+ if d.entries.valid(0):
+ # the first entry is valid, so we need to make room before.
+ new_allocated = _overallocate_entries_len(d.num_ever_used_items)
+ idst = ((new_allocated - d.num_ever_used_items) * 3) // 4
+ ll_assert(idst > 0, "overallocate did not do enough")
+ newitems = lltype.malloc(lltype.typeOf(d).TO.entries.TO, new_allocated)
+ rgc.ll_arraycopy(d.entries, newitems, 0, idst, d.num_ever_used_items)
+ d.entries = newitems
+ i = 0
+ while i < idst:
+ d.entries.mark_deleted(i)
+ i += 1
+ d.num_ever_used_items += idst
+ old_index += idst
+ must_reindex = True
+ idst -= 1
+ else:
+ idst = d.lookup_function_no >> FUNC_SHIFT
+ # All entries in range(0, idst) are deleted. Check if more are
+ while not d.entries.valid(idst):
+ idst += 1
+ if idst == old_index:
+ d.lookup_function_no = ((d.lookup_function_no & FUNC_MASK) |
+ (old_index << FUNC_SHIFT))
+ return
+ idst -= 1
+ d.lookup_function_no = ((d.lookup_function_no & FUNC_MASK) |
+ (idst << FUNC_SHIFT))
+
+ # remove the entry at the old position
+ ll_assert(d.entries.valid(old_index),
+ "ll_dict_move_to_beginning: lost old_index")
+ ENTRY = lltype.typeOf(d.entries).TO.OF
+ old_entry = d.entries[old_index]
+ key = old_entry.key
+ value = old_entry.value
+ if hasattr(ENTRY, 'f_hash'):
+ ll_assert(old_entry.f_hash == hash,
+ "ll_dict_move_to_beginning: bad hash")
+ _ll_dict_del_entry(d, old_index)
+
+ # put the entry at its new position
+ ll_assert(not d.entries.valid(idst),
+ "ll_dict_move_to_beginning: overwriting idst")
+ new_entry = d.entries[idst]
+ new_entry.key = key
+ new_entry.value = value
+ if hasattr(ENTRY, 'f_hash'):
+ new_entry.f_hash = hash
+ if hasattr(ENTRY, 'f_valid'):
+ new_entry.f_valid = True
+ d.num_live_items += 1
+
+ # fix the index
+ if must_reindex:
+ ll_dict_reindex(d, _ll_len_of_d_indexes(d))
+ else:
+ ll_call_delete_by_entry_index(d, hash, old_index,
+ replace_with = VALID_OFFSET + idst)
diff --git a/rpython/rtyper/test/test_rordereddict.py
b/rpython/rtyper/test/test_rordereddict.py
--- a/rpython/rtyper/test/test_rordereddict.py
+++ b/rpython/rtyper/test/test_rordereddict.py
@@ -360,12 +360,12 @@
elif case == 3:
py.test.raises(KeyError, rordereddict.ll_dict_move_to_end,
ll_d, 3, True)
- #elif case == 4:
- # rordereddict.ll_dict_move_to_end(ll_d, 2, False)
- # assert content() == [(2, 22), (1, 11)]
- #elif case == 5:
- # rordereddict.ll_dict_move_to_end(ll_d, 1, False)
- # assert content() == [(1, 11), (2, 22)]
+ elif case == 4:
+ rordereddict.ll_dict_move_to_end(ll_d, 2, False)
+ assert content() == [(2, 22), (1, 11)]
+ elif case == 5:
+ rordereddict.ll_dict_move_to_end(ll_d, 1, False)
+ assert content() == [(1, 11), (2, 22)]
class TestRDictDirectDummyKey(TestRDictDirect):
@@ -409,14 +409,6 @@
assert d1.keys() == ['key2', 'key1']
objectmodel.move_to_end(d1, 'key2')
assert d1.keys() == ['key1', 'key2']
- func()
- self.interpret(func, [])
-
- def test_move_to_beginning(self):
- def func():
- d1 = OrderedDict()
- d1['key1'] = 'value1'
- d1['key2'] = 'value2'
for i in range(20):
objectmodel.move_to_end(d1, 'key2', last=False)
assert d1.keys() == ['key2', 'key1']
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit