Skip to content

Commit

Permalink
See #359: Introduce build time option PyObjC_ENABLE_LOOKUP_CACHE
Browse files Browse the repository at this point in the history
This option enables caching the result of _type_lookup in
classes. Disabled for now because this slightly changes
semantics (super calls on arbitrary classes can behave differently).

I'm also not 100% convinced this new option is safe to use
with "normal" Python application code. It is clearly faster for
lookups of inherited methods (just like PyObjC_FAST_BUT_INEXACT).
  • Loading branch information
ronaldoussoren committed May 14, 2021
1 parent 5c8ed0a commit d8172e1
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 2 deletions.
32 changes: 32 additions & 0 deletions pyobjc-core/Modules/objc/objc-class.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ typedef struct _PyObjCClassObject {
PyObject* delmethod;
PyObject* hiddenSelectors;
PyObject* hiddenClassSelectors;
PyObject* lookup_cache;

Py_ssize_t dictoffset;
Py_ssize_t generation;
Expand Down Expand Up @@ -116,6 +117,37 @@ extern PyObject* PyObjCClass_TryResolveSelector(PyObject* base, PyObject* name,
extern PyObject* PyObjCMetaClass_TryResolveSelector(PyObject* base, PyObject* name,
SEL sel);

static inline PyObject*
PyObjCClass_GetLookupCache(PyTypeObject* tp)
{
return ((PyObjCClassObject*)tp)->lookup_cache;
}

static inline int
PyObjCClass_AddToLookupCache(PyTypeObject* _tp, PyObject* name, PyObject* value)
{
#ifdef PyObjC_ENABLE_LOOKUP_CACHE
int r;
PyObjCClassObject* tp = (PyObjCClassObject*)_tp;
if (tp->lookup_cache == NULL) {
tp->lookup_cache = PyDict_New();
if (tp->lookup_cache == NULL) {
return -1;
}
}
r = PyDict_SetItem(tp->lookup_cache, name, value);
return r;
#else
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-value"

&_tp; &name; &value;
#pragma clang diagnostic pop
return 0;
#endif
}


static inline int
PyObjCClass_IsCFWrapper(PyTypeObject* tp)
{
Expand Down
2 changes: 2 additions & 0 deletions pyobjc-core/Modules/objc/objc-class.m
Original file line number Diff line number Diff line change
Expand Up @@ -962,6 +962,7 @@
info->isFinal = final;
info->hiddenSelectors = hiddenSelectors;
info->hiddenClassSelectors = hiddenClassSelectors;
info->lookup_cache = NULL;

var = class_getInstanceVariable(objc_class, "__dict__");
if (var != NULL) {
Expand Down Expand Up @@ -2183,6 +2184,7 @@
info->isCFWrapper = 0;
info->isFinal = 0;
info->hiddenSelectors = hiddenSelectors;
info->lookup_cache = NULL;

objc_class_register(objc_class, result);

Expand Down
20 changes: 20 additions & 0 deletions pyobjc-core/Modules/objc/objc-object.m
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@
{
Py_ssize_t i, n;
PyObject * mro, *base, *dict;
PyObject* first_class = NULL;
PyObject* descr = NULL;
PyObject* res;
SEL sel = PyObjCSelector_DefaultSelector(PyObjC_Unicode_Fast_Bytes(name));
Expand All @@ -319,6 +320,20 @@
base = PyTuple_GET_ITEM(mro, i);

if (PyObjCClass_Check(base)) {
if (i == 0) {
first_class = base;
}
PyObject* cache = PyObjCClass_GetLookupCache((PyTypeObject*)base);
if (cache != NULL) {
descr = PyDict_GetItemWithError(cache, name);
if (descr == NULL && PyErr_Occurred()) {
return NULL;
}
if (descr != NULL) {
break;
}
}

if (PyObjCClass_CheckMethodList(base, 0) < 0) {
return NULL;
}
Expand All @@ -338,6 +353,11 @@
if (descr == NULL && PyErr_Occurred()) {
return NULL;
} else if (descr != NULL) {
if (first_class != NULL) {
if (PyObjCClass_AddToLookupCache((PyTypeObject*)first_class, name, descr) == -1) {
PyErr_Clear();
}
}
break;
}

Expand Down
13 changes: 12 additions & 1 deletion pyobjc-core/Modules/objc/pyobjc.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,18 @@
*
* NOTE: Option is present for performance testing.
*/
/*#define PyObjC_FAST_BUT_INEXACT 1*/
/* #define PyObjC_FAST_BUT_INEXACT 1 */

/* PyObjC_ENABLE_LOOKUP_CACHE: If defined the _type_lookup will cache
* found entries in leaf classes.
*
* A side effect of this is that methods introduced in categories might not
* be seen in the Python proxy and as such suffers from the same issue as
* PyObjC_FAST_BUT_INEXACT.
*
* NOTE: Option is present for performance testing.
*/
/* #define PyObjC_ENABLE_LOOKUP_CACHE 1 */

/*
* End of configuration block
Expand Down
3 changes: 2 additions & 1 deletion pyobjc-core/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ def get_sdk_level(sdk):
# "--analyze",
"-Werror",
"-I/usr/include/ffi",
# "-O3", "-flto",
]

# CFLAGS for other (test) extensions:
Expand All @@ -99,7 +100,7 @@ def get_sdk_level(sdk):
"-g",
"-lffi",
# "-fsanitize=address", "-fsanitize=undefined", "-fno-sanitize=vptr",
# "-O3",
# "-O3", "-flto"
]


Expand Down

0 comments on commit d8172e1

Please sign in to comment.