Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
P
persistent
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
persistent
Commits
c0ef0335
Commit
c0ef0335
authored
Apr 27, 2015
by
Jason Madden
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Checkpoint on a CFFI version of the cache MRU ring. Currently fails due to lifetime issues.
parent
add499ab
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
130 additions
and
52 deletions
+130
-52
persistent/persistence.py
persistent/persistence.py
+15
-2
persistent/picklecache.py
persistent/picklecache.py
+34
-50
persistent/ring.py
persistent/ring.py
+81
-0
No files found.
persistent/persistence.py
View file @
c0ef0335
...
@@ -27,6 +27,8 @@ from persistent.timestamp import _ZERO
...
@@ -27,6 +27,8 @@ from persistent.timestamp import _ZERO
from
persistent._compat
import
copy_reg
from
persistent._compat
import
copy_reg
from
persistent._compat
import
intern
from
persistent._compat
import
intern
from
.
import
ring
_INITIAL_SERIAL
=
_ZERO
_INITIAL_SERIAL
=
_ZERO
...
@@ -54,7 +56,7 @@ _SPECIAL_NAMES = set(SPECIAL_NAMES)
...
@@ -54,7 +56,7 @@ _SPECIAL_NAMES = set(SPECIAL_NAMES)
class
Persistent
(
object
):
class
Persistent
(
object
):
""" Pure Python implmentation of Persistent base class
""" Pure Python implmentation of Persistent base class
"""
"""
__slots__
=
(
'__jar'
,
'__oid'
,
'__serial'
,
'__flags'
,
'__size'
)
__slots__
=
(
'__jar'
,
'__oid'
,
'__serial'
,
'__flags'
,
'__size'
,
'__ring'
,
'__ring_handle'
)
def
__new__
(
cls
,
*
args
,
**
kw
):
def
__new__
(
cls
,
*
args
,
**
kw
):
inst
=
super
(
Persistent
,
cls
).
__new__
(
cls
)
inst
=
super
(
Persistent
,
cls
).
__new__
(
cls
)
...
@@ -67,6 +69,8 @@ class Persistent(object):
...
@@ -67,6 +69,8 @@ class Persistent(object):
_OSA
(
inst
,
'_Persistent__serial'
,
None
)
_OSA
(
inst
,
'_Persistent__serial'
,
None
)
_OSA
(
inst
,
'_Persistent__flags'
,
None
)
_OSA
(
inst
,
'_Persistent__flags'
,
None
)
_OSA
(
inst
,
'_Persistent__size'
,
0
)
_OSA
(
inst
,
'_Persistent__size'
,
0
)
_OSA
(
inst
,
'_Persistent__ring'
,
None
)
_OSA
(
inst
,
'_Persistent__ring_handle'
,
None
)
return
inst
return
inst
# _p_jar: see IPersistent.
# _p_jar: see IPersistent.
...
@@ -483,6 +487,9 @@ class Persistent(object):
...
@@ -483,6 +487,9 @@ class Persistent(object):
jar
=
oga
(
self
,
'_Persistent__jar'
)
jar
=
oga
(
self
,
'_Persistent__jar'
)
if
jar
is
None
:
if
jar
is
None
:
return
return
myring
=
oga
(
self
,
'_Persistent__ring'
)
if
ring
is
None
:
return
oid
=
oga
(
self
,
'_Persistent__oid'
)
oid
=
oga
(
self
,
'_Persistent__oid'
)
if
oid
is
None
:
if
oid
is
None
:
return
return
...
@@ -490,6 +497,7 @@ class Persistent(object):
...
@@ -490,6 +497,7 @@ class Persistent(object):
if
flags
is
None
:
# ghost
if
flags
is
None
:
# ghost
return
return
# The KeyError arises in ZODB: ZODB.serialize.ObjectWriter
# The KeyError arises in ZODB: ZODB.serialize.ObjectWriter
# can assign a jar and an oid to newly seen persistent objects,
# can assign a jar and an oid to newly seen persistent objects,
# but because they are newly created, they aren't in the
# but because they are newly created, they aren't in the
...
@@ -497,10 +505,11 @@ class Persistent(object):
...
@@ -497,10 +505,11 @@ class Persistent(object):
# that at this level, all we can do is catch it.
# that at this level, all we can do is catch it.
# The AttributeError arises in ZODB test cases
# The AttributeError arises in ZODB test cases
try
:
try
:
jar
.
_cache
.
mru
(
oid
)
ring
.
move_to_head
(
jar
.
_cache
.
ring_home
,
myring
)
except
(
AttributeError
,
KeyError
):
except
(
AttributeError
,
KeyError
):
pass
pass
def
_p_is_in_cache
(
self
):
def
_p_is_in_cache
(
self
):
oid
=
self
.
__oid
oid
=
self
.
__oid
if
not
oid
:
if
not
oid
:
...
@@ -511,6 +520,10 @@ class Persistent(object):
...
@@ -511,6 +520,10 @@ class Persistent(object):
if
cache
is
not
None
:
if
cache
is
not
None
:
return
cache
.
get
(
oid
)
is
self
return
cache
.
get
(
oid
)
is
self
def
__del__
(
self
):
if
self
.
_p_is_in_cache
():
ring
.
del_
(
self
.
_Persistent__ring
)
def
_estimated_size_in_24_bits
(
value
):
def
_estimated_size_in_24_bits
(
value
):
if
value
>
1073741696
:
if
value
>
1073741696
:
return
16777215
return
16777215
...
...
persistent/picklecache.py
View file @
c0ef0335
...
@@ -71,6 +71,8 @@ def _sweeping_ring(f):
...
@@ -71,6 +71,8 @@ def _sweeping_ring(f):
from
collections
import
deque
from
collections
import
deque
from
.
import
ring
@
implementer
(
IPickleCache
)
@
implementer
(
IPickleCache
)
class
PickleCache
(
object
):
class
PickleCache
(
object
):
...
@@ -100,11 +102,7 @@ class PickleCache(object):
...
@@ -100,11 +102,7 @@ class PickleCache(object):
self
.
non_ghost_count
=
0
self
.
non_ghost_count
=
0
self
.
persistent_classes
=
{}
self
.
persistent_classes
=
{}
self
.
data
=
weakref
.
WeakValueDictionary
()
self
.
data
=
weakref
.
WeakValueDictionary
()
# oldest is on the left, newest on the right so that default
self
.
ring_home
=
ring
.
CPersistentRingHead
()
# iteration order is maintained from oldest to newest.
# Note that the remove() method is verboten: it uses equality
# comparisons, but we must use identity comparisons
self
.
ring
=
deque
()
self
.
cache_size_bytes
=
cache_size_bytes
self
.
cache_size_bytes
=
cache_size_bytes
# IPickleCache API
# IPickleCache API
...
@@ -164,7 +162,11 @@ class PickleCache(object):
...
@@ -164,7 +162,11 @@ class PickleCache(object):
else
:
else
:
self
.
data
[
oid
]
=
value
self
.
data
[
oid
]
=
value
_gc_monitor
(
value
)
_gc_monitor
(
value
)
self
.
mru
(
oid
)
node
=
ring
.
CPersistentRing
(
value
)
value
.
_Persistent__ring
=
node
if
_OGA
(
value
,
'_p_state'
)
!=
GHOST
:
ring
.
add
(
self
.
ring_home
,
node
)
self
.
non_ghost_count
+=
1
def
__delitem__
(
self
,
oid
):
def
__delitem__
(
self
,
oid
):
""" See IPickleCache.
""" See IPickleCache.
...
@@ -196,21 +198,18 @@ class PickleCache(object):
...
@@ -196,21 +198,18 @@ class PickleCache(object):
return
False
# marker return for tests
return
False
# marker return for tests
value
=
self
.
data
[
oid
]
value
=
self
.
data
[
oid
]
was_in_ring
=
self
.
_remove_from_ring
(
value
)
id_value
=
id
(
value
)
if
was_in_ring
:
# Compensate for decrementing the count; by
was_in_ring
=
bool
(
value
.
_Persistent__ring
.
r_next
)
# definition it should already have been not-a-ghost
ring
.
move_to_head
(
self
.
ring_home
,
value
.
_Persistent__ring
)
# so we can avoid a trip through Persistent.__getattribute__
if
not
was_in_ring
and
_OGA
(
value
,
'_p_state'
)
!=
GHOST
:
self
.
ring
.
append
(
value
)
self
.
non_ghost_count
+=
1
elif
_OGA
(
value
,
'_p_state'
)
!=
GHOST
:
self
.
ring
.
append
(
value
)
self
.
non_ghost_count
+=
1
self
.
non_ghost_count
+=
1
def
ringlen
(
self
):
def
ringlen
(
self
):
""" See IPickleCache.
""" See IPickleCache.
"""
"""
return
len
(
self
.
ring
)
return
ring
.
ringlen
(
self
.
ring_home
)
def
items
(
self
):
def
items
(
self
):
""" See IPickleCache.
""" See IPickleCache.
...
@@ -220,7 +219,11 @@ class PickleCache(object):
...
@@ -220,7 +219,11 @@ class PickleCache(object):
def
lru_items
(
self
):
def
lru_items
(
self
):
""" See IPickleCache.
""" See IPickleCache.
"""
"""
return
[(
x
.
_p_oid
,
x
)
for
x
in
self
.
ring
]
result
=
[]
for
item
in
ring
.
iteritems
(
self
.
ring_home
):
obj
=
ring
.
get_object
(
item
)
result
.
append
((
obj
.
_p_oid
,
obj
))
return
result
def
klass_items
(
self
):
def
klass_items
(
self
):
""" See IPickleCache.
""" See IPickleCache.
...
@@ -346,17 +349,16 @@ class PickleCache(object):
...
@@ -346,17 +349,16 @@ class PickleCache(object):
@
_sweeping_ring
@
_sweeping_ring
def
_sweep
(
self
,
target
,
target_size_bytes
=
0
):
def
_sweep
(
self
,
target
,
target_size_bytes
=
0
):
# lock
# lock
# We can't mutate while we're iterating, so store ejections by index here
ejected
=
0
# (deleting by index is potentially more efficient then by value because it
for
here
in
ring
.
iteritems
(
self
.
ring_home
):
# can use the rotate() method and not be O(n)). Also note that we do not use
value
=
ring
.
get_object
(
here
)
# self._remove_from_ring because we need to decrement the non_ghost_count
if
value
is
None
:
# as we traverse the ring to be sure to meet our target
continue
to_eject
=
[]
here
=
here
.
r_next
i
=
-
1
# Using a manual numeric counter instead of enumerate() is much faster on PyPy
for
value
in
self
.
ring
:
if
self
.
non_ghost_count
<=
target
and
(
self
.
total_estimated_size
<=
target_size_bytes
or
not
target_size_bytes
):
if
self
.
non_ghost_count
<=
target
and
(
self
.
total_estimated_size
<=
target_size_bytes
or
not
target_size_bytes
):
break
break
i
+=
1
if
value
.
_p_state
==
UPTODATE
:
if
value
.
_p_state
==
UPTODATE
:
# The C implementation will only evict things that are specifically
# The C implementation will only evict things that are specifically
# in the up-to-date state
# in the up-to-date state
...
@@ -379,16 +381,14 @@ class PickleCache(object):
...
@@ -379,16 +381,14 @@ class PickleCache(object):
# they don't cooperate (without this check a bunch of test_picklecache
# they don't cooperate (without this check a bunch of test_picklecache
# breaks)
# breaks)
or
not
isinstance
(
value
,
_SWEEPABLE_TYPES
)):
or
not
isinstance
(
value
,
_SWEEPABLE_TYPES
)):
to_eject
.
append
(
i
)
self
.
_remove_from_ring
(
value
)
self
.
non_ghost_count
-=
1
self
.
non_ghost_count
-=
1
ejected
+=
1
for
i
in
reversed
(
to_eject
):
if
ejected
and
_SWEEP_NEEDS_GC
:
del
self
.
ring
[
i
]
if
to_eject
and
_SWEEP_NEEDS_GC
:
# See comments on _SWEEP_NEEDS_GC
# See comments on _SWEEP_NEEDS_GC
gc
.
collect
()
gc
.
collect
()
return
len
(
to_eject
)
return
ejected
@
_sweeping_ring
@
_sweeping_ring
def
_invalidate
(
self
,
oid
):
def
_invalidate
(
self
,
oid
):
...
@@ -408,21 +408,5 @@ class PickleCache(object):
...
@@ -408,21 +408,5 @@ class PickleCache(object):
pass
pass
def
_remove_from_ring
(
self
,
value
):
def
_remove_from_ring
(
self
,
value
):
"""
if
value
.
_Persistent__ring
.
r_next
:
Removes the previously non-ghost `value` from the ring, decrementing
ring
.
del_
(
value
.
_Persistent__ring
)
the `non_ghost_count` if it's found. The value may be a ghost when
this method is called.
:return: A true value if the object was found in the ring.
"""
# Note that we do not use self.ring.remove() because that
# uses equality semantics and we don't want to call the persistent
# object's __eq__ method (which might wake it up just after we
# tried to ghost it)
i
=
0
# Using a manual numeric counter instead of enumerate() is much faster on PyPy
for
o
in
self
.
ring
:
if
o
is
value
:
del
self
.
ring
[
i
]
self
.
non_ghost_count
-=
1
return
1
i
+=
1
persistent/ring.py
0 → 100644
View file @
c0ef0335
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
"""
from
cffi
import
FFI
import
pkg_resources
import
os
ffi
=
FFI
()
ffi
.
cdef
(
"""
typedef struct CPersistentRingEx_struct
{
struct CPersistentRingEx_struct *r_prev;
struct CPersistentRingEx_struct *r_next;
void* object;
} CPersistentRingEx;
"""
)
ffi
.
cdef
(
pkg_resources
.
resource_string
(
'persistent'
,
'ring.h'
))
ring
=
ffi
.
verify
(
"""
typedef struct CPersistentRingEx_struct
{
struct CPersistentRingEx_struct *r_prev;
struct CPersistentRingEx_struct *r_next;
void* object;
} CPersistentRingEx;
#include "ring.c"
"""
,
include_dirs
=
[
os
.
path
.
dirname
(
os
.
path
.
abspath
(
__file__
))])
class
CPersistentRing
(
object
):
def
__init__
(
self
,
obj
=
None
):
self
.
handle
=
None
self
.
node
=
ffi
.
new
(
"CPersistentRingEx*"
)
if
obj
is
not
None
:
self
.
handle
=
self
.
node
.
object
=
ffi
.
new_handle
(
obj
)
self
.
_object
=
obj
# Circular reference
def
__getattr__
(
self
,
name
):
return
getattr
(
self
.
node
,
name
)
def
CPersistentRingHead
():
head
=
CPersistentRing
()
head
.
node
.
r_next
=
head
.
node
head
.
node
.
r_prev
=
head
.
node
return
head
def
_c
(
node
):
return
ffi
.
cast
(
"CPersistentRing*"
,
node
.
node
)
def
add
(
head
,
elt
):
ring
.
ring_add
(
_c
(
head
),
_c
(
elt
))
def
del_
(
elt
):
ring
.
ring_del
(
_c
(
elt
))
def
move_to_head
(
head
,
elt
):
ring
.
ring_move_to_head
(
_c
(
head
),
_c
(
elt
))
def
iteritems
(
head
):
here
=
head
.
r_next
while
here
!=
head
.
node
:
yield
here
here
=
here
.
r_next
def
ringlen
(
head
):
count
=
0
for
_
in
iteritems
(
head
):
count
+=
1
return
count
def
get_object
(
node
):
return
ffi
.
from_handle
(
node
.
object
)
print
CPersistentRing
()
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment