On Tue, Apr 30, 2013 at 8:06 PM, Michael Bayer <mike...@zzzcomputing.com> wrote:
>>    try:
>>        from sqlalchemy.cinstrumented import InstrumentedGetter
>>        __get__ = InstrumentedGetter(globals())
>>        __get__.__name__ = '__get__'
>>        del InstrumentedGetter
>>    except ImportError:
>>        def __get__(self, instance, owner):
>>            if instance is None:
>>                return self
>>
>>            dict_ = instance_dict(instance)
>>            if self._supports_population and self.key in dict_:
>>                return dict_[self.key]
>>            else:
>>                return self.impl.get(instance_state(instance),dict_)
>>
>> Thing is, doing the whole class in C makes no sense for set and
>> delete, but it also complicates linking its instance_dict and
>> instance_state to SA.attribute's.
>>
>> This way looks ugly, but it reacts immediately to changing those
>> globals, so it does seem like the better option.
>>
>> Opinions (while I profile)?
>
> I'd want to see the whole thing, like what's up with that globals() call, 
> etc.   The instance_dict is shuttled around everywhere so that we aren't 
> constantly pulling it from the given object; we have a system in place 
> whereby the fact that instance_dict is object.__dict__ is not necessarily a 
> given, and you can actually use some other system of getting at __dict__.   
> It saved us on a huge number of function calls at some point to expand it out 
> like that, as inconvenient as it is.

I realize that. That globals call is exactly for that.

Well, without going into the C code, the python code does:

dict_ = instance_dict(instance)

That under the hood means:

dict_ = globals()['instance_dict'](instance)

Well, globals there is bound syntactically in python code, but C code
has no globals, and injecting instance_dict into cinstrumented's
module dict sounded like extra complexity for no reason.

Importing attributes from cinstrumented is also no good, since at the
point the InstrumentedGetter is constructed, attribute isn't on
sys.modules, and doing it on invocation would have meant extra
overhead.

So, that globals call is to mimic python's syntactic binding to the
module's global dict, at import time, and be able to query the dict
and find instance_dict no matter how it's modified later.

Afterward, the getter works more or less like this (in C):

def __get__(self, instance, owner):
    if instance is None:
       return self
    if self.cached_instance_dict is not None \
       and self.cached_instance_dict is instance_dict \
       and self.cached_supports_population \
       and hasattr(instance, '__dict__'):
        return instance.__dict__[self.key]
    else:
       self.cached_supports_population = self._supports_population
       self.cached_instance_dict = None
       dict_ = instance_dict(instance)
       if dict_ is instance.__dict__:
           self.cached_instance_dict = instance_dict
       return self.impl.get(instance_state(instance), dict_)

Well, in spite of being more complicated, those self.cache_blah things
are really fast since they just compare pointers in C, and, more
importantly, entity.column will invoke this code from CPython's eval
loop (C) directly to the descriptor's getter (C), in no way incurring
python's frame allocation overhead.

I'm attaching the C module in case it clarifies.

I'm not entirely sure about the garbage collection part yet... so it's
not final.

-- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sqlalchemy+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at http://groups.google.com/group/sqlalchemy?hl=en.
For more options, visit https://groups.google.com/groups/opt_out.


/*
instrumented.c
Copyright (C) 2013 Claudio Freire klaussfre...@gmail.com

This module is part of SQLAlchemy and is released under
the MIT License: http://www.opensource.org/licenses/mit-license.php
*/

#include <Python.h>

#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN)
typedef int Py_ssize_t;
#define PY_SSIZE_T_MAX INT_MAX
#define PY_SSIZE_T_MIN INT_MIN
typedef Py_ssize_t (*lenfunc)(PyObject *);
#define PyInt_FromSsize_t(x) PyInt_FromLong(x)
typedef intargfunc ssizeargfunc;
#endif

#if PY_VERSION_HEX >= 0x03000000
#define PyString_InternFromString PyUnicode_InternFromString
#endif


PyObject *get_string = NULL;
PyObject *uget_string = NULL;

/***********
 * Structs *
 ***********/

typedef struct {
    PyObject_HEAD

    /* Where to get instance_dict from */
    PyObject* globals;

    /* Name to which it was bound */
    PyObject* name;
    
    /* non-reference, just a pointer for identity comparison */
    void *cached_instance_dict;
    
    /* Only valid if cached_instance_dict != NULL and equal to global instance_dict */
    int cached_supports_population;
} InstrumentedGetter;

/**********************
 * InstrumentedGetter *
 **********************/

static int
InstrumentedGetter_init(InstrumentedGetter *self, PyObject *args, PyObject *kwds)
{
    PyObject *globals;

    if (!PyArg_UnpackTuple(args, "InstrumentedGetter", 1, 1,
                           &globals))
        return -1;

    Py_INCREF(globals);
    self->globals = globals;
    
    Py_INCREF(uget_string);
    self->name = uget_string;

    self->cached_instance_dict = NULL;

    return 0;
}

/* Bind to an object */
static PyObject *
InstrumentedGetter_descr_get(PyObject *func, PyObject *obj, PyObject *type)
{
    if (obj == Py_None)
        obj = NULL;
    return PyMethod_New(func, obj, type);
}

static PyObject *
InstrumentedGetter_get_name(InstrumentedGetter *op)
{
    Py_INCREF(op->name);
    return op->name;
}

static int
InstrumentedGetter_set_name(InstrumentedGetter *op, PyObject *value)
{
    PyObject *tmp;

    /* Not legal to del name or to set it to anything
     * other than a string object. */
    if (value == NULL || !PyString_Check(value)) {
        PyErr_SetString(PyExc_TypeError,
                        "__name__ must be set to a string object");
        return -1;
    }
    tmp = op->name;
    Py_INCREF(value);
    op->name = value;
    Py_DECREF(tmp);
    return 0;
}

static PyObject*
InstrumentedGetter_call(InstrumentedGetter *self, PyObject *args, PyObject *kwds)
{
    PyObject *me, *instance, *owner, *instance_dict, *basic_dict, *key, *rv, *x;

    if (!PyArg_UnpackTuple(args, "InstrumentedGetter", 3, 3,
                           &me, &instance, &owner))
        return NULL;

    if (instance == NULL || instance == Py_None) {
        Py_INCREF(me);
        return me;
    }

    /* Check dict */
    instance_dict = PyMapping_GetItemString(self->globals, "instance_dict");
    if (instance_dict == NULL)
        return NULL;
    
    key = PyObject_GetAttrString(me, "key");
    if (key == NULL) {
        /* Um... bad... */
        Py_DECREF(instance_dict);
        return NULL;
    }

    basic_dict = PyObject_GetAttrString(instance, "__dict__");
    if (basic_dict == NULL) {
        /* No problem, we'll fall back to the generic implementation anyway */
        PyErr_Clear();
    }

    if (   basic_dict != NULL
        && self->cached_instance_dict != NULL 
        && self->cached_instance_dict == instance_dict
        && self->cached_supports_population )
    {
        rv = PyObject_GetItem(basic_dict, key);
        if (rv == NULL) {
            /* OOps */
            PyErr_Clear();
            goto generic;
        }
    }
    else {
    generic:
        if ((x = PyObject_GetAttrString(me, "_supports_population")) == NULL) {
            rv = NULL;
            goto err;
        }
        else {
            self->cached_instance_dict = NULL;
            self->cached_supports_population = PyObject_IsTrue(x);
            Py_DECREF(x);
        }

        if (self->cached_supports_population) {
            x = PyObject_CallFunctionObjArgs(instance_dict, instance, NULL);
            if (x == NULL) {
                rv = NULL;
                goto err;
            }
            else {
                if (x == basic_dict) {
                    self->cached_instance_dict = instance_dict;
                }
                Py_XDECREF(basic_dict);
                basic_dict = x;
            }

            rv = PyObject_GetItem(basic_dict, key);
            if (rv == NULL) {
                /* Ignore exception, will fall back to impl */
                PyErr_Clear();
            }
        }
        else {
            rv = NULL;
            x = PyObject_CallFunctionObjArgs(instance_dict, instance, NULL);
            if (x == NULL) {
                goto err;
            }
            else {
                Py_XDECREF(basic_dict);
                basic_dict = x;
            }
        }
        if (rv == NULL) {
            /* Fall back to impl */
            x = PyObject_GetAttrString(me, "impl");
            if (x != NULL) {
                PyObject *instance_state;
                instance_state = PyMapping_GetItemString(self->globals, "instance_state");
                if (instance_state != NULL) {
                    PyObject *state;
                    state = PyObject_CallFunctionObjArgs(instance_state, instance, NULL);
                    if (state != NULL) {
                        rv = PyObject_CallMethodObjArgs(x, get_string, state, basic_dict, NULL);
                        Py_DECREF(state);
                    }
                    Py_DECREF(instance_state);
                }
                Py_DECREF(x);
            }
        }
    }

err:
    Py_DECREF(instance_dict);
    Py_DECREF(key);
    Py_XDECREF(basic_dict);
    return rv;
}

static void
InstrumentedGetter_dealloc(InstrumentedGetter *self)
{
    Py_XDECREF(self->globals);
    self->ob_type->tp_free((PyObject *)self);
}

static int
InstrumentedGetter_traverse(InstrumentedGetter *self, visitproc visit, void *arg)
{
    Py_VISIT(self->globals);
    return 0;
}

static int
InstrumentedGetter_clear(InstrumentedGetter *self)
{
    Py_CLEAR(self->globals);
    return 0;
}

static PyGetSetDef InstrumentedGetter_getsetlist[] = {
    {"__name__", (getter)InstrumentedGetter_get_name, (setter)InstrumentedGetter_set_name},
    {NULL} /* Sentinel */
};

static PyTypeObject InstrumentedGetterType = {
    PyObject_HEAD_INIT(NULL)
    0,                                  /* ob_size */
    "sqlalchemy.cinstrumented.InstrumentedGetter",          /* tp_name */
    sizeof(InstrumentedGetter),         /* tp_basicsize */
    0,                                  /* tp_itemsize */
    (destructor)InstrumentedGetter_dealloc,   /* tp_dealloc */
    0,                                  /* tp_print */
    0,                                  /* tp_getattr */
    0,                                  /* tp_setattr */
    0,                                  /* tp_compare */
    0,                                  /* tp_repr */
    0,                                  /* tp_as_number */
    0,                                  /* tp_as_sequence */
    0,                                  /* tp_as_mapping */
    0,                                  /* tp_hash */
    (ternaryfunc)InstrumentedGetter_call, /* tp_call */
    0,                                  /* tp_str */
    0,                                  /* tp_getattro */
    0,                                  /* tp_setattro */
    0,                                  /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,               /* tp_flags */
    "Stateful callable implementing an optimized instrumented attribute getter.",   /* tp_doc */
    (traverseproc)InstrumentedGetter_traverse, /* tp_traverse */
    (inquiry)InstrumentedGetter_clear,  /* tp_clear */
    0,                                  /* tp_richcompare */
    0,                                  /* tp_weaklistoffset */
    0,                                  /* tp_iter */
    0,                                  /* tp_iternext */
    0,                                  /* tp_methods */
    0,                                  /* tp_members */
    InstrumentedGetter_getsetlist,      /* tp_getset */
    0,                                  /* tp_base */
    0,                                  /* tp_dict */
    InstrumentedGetter_descr_get,       /* tp_descr_get */
    0,                                  /* tp_descr_set */
    0,                                  /* tp_dictoffset */
    (initproc)InstrumentedGetter_init,  /* tp_init */
    0,                                  /* tp_alloc */
    0                                   /* tp_new */
};


#ifndef PyMODINIT_FUNC  /* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif


static PyMethodDef module_methods[] = {
    {NULL, NULL, 0, NULL}        /* Sentinel */
};

PyMODINIT_FUNC
initcinstrumented(void)
{
    PyObject *m;

    InstrumentedGetterType.tp_new = PyType_GenericNew;
    if (PyType_Ready(&InstrumentedGetterType) < 0)
        return;

    m = Py_InitModule3("cinstrumented", module_methods,
                       "Module containing C versions of core ResultProxy classes.");
    if (m == NULL)
        return;

    get_string = PyString_InternFromString("get");
    uget_string = PyString_InternFromString("__get__");

    Py_INCREF(&InstrumentedGetterType);
    PyModule_AddObject(m, "InstrumentedGetter", (PyObject *)&InstrumentedGetterType);
}

Reply via email to