Commits

Ronald Oussoren committed 49b5260

First attempt at improving speed of method/function/block calls

Comments (0)

Files changed (11)

pyobjc-core/Modules/objc/ObjCPointer.h

 extern PyTypeObject PyObjCPointer_Type;
 #define PyObjCPointer_Check(o) (Py_TYPE(o) == &PyObjCPointer_Type)
 
-extern PyObject* PyObjCPointer_New(void *ptr, const char *type);
-extern void* PyObjCPointer_Ptr(PyObject* object);
+extern PyObject* PyObjCPointer_New(void *ptr, const char *type) __attribute__((__nonnull__(2))) __attribute__((__warn_unused_result__));
+extern void* PyObjCPointer_Ptr(PyObject* object) __attribute__((__nonnull__(1)));
 
 #endif /* PyObjC_OBJC_POINTER_H */

pyobjc-core/Modules/objc/block_support.h

 #define PyOBJC_BLOCK_SUPPORT_H
 
 typedef void (*_block_func_ptr)(void*, ...);
-extern _block_func_ptr PyObjCBlock_GetFunction(void* block);
-extern const char* PyObjCBlock_GetSignature(void* _block);
-extern void* PyObjCBlock_Create(PyObjCMethodSignature* signature, PyObject* callable);
-extern void PyObjCBlock_Release(void* _block);
-extern int PyObjCBlock_Setup(void);
-extern PyObject* PyObjCBlock_Call(PyObject* self, PyObject* args);
+extern _block_func_ptr PyObjCBlock_GetFunction(void* block) __attribute__((__nonnull__(1)));
+extern const char* PyObjCBlock_GetSignature(void* _block) __attribute__((__nonnull__(1)));
+extern void* PyObjCBlock_Create(PyObjCMethodSignature* signature, PyObject* callable) __attribute__((__nonnull__(1,2))) __attribute__((__warn_unused_result__));
+extern void PyObjCBlock_Release(void* _block) __attribute__((__nonnull__(1)));
+extern int PyObjCBlock_Setup(void) __attribute__((__warn_unused_result__));
+extern PyObject* PyObjCBlock_Call(PyObject* self, PyObject* args) __attribute__((__nonnull__(1,2))) __attribute__((__warn_unused_result__));
 
 
 #endif /* PyOBJC_BLOCK_SUPPORT_H */

pyobjc-core/Modules/objc/block_support.m

     BOOL variadicAllArgs = NO;
     int r;
     unsigned char* argbuf = NULL;
-    ffi_type* arglist[64];
-    void* values[64];
-    void** byref = NULL;
-    struct byref_attr* byref_attr = NULL;
+    ffi_type* arglist[MAX_ARGCOUNT];
+    void* values[MAX_ARGCOUNT];
+    void* byref[MAX_ARGCOUNT] =  { 0 };
+    struct byref_attr byref_attr[MAX_ARGCOUNT] =  { {0, 0} };
     ffi_cif cif;
     PyObject* retval;
 
             return NULL;
         }
 
+        if (PyTuple_Size(args) > MAX_ARGCOUNT - 1) {
+            PyErr_Format(PyExc_TypeError, "At most %d arguments are supported, got %" PY_FORMAT_SIZE_T "d arguments", MAX_ARGCOUNT, PyTuple_Size(args));
+            return NULL;
+        }
+
     } else if (PyTuple_Size(args) != Py_SIZE(signature) - 1) {
+        if (Py_SIZE(signature) > MAX_ARGCOUNT) {
+            PyErr_Format(PyExc_TypeError, "At most %d arguments are supported, got %" PY_FORMAT_SIZE_T "d arguments", MAX_ARGCOUNT, PyTuple_Size(args));
+            return NULL;
+        }
+
         PyErr_Format(PyExc_TypeError, "Need %"PY_FORMAT_SIZE_T"d arguments, got %"PY_FORMAT_SIZE_T"d",
-        Py_SIZE(signature), PyTuple_Size(args));
+            Py_SIZE(signature), PyTuple_Size(args));
         return NULL;
     }
 
         return NULL;
     }
 
-    if (variadicAllArgs) {
-        if (PyObjCFFI_AllocByRef(Py_SIZE(signature) + PyTuple_Size(args), &byref, &byref_attr) < 0) {
-            goto error;
-        }
-
-    } else {
-        if (PyObjCFFI_AllocByRef(Py_SIZE(signature), &byref, &byref_attr) < 0) {
-            goto error;
-        }
-    }
-
-
     cif_arg_count = PyObjCFFI_ParseArguments(
         signature, 1, args,
         align(PyObjCRT_SizeOfReturnType(signature->rettype.type), sizeof(void*)) + sizeof(void*),
 
     if (variadicAllArgs) {
         if (PyObjCFFI_FreeByRef(Py_SIZE(signature)+PyTuple_Size(args), byref, byref_attr) < 0) {
-            byref = NULL; byref_attr = NULL;
             goto error;
         }
 
     } else {
         if (PyObjCFFI_FreeByRef(Py_SIZE(signature), byref, byref_attr) < 0) {
-            byref = NULL; byref_attr = NULL;
             goto error;
         }
     }

pyobjc-core/Modules/objc/bundle-variables.h

 #ifndef PyObjC_BUNDLE_VARIABLES_H
 #define PyObjC_BUNDLE_VARIABLES_H
 
-extern PyObject* PyObjC_loadSpecialVar(PyObject*, PyObject*, PyObject*);
-extern PyObject* PyObjC_loadBundleVariables(PyObject*, PyObject*, PyObject*);
-extern PyObject* PyObjC_loadBundleFunctions(PyObject*, PyObject*, PyObject*);
-extern PyObject* PyObjC_loadFunctionList(PyObject*, PyObject*, PyObject*);
+extern PyObject* PyObjC_loadSpecialVar(PyObject*, PyObject*, PyObject*) __attribute__((__nonnull__)) __attribute__((__warn_unused_result__));
+extern PyObject* PyObjC_loadBundleVariables(PyObject*, PyObject*, PyObject*) __attribute__((__nonnull__)) __attribute__((__warn_unused_result__));
+extern PyObject* PyObjC_loadBundleFunctions(PyObject*, PyObject*, PyObject*) __attribute__((__nonnull__)) __attribute__((__warn_unused_result__));
+extern PyObject* PyObjC_loadFunctionList(PyObject*, PyObject*, PyObject*) __attribute__((__nonnull__)) __attribute__((__warn_unused_result__));
 
 #endif /* PyObjC_BUNDLE_VARIABLES_H */

pyobjc-core/Modules/objc/function.m

     BOOL variadicAllArgs = NO;
 
     unsigned char* argbuf = NULL;
-    ffi_type* arglist[64];
-    void*     values[64];
-    void**      byref = NULL;
-    struct byref_attr* byref_attr = NULL;
+    ffi_type* arglist[MAX_ARGCOUNT];
+    void*     values[MAX_ARGCOUNT];
+    void*      byref[MAX_ARGCOUNT] = { 0 };
+    struct byref_attr byref_attr[MAX_ARGCOUNT] = { {0, 0} };
     ffi_cif cif;
     ffi_cif* cifptr;
 
         return NULL;
     }
 
-    if (variadicAllArgs) {
-        if (PyObjCFFI_AllocByRef(Py_SIZE(self->methinfo)+PyTuple_Size(args), &byref, &byref_attr) < 0) {
-            goto error;
-        }
-    } else {
-        if (PyObjCFFI_AllocByRef(Py_SIZE(self->methinfo), &byref, &byref_attr) < 0) {
-            goto error;
-        }
-    }
-
     cif_arg_count = PyObjCFFI_ParseArguments(
         self->methinfo, 0, args,
         align(PyObjCRT_SizeOfReturnType(self->methinfo->rettype.type), sizeof(void*)),
 
     if (variadicAllArgs) {
         if (PyObjCFFI_FreeByRef(Py_SIZE(self->methinfo)+PyTuple_Size(args), byref, byref_attr) < 0) {
-            byref = NULL; byref_attr = NULL;
             goto error;
         }
 
     } else {
         if (PyObjCFFI_FreeByRef(Py_SIZE(self->methinfo), byref, byref_attr) < 0) {
-            byref = NULL; byref_attr = NULL;
             goto error;
         }
     }
 
 error:
     if (variadicAllArgs) {
-        if (PyObjCFFI_FreeByRef(PyTuple_Size(args), byref, byref_attr) < 0) {
-            byref = NULL; byref_attr = NULL;
-            goto error;
-        }
+        PyObjCFFI_FreeByRef(PyTuple_Size(args), byref, byref_attr);
 
     } else {
-        if (PyObjCFFI_FreeByRef(Py_SIZE(self->methinfo), byref, byref_attr) < 0) {
-            byref = NULL; byref_attr = NULL;
-            goto error;
-        }
+        PyObjCFFI_FreeByRef(Py_SIZE(self->methinfo), byref, byref_attr);
     }
 
     if (argbuf) {

pyobjc-core/Modules/objc/libffi_support.h

 #    error "Need FFI_CLOSURES!"
 #endif
 
+#define MAX_ARGCOUNT 64
+
 struct byref_attr {
     int token;
     PyObject* buffer;
 typedef void (*PyObjC_callback_function)(void);
 typedef void (*PyObjCBlockFunction)(void*, ...);
 
-extern int PyObjCRT_ResultUsesStret(const char*);
+extern int PyObjCRT_ResultUsesStret(const char*) __attribute__((__pure__));
 extern void PyObjCFFI_FreeCIF(ffi_cif*);
 extern ffi_cif* PyObjCFFI_CIFForSignature(PyObjCMethodSignature*);
 extern IMP PyObjCFFI_MakeClosure(PyObjCMethodSignature*, PyObjCFFI_ClosureFunc, void*);
 extern PyObject* PyObjCFFI_BuildResult(
     PyObjCMethodSignature*, Py_ssize_t argOffset, void* pRetval, void**,
     struct byref_attr*byref_attr, Py_ssize_t, PyObject*, int, void**);
-extern int PyObjCFFI_AllocByRef(Py_ssize_t, void***, struct byref_attr**);
 extern int PyObjCFFI_FreeByRef(Py_ssize_t, void**, struct byref_attr*);
 extern ffi_type* PyObjCFFI_Typestr2FFI(const char*);
 extern PyObjC_callback_function PyObjCFFI_MakeFunctionClosure(PyObjCMethodSignature*, PyObject*);

pyobjc-core/Modules/objc/libffi_support.m

     const char* format;
     PyObject* v;
 
+    if (byref == NULL || byref_attr == NULL) {
+        PyErr_SetString(PyExc_TypeError, "Byref/byref_attr == NULL?");
+        return -1;
+    }
+
     if (PyBytes_Check(py_format)) {
         encoded = py_format;
         Py_INCREF(encoded);
     Py_ssize_t maxarg = PyTuple_Size(argtuple);
     Py_ssize_t argSize;
 
+
+    if (byref == NULL) {
+        PyErr_SetString(PyExc_TypeError, "byref == NULL");
+        return -1;
+    }
+
     if (count != -1) {
         if (maxarg - curarg != count) {
             PyErr_Format(PyExc_ValueError, "Wrong number of variadic arguments, need %" PY_FORMAT_SIZE_T "d, got %" PY_FORMAT_SIZE_T "d",
 
     *byref_in_count = *byref_out_count = *plain_count = 0;
 
+    if (methinfo->shortcut_signature) {
+        *argbuf_len += methinfo->shortcut_argbuf_size;
+        *variadicAllArgs = NO;
+        return 0;
+    }
+
     for (i = argOffset; i < Py_SIZE(methinfo); i++) {
         const char *argtype = methinfo->argtype[i].type;
 
 
             } else if (argument == PyObjC_NULL) {
                 if (methinfo->argtype[i].allowNULL) {
-
-                    byref[i] = NULL;
-                    arglist[i] = &ffi_type_pointer;
-                    values[i] = byref + i;
-
-                    error = 0;
+                    if (byref == NULL) {
+                        PyErr_SetString(PyExc_TypeError, "byref == NULL");
+                        error = -1;
+                    } else {
+
+                        byref[i] = NULL;
+                        arglist[i] = &ffi_type_pointer;
+                        values[i] = byref + i;
+
+                        error = 0;
+                    }
 
                 } else {
                     PyErr_Format(
                 argbuf_cur = align(argbuf_cur,
                     PyObjCRT_AlignOfType(resttype));
                 sz = PyObjCRT_SizeOfType(resttype);
+                /* XXX */
                 byref[i] = PyMem_Malloc(sz);
                 arg = NULL;
 
     return NULL;
 }
 
-int PyObjCFFI_AllocByRef(Py_ssize_t argcount, void*** byref, struct byref_attr** byref_attr)
-{
-    *byref = NULL; *byref_attr = NULL;
-
-    *byref = PyMem_Malloc(sizeof(void*) * argcount);
-    if (*byref == NULL) {
-        PyErr_NoMemory();
-        return -1;
-    }
-    memset(*byref, 0, sizeof(void*) * argcount);
-
-    *byref_attr = PyMem_Malloc(sizeof(struct byref_attr) * argcount);
-    if (*byref_attr == NULL) {
-        free(*byref);
-        *byref = NULL;
-
-        PyErr_NoMemory();
-        return -1;
-    }
-    memset(*byref_attr, 0, sizeof(struct byref_attr) * argcount);
-
-    return 0;
-}
-
 int PyObjCFFI_FreeByRef(Py_ssize_t argcount, void** byref, struct byref_attr* byref_attr)
 {
     Py_ssize_t i;
                 PyMem_Free(byref[i]); byref[i] = NULL;
             }
         }
-        PyMem_Free(byref);
-    }
-
-    if (byref_attr) {
-        PyMem_Free(byref_attr);
     }
 
     return 0;
     Py_ssize_t byref_in_count = 0;
     Py_ssize_t byref_out_count = 0;
     Py_ssize_t plain_count = 0;
-    void** byref = NULL; /* offset for arguments in argbuf */
-    struct byref_attr* byref_attr = NULL;
-    const char*       rettype;
     PyObjCMethodSignature*  methinfo;
     PyObjCNativeSelector* meth = (PyObjCNativeSelector*)aMeth;
     PyObject* objc_result = NULL;
     struct objc_super super;
     struct objc_super* superPtr;
     ffi_cif cif;
-    ffi_type* arglist[128]; /* XX: Magic constant */
-    void* values[128];
+    ffi_type* arglist[MAX_ARGCOUNT];
+    void* values[MAX_ARGCOUNT];
+    struct byref_attr byref_attr[MAX_ARGCOUNT] = { {0, 0} };
+    void* byref[MAX_ARGCOUNT] = { 0 };
     Py_ssize_t r;
     void* msgResult;
     Py_ssize_t resultSize;
     SEL theSel;
     int isUninitialized;
     BOOL variadicAllArgs = NO;
+    const char* rettype;
 
     if (PyObjCIMP_Check(aMeth)) {
         methinfo = PyObjCIMP_GetSignature(aMeth);
             goto error_cleanup;
         }
 
-        if (PyTuple_Size(args) > 127) {
-            PyErr_Format(PyExc_TypeError, "At most %d arguments are supported, got %" PY_FORMAT_SIZE_T "d arguments", 127, PyTuple_Size(args));
+        if (PyTuple_Size(args) > MAX_ARGCOUNT - 1) {
+            PyErr_Format(PyExc_TypeError, "At most %d arguments are supported, got %" PY_FORMAT_SIZE_T "d arguments", MAX_ARGCOUNT, PyTuple_Size(args));
             goto error_cleanup;
         }
 
     } else if (PyTuple_Size(args) != Py_SIZE(methinfo) - 2) {
+        if (Py_SIZE(methinfo) > MAX_ARGCOUNT) {
+            PyErr_Format(PyExc_TypeError, "At most %d arguments are supported, got %" PY_FORMAT_SIZE_T "d arguments", MAX_ARGCOUNT, PyTuple_Size(args));
+            goto error_cleanup;
+        }
+
 
         PyErr_Format(PyExc_TypeError, "Need %"PY_FORMAT_SIZE_T"d arguments, got %"PY_FORMAT_SIZE_T"d",
             Py_SIZE(methinfo) - 2, PyTuple_Size(args));
         goto error_cleanup;
     }
 
-    if (variadicAllArgs) {
-        if (PyObjCFFI_AllocByRef(Py_SIZE(methinfo)+PyTuple_Size(args),
-                    &byref, &byref_attr) < 0) {
-            goto error_cleanup;
-        }
-
-    } else {
-        if (PyObjCFFI_AllocByRef(Py_SIZE(methinfo), &byref, &byref_attr) < 0) {
-            goto error_cleanup;
-        }
-    }
-
     /* Set 'self' argument, for class methods we use the class */
     if (flags & PyObjCSelector_kCLASS_METHOD) {
         if (PyObjCObject_Check(self)) {
             }
         }
     }
-    /* XXX: Ronald: why the XXX? */
-
 
     useStret = 0;
 
 
     if (variadicAllArgs) {
         if (PyObjCFFI_FreeByRef(Py_SIZE(methinfo)+PyTuple_Size(args), byref, byref_attr) < 0) {
-            byref = NULL; byref_attr = NULL;
             goto error_cleanup;
         }
 
     } else {
         if (PyObjCFFI_FreeByRef(Py_SIZE(methinfo), byref, byref_attr) < 0) {
-            byref = NULL; byref_attr = NULL;
             goto error_cleanup;
         }
     }
         result = NULL;
     }
 
-    if (variadicAllArgs) {
-        if (PyObjCFFI_FreeByRef(PyTuple_Size(args), byref, byref_attr) < 0) {
-            byref = NULL; byref_attr = NULL;
-            goto error_cleanup;
-        }
+    if (methinfo->shortcut_signature) {
+        /* pass */
+    } else if (variadicAllArgs) {
+        PyObjCFFI_FreeByRef(PyTuple_Size(args), byref, byref_attr);
 
     } else {
-        if (PyObjCFFI_FreeByRef(Py_SIZE(methinfo), byref, byref_attr) < 0) {
-            byref = NULL; byref_attr = NULL;
-            goto error_cleanup;
-        }
+        PyObjCFFI_FreeByRef(Py_SIZE(methinfo), byref, byref_attr);
     }
 
     if (argbuf) {

pyobjc-core/Modules/objc/method-signature.h

     PyObject_VAR_HEAD
 
     const char* signature;
-    int arrayArg;
     unsigned char variadic:1;
     unsigned char null_terminated_array:1;
     unsigned char free_result:1;
+    unsigned char shortcut_signature:1;
+    unsigned int shortcut_argbuf_size:12;
+    int16_t arrayArg;
     PyObject* suggestion;
     struct _PyObjC_ArgDescr rettype;
     struct _PyObjC_ArgDescr argtype[1];

pyobjc-core/Modules/objc/method-signature.m

 
 static PyObjCMethodSignature* new_methodsignature(const char*);
 
+
 static PyObject*
 sig_str(PyObject* _self)
 {
     .tp_flags       = Py_TPFLAGS_DEFAULT,
 };
 
+static void determine_if_shortcut(PyObjCMethodSignature* methinfo)
+{
+    /* TODO:
+     * Set shortcut_signature and shortcut_argbuf_size if appropriate,
+     * clear otherwise
+     *
+     * These should be set if all arguments are basic types (no functions, no byreference, ...)
+     * and method parts of the function setup code can be skipped.
+     *
+     * Note that shortcut_argbuf_size has a limited size, this will also not work when
+     * there are a lot, or large, arguments/return values.
+     */
+    Py_ssize_t byref_in_count = 0, byref_out_count = 0, plain_count = 0, argbuf_len = 0;
+    BOOL variadic_args = NO;
+
+    methinfo->shortcut_signature = NO;
+    methinfo->shortcut_argbuf_size = 0;
+
+    if (methinfo->variadic) {
+        return;
+    }
+
+    int r = PyObjCFFI_CountArguments(
+            methinfo, 0, &byref_in_count, &byref_out_count, &plain_count, &argbuf_len, &variadic_args);
+    if (r == -1) {
+        PyErr_Clear();
+        return;
+    }
+
+    if (byref_in_count || byref_out_count) {
+        /* TODO: simple pass-by-reference objects args should work (NSError** arguments) */
+        return;
+    }
+
+    if (argbuf_len >= 1 << 12) {
+        return;
+    }
+
+    if (variadic_args) {
+        return;
+    }
+
+    methinfo->shortcut_signature = YES;
+    methinfo->shortcut_argbuf_size = (unsigned int)argbuf_len;
+}
 
 static PyObjCMethodSignature*
 new_methodsignature(const char* signature)
     retval->suggestion = NULL;
     retval->variadic = NO;
     retval->free_result = NO;
+    retval->shortcut_signature = NO;
+    retval->shortcut_argbuf_size = 0;
     retval->null_terminated_array = NO;
     retval->signature = PyObjCUtil_Strdup(signature);
     if (retval->signature == NULL) {
     }
     Py_SIZE(retval) = nargs;
 
+    determine_if_shortcut(retval);
     return retval;
 }
 
             }
             Py_XDECREF(av);
         }
+
     }
 
 
     }
 
     if (!metadata) {
-        return methinfo;
+        goto done;
     }
 
 
                     && (methinfo->arrayArg == -1)) {
             for (i = 0; i < Py_SIZE(methinfo); i++) {
                 if (methinfo->argtype[i].printfFormat) {
-                    return methinfo;
+                    goto done;
                 }
             }
 
         }
     }
 
+done:
+    determine_if_shortcut(methinfo);
     return methinfo;
 }
 

pyobjc-core/Modules/objc/objc_support.h

 extern int depythonify_c_return_array_nullterminated(const char* rettype, PyObject* arg, void* resp, BOOL already_retained, BOOL already_cfretained);
 
 
-extern Py_ssize_t PyObjCRT_SizeOfReturnType(const char* type);
-extern Py_ssize_t PyObjCRT_SizeOfType(const char *type);
-extern Py_ssize_t PyObjCRT_AlignOfType(const char *type);
+extern Py_ssize_t PyObjCRT_SizeOfReturnType(const char* type) __attribute__((__pure__));
+extern Py_ssize_t PyObjCRT_SizeOfType(const char *type) __attribute__((__pure__));
+extern Py_ssize_t PyObjCRT_AlignOfType(const char *type) __attribute__((__pure__));
 extern const char *PyObjCRT_SkipTypeSpec (const char *type);
 extern const char* PyObjCRT_NextField(const char *type);
 extern const char* PyObjCRT_SkipTypeQualifiers (const char* type);
-extern Py_ssize_t PyObjCRT_AlignedSize (const char *type);
+extern Py_ssize_t PyObjCRT_AlignedSize (const char *type) __attribute__((__pure__));
 
 
 extern const char* PyObjCRT_RemoveFieldNames(char* buf, const char* type);
 static inline PyObject*
 PyObjC_IdToPython(id value)
 {
-    PyObject* res;
-
-    res = pythonify_c_value(@encode(id), &value);
-    return res;
+    return pythonify_c_value(@encode(id), &value);
 }
 
 #endif /* _objc_support_H */

pyobjc-core/Tools/pyobjcbench.py

+#!/usr/bin/env python
+"""
+Script for gathering performance statistics.
+
+This is very much a work in progress, and will morph into a tool
+simular to pybench, but just measuring the speed of a number of
+PyObjC primitives:
+
+* For methods, functions and blocks:
+
+  - Call with 0, 1, 5 simple arguments
+  - Same with by reference arguments
+
+* For methods:
+  - Attribute lookup
+  - Attribute lookup + call (e.g. how they are usually called)
+
+* objc.repythonify for a number of types
+
+"""
+from __future__ import print_function
+import objc
+import timeit
+
+NSObject = objc.lookUpClass("NSObject")
+NSArray = objc.lookUpClass("NSArray")
+
+print("NSObject description lookup:", timeit.timeit(setup='import objc; NSObject = objc.lookUpClass("NSObject"); o = NSObject.alloc().init()', stmt='o.description'))
+print("NSArray description lookup: ", timeit.timeit(setup='import objc; NSArray = objc.lookUpClass("NSArray"); o = NSArray.alloc().init()', stmt='o.description'))
+print("NSObject description call:  ", timeit.timeit(setup='import objc; NSObject = objc.lookUpClass("NSObject"); m = NSObject.alloc().init().description', stmt='m()'))
+print("NSArray description call:   ", timeit.timeit(setup='import objc; NSArray = objc.lookUpClass("NSArray"); m = NSArray.alloc().init().description', stmt='m()'))