From d8172e1fc5aca8f6bb065c42c1dd8408e61c6a8a Mon Sep 17 00:00:00 2001 From: Ronald Oussoren Date: Fri, 14 May 2021 11:22:56 +0200 Subject: [PATCH] See #359: Introduce build time option PyObjC_ENABLE_LOOKUP_CACHE 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). --- pyobjc-core/Modules/objc/objc-class.h | 32 ++++++++++++++++++++++++++ pyobjc-core/Modules/objc/objc-class.m | 2 ++ pyobjc-core/Modules/objc/objc-object.m | 20 ++++++++++++++++ pyobjc-core/Modules/objc/pyobjc.h | 13 ++++++++++- pyobjc-core/setup.py | 3 ++- 5 files changed, 68 insertions(+), 2 deletions(-) diff --git a/pyobjc-core/Modules/objc/objc-class.h b/pyobjc-core/Modules/objc/objc-class.h index a1a75b0756..4091d89864 100644 --- a/pyobjc-core/Modules/objc/objc-class.h +++ b/pyobjc-core/Modules/objc/objc-class.h @@ -83,6 +83,7 @@ typedef struct _PyObjCClassObject { PyObject* delmethod; PyObject* hiddenSelectors; PyObject* hiddenClassSelectors; + PyObject* lookup_cache; Py_ssize_t dictoffset; Py_ssize_t generation; @@ -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) { diff --git a/pyobjc-core/Modules/objc/objc-class.m b/pyobjc-core/Modules/objc/objc-class.m index cebdd3aa47..ecdba81044 100644 --- a/pyobjc-core/Modules/objc/objc-class.m +++ b/pyobjc-core/Modules/objc/objc-class.m @@ -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) { @@ -2183,6 +2184,7 @@ info->isCFWrapper = 0; info->isFinal = 0; info->hiddenSelectors = hiddenSelectors; + info->lookup_cache = NULL; objc_class_register(objc_class, result); diff --git a/pyobjc-core/Modules/objc/objc-object.m b/pyobjc-core/Modules/objc/objc-object.m index baf6a48296..d62f443f18 100644 --- a/pyobjc-core/Modules/objc/objc-object.m +++ b/pyobjc-core/Modules/objc/objc-object.m @@ -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)); @@ -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; } @@ -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; } diff --git a/pyobjc-core/Modules/objc/pyobjc.h b/pyobjc-core/Modules/objc/pyobjc.h index 10a79eaeb1..1f2b9a338d 100644 --- a/pyobjc-core/Modules/objc/pyobjc.h +++ b/pyobjc-core/Modules/objc/pyobjc.h @@ -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 diff --git a/pyobjc-core/setup.py b/pyobjc-core/setup.py index 66ce0c3abd..c49c865dd0 100644 --- a/pyobjc-core/setup.py +++ b/pyobjc-core/setup.py @@ -82,6 +82,7 @@ def get_sdk_level(sdk): # "--analyze", "-Werror", "-I/usr/include/ffi", + # "-O3", "-flto", ] # CFLAGS for other (test) extensions: @@ -99,7 +100,7 @@ def get_sdk_level(sdk): "-g", "-lffi", # "-fsanitize=address", "-fsanitize=undefined", "-fno-sanitize=vptr", - # "-O3", + # "-O3", "-flto" ]