J5’s Blog

January 6, 2011

Cleaning up invoke with a caching layer

Filed under: Gnome, performance — J5 @ 7:32 pm

I’ve gotten pretty far with the new caching layer for PyGObject. Nothing compiles yet but the skeleton code is sitting on the invoke-rewrite branch. Right now I am working with the “in” marshallers and validators. I came to the conclusion that validation and marshalling are really intertwined so it is much clearer to do the validation during the marshalling step. So now since I am breaking things up into cachable functions, this hot mess:

UINT8 validation

        case GI_TYPE_TAG_UINT8:
            /* UINT8 types can be characters */
            if (PYGLIB_PyBytes_Check(object)) {
                if (PYGLIB_PyBytes_Size(object) != 1) {
                    PyErr_Format (PyExc_TypeError, "Must be a single character");
                    retval = 0;
                    break;
                }

                break;
            }
        case GI_TYPE_TAG_INT8:
        case GI_TYPE_TAG_INT16:
        case GI_TYPE_TAG_UINT16:
        case GI_TYPE_TAG_INT32:
        case GI_TYPE_TAG_UINT32:
        case GI_TYPE_TAG_INT64:
        case GI_TYPE_TAG_UINT64:
        case GI_TYPE_TAG_FLOAT:
        case GI_TYPE_TAG_DOUBLE:
        {
            PyObject *number, *lower, *upper;

            if (!PyNumber_Check (object)) {
                PyErr_Format (PyExc_TypeError, "Must be number, not %s",
                              object->ob_type->tp_name);
                retval = 0;
                break;
            }

            if (type_tag == GI_TYPE_TAG_FLOAT || type_tag == GI_TYPE_TAG_DOUBLE) {
                number = PyNumber_Float (object);
            } else {
                number = PYGLIB_PyNumber_Long (object);
            }

            _pygi_g_type_tag_py_bounds (type_tag, &lower, &upper);

            if (lower == NULL || upper == NULL || number == NULL) {
                retval = -1;
                goto check_number_release;
            }

            /* Check bounds */
            if (PyObject_RichCompareBool (lower, number, Py_GT)
                    || PyObject_RichCompareBool (upper, number, Py_LT)) {
                PyObject *lower_str;
                PyObject *upper_str;

                if (PyErr_Occurred()) {
                    retval = -1;
                    goto check_number_release;
                }

                lower_str = PyObject_Str (lower);
                upper_str = PyObject_Str (upper);
                if (lower_str == NULL || upper_str == NULL) {
                    retval = -1;
                    goto check_number_error_release;
                }

#if PY_VERSION_HEX < 0x03000000
                PyErr_Format (PyExc_ValueError, "Must range from %s to %s",
                              PyString_AS_STRING (lower_str),
                              PyString_AS_STRING (upper_str));
#else
                {
                    PyObject *lower_pybytes_obj = PyUnicode_AsUTF8String (lower_str);
                    if (!lower_pybytes_obj)
                        goto utf8_fail;

                    PyObject *upper_pybytes_obj = PyUnicode_AsUTF8String (upper_str);
                    if (!upper_pybytes_obj) {
                        Py_DECREF(lower_pybytes_obj);
                        goto utf8_fail;
                    }

                    PyErr_Format (PyExc_ValueError, "Must range from %s to %s",
                                  PyBytes_AsString (lower_pybytes_obj),
                                  PyBytes_AsString (upper_pybytes_obj));
                    Py_DECREF (lower_pybytes_obj);
                    Py_DECREF (upper_pybytes_obj);
                }
utf8_fail:
#endif
                retval = 0;

check_number_error_release:
                Py_XDECREF (lower_str);
                Py_XDECREF (upper_str);
            }

check_number_release:
            Py_XDECREF (number);
            Py_XDECREF (lower);
            Py_XDECREF (upper);
            break;
        }

UINT8 marshalling

        case GI_TYPE_TAG_UINT8:
            if (PYGLIB_PyBytes_Check(object)) {
                arg.v_long = (long)(PYGLIB_PyBytes_AsString(object)[0]);
                break;
            }

        case GI_TYPE_TAG_INT8:
        case GI_TYPE_TAG_INT16:
        case GI_TYPE_TAG_UINT16:
        case GI_TYPE_TAG_INT32:
        {
            PyObject *int_;

            int_ = PYGLIB_PyNumber_Long (object);
            if (int_ == NULL) {
                break;
            }

            arg.v_long = PYGLIB_PyLong_AsLong (int_);

            Py_DECREF (int_);

            break;
        }

becomes this easier to read function:

UINT8 cached marshalling and validation function

gboolean
_pygi_marshal_in_uint8 (PyGIState         *state,
                        PyGIFunctionCache *function_cache,
                        PyGIArgCache      *arg_cache,
                        PyObject          *py_arg,
                        GIArgument        *arg)
{
    long long_;

    if (PYGLIB_PyBytes_Check(py_arg)) { 

        if (PYGLIB_PyBytes_Size(py_arg) != 1) {
            PyErr_Format (PyExc_TypeError, "Must be a single character");
            return FALSE;
        }

        long_ = (long)(PYGLIB_PyBytes_AsString(py_arg)[0]);

    } else if (PyNumber_Check(py_arg)) {
        PyObject *py_long;
        py_long = PYGLIB_PyNumber_Long(py_arg);
        if (!py_long)
            return FALSE;

        long_ = PYGLIB_PyLong_AsLong(py_long);
        Py_DECREF(py_long);

        if (PyErr_Occured())
            return FALSE;
    } else {
        PyErr_Format (PyExc_TypeError, "Must be number or single byte string, not %s",
                      py_arg->ob_type->tp_name);
        return FALSE;
    }

    if (long_ < 0 || long_ > 255) {
        PyErr_Format (PyExc_ValueError, "%li not in range %i to %i", long_, 0, 255);
        return FALSE;
    }

    arg.v_long = long_;

    return TRUE;
}

Notice the if (long_ < 0 || long_ > 255) check. With the previous version we were actually converting the ranges to python objects and comparing them via the python RichCompare interface. Now since we have to decode the value anyway, we can just do a simple C comparison which is an order of magnitude faster. This doesn't even show some of the areas where we are decoding twice because of the split validation and marshalling routines. In any case ... progress ... though we won't know how much until I have implemented enough to get it running the test suite.

Other advantage come from reorganizing invoke including preliminary support for default values. I added a way for user_data to default to NULL if not passed in by the user. It is designed in such a way that any default value can be added to an argument cache and invoke should be able to substitute it. This means that once GI adds a way to specify default values we simply need to marshal them into the the corresponding cache and it should just work. Another enhancement that should be easy to add later is keyword argument support. Since we are normalizing the data in the cache, theoretically one should be able to pick a random argument and marshal it without having to process the argument list linearly. That however will be a later feature.

[read this post in: ar de es fr it ja ko pt ru zh-CN ]

1 Comment

  1. Nice work!

    Comment by Philip Withnall — January 6, 2011 @ 7:53 pm

RSS feed for comments on this post.

Sorry, the comment form is closed at this time.

Powered by WordPress