By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
444,190 Members | 1,503 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 444,190 IT Pros & Developers. It's quick & easy.

Accessing =?utf-8?b?X19zbG90c19f?= from C

P: n/a
Hi,

I'd like to be able to access an attribute of a particular Python
object as fast as possible from some C code.

I wondered if using __slots__ to store the attribute would allow me to
do this in a faster way.

The reason I'd like to do this is because I need to access the
attribute inside a loop within some C code, and I find that the
attribute lookup using the 'PyObject_GetAttrString' call is far slower
than any of the subsequent calculations I perform in C.

Using the 'PyObject_GetAttrString' function to get the attribute, I
find it is slightly faster when the attribute is a slot than when it
isn't, but that the attribute lookup remains the performance-limiting
factor.

Additionally, if I instead build a list of the attributes first and
pass that to the C code, using the 'PyList_GetItem' function to access
each item, the performance improves significantly. I'd rather be able
to access the information from C, instead of building the list
externally.

As far as I can tell, nothing is documented about accessing __slot__
members in a special way from C, but perhaps someone knows how to do
it anyway?

To be more specific, below is an example of what I'm talking about. I
use SciPy's weave to inline C code, but I assume that doesn't make any
difference to my problem.

Thanks for any suggestions,
Chris

class MyObject(object):

__slots__ = ['attr_one']

def __init__(self,attr_one=1.0):
self.attr_one = attr_one
import weave
def test_loop(myobjects):

rows,cols = len(myobjects),len(myobjects[0])

code = """
for (int r=0; r<rows; ++r) {
PyObject *myobjects_row = PyList_GetItem(myobjects,r);
for (int l=0; l<cols; ++l) {
PyObject *myobject = PyList_GetItem(myobjects_row,l);
// any faster way when attr_one is a slot?
PyObject *attr_one_obj=PyObject_GetAttrString(myobject,"att r_one");

// more computations involving attr_one; just print for now...
double attr_one = PyFloat_AsDouble(attr_one_obj);
printf("%f\\n",attr_one);
}
}
"""
weave.inline(code,['myobjects','rows','cols'],local_dict=locals(),verbose=1)
test_list = [[MyObject(0.0),MyObject(1.0)],[MyObject(2.0),MyObject(3.0)]]

test_loop(test_list)
Sep 10 '08 #1
Share this Question
Share on Google+
10 Replies


P: n/a
[ You can use the capi-sig for questions like this; see
http://mail.python.org/mailman/listinfo/capi-sig ]

Chris <ce****@users.sourceforge.netwrites:
I'd like to be able to access an attribute of a particular Python
object as fast as possible from some C code.

I wondered if using __slots__ to store the attribute would allow me to
do this in a faster way.

The reason I'd like to do this is because I need to access the
attribute inside a loop within some C code, and I find that the
attribute lookup using the 'PyObject_GetAttrString' call is far slower
than any of the subsequent calculations I perform in C.
PyObject_GetAttrString is convenient, but it creates a Python string
only so it can intern it (and in most cases throw away the freshly
created version). For maximum efficiency, pre-create the string
object using PyString_InternFromString, and use that with
PyObject_GetAttr.
Sep 11 '08 #2

P: n/a
Hrvoje Niksic <hniksic <atxemacs.orgwrites:
....
[ You can use the capi-sig for questions like this; see
http://mail.python.org/mailman/listinfo/capi-sig ]
Thanks, I had no idea about that.
PyObject_GetAttrString is convenient, but it creates a Python string
only so it can intern it (and in most cases throw away the freshly
created version). For maximum efficiency, pre-create the string
object using PyString_InternFromString, and use that with
PyObject_GetAttr.
Yes, we'd thought of that too, but it doesn't seem to be an important
factor compared to the actual attribute lookup.

Thanks for the advice,
Chris

Sep 11 '08 #3

P: n/a
On Sep 10, 7:41 am, Chris <ceb...@users.sourceforge.netwrote:
Hi,

I'd like to be able to access an attribute of a particular Python
object as fast as possible from some C code.

I wondered if using __slots__ to store the attribute would allow me to
do this in a faster way.

The reason I'd like to do this is because I need to access the
attribute inside a loop within some C code, and I find that the
attribute lookup using the 'PyObject_GetAttrString' call is far slower
than any of the subsequent calculations I perform in C.

Using the 'PyObject_GetAttrString' function to get the attribute, I
find it is slightly faster when the attribute is a slot than when it
isn't, but that the attribute lookup remains the performance-limiting
factor.
You can determine the offset the of the slot in the object structure
by
querying the member descriptor of the type object.

descr = GetAttrString(cls,"varname");
offset = descr->d_member->offset;
slotvar = (PyObject*)(((char*)obj)+offset)

There might be some macros to simplify this.

Use at your own risk.
Carl Banks
Sep 11 '08 #4

P: n/a
Carl Banks <pavlovevidence <atgmail.comwrites:
....
You can determine the offset the of the slot in the object structure
by
querying the member descriptor of the type object.
That sounds like just the kind of thing we were looking for - thanks!
descr = GetAttrString(cls,"varname");
offset = descr->d_member->offset;
slotvar = (PyObject*)(((char*)obj)+offset)
Unfortunately, I am inexperienced at this kind of thing, so I wasn't
able to get something working. Maybe someone could tell me what's
wrong with the code below (it gives the error "'struct _object' has no
member named 'd_member'")?

PyObject *descr = PyObject_GetAttrString(x,"attr_one");
int offset = descr->d_member->offset;
PyObject* slotvar = (PyObject*)(((char*)obj)+offset);

where x is the class and attr_one is a slot (the full example is
appended to this message). I guessed the type of offset; I'm not sure
what it should be.

I couldn't find any information about d_member on the web.

There might be some macros to simplify this.
Sorry to say that I also have no idea about where to find such macros!
Maybe I should continue this thread on capi-sig?
Thanks for your help,
Chris
class MyObject(object):

__slots__ = ['attr_one']

def __init__(self,attr_one=1.0):
self.attr_one = attr_one

import weave
def test():

x = MyObject

code = """
PyObject *descr = PyObject_GetAttrString(x,"attr_one");
int offset = descr->d_member->offset;
//PyObject* slotvar = (PyObject*)(((char*)obj)+offset);
"""
weave.inline(code,['x'],local_dict=locals(),verbose=1)

test()
Sep 12 '08 #5

P: n/a
Hrvoje Niksic <hniksic <atxemacs.orgwrites:
....
Chris <ceball <atusers.sourceforge.netwrites:
PyObject_GetAttrString is convenient, but it creates a Python string
only so it can intern it (and in most cases throw away the freshly
created version). For maximum efficiency, pre-create the string
object using PyString_InternFromString, and use that with
PyObject_GetAttr.
Yes, we'd thought of that too, but it doesn't seem to be an
important factor compared to the actual attribute lookup.

I'd like to see your test code.
Thanks for your interest in this. My test code is a whole function
that's part of a big simulator, unfortunately! I need to use the data
structures created by the simulator as part of the testing. While the
source is freely available*, that doesn't make it easy for others to
run the tests...

In my experience, as long as you're
accessing simple slots, you should notice a difference.
(I'm not sure what you mean by a 'simple slot'. The slot we're
accessing is a numpy array.)

Sorry I wasn't clear before - we do notice a difference, but not as
big a difference as when we access the attributes (arrays) from a
pre-built list. Below are timings from running the simulator
(i.e. calling the function in question many times) using the three
approaches (GetAttrString, GetAttr, and instead using a list and
GetItem; times outside parentheses are from a stopwatch; times in
parentheses are from Python's cProfile module):
- GetAttrString: 55 seconds (58 seconds)
inside the loop:
PyObject *weights_obj = PyObject_GetAttrString(obj,"attr_one");
- GetAttr: 46 seconds (46 seconds)
outside the loop:
PyObject *name = PyString_FromString("attr_one");

inside the loop:
PyObject *obj = PyObject_GetAttr(obj,name);
- PyList_GetItem: 35 seconds (37 seconds)
So, of course, you are right to say that we should notice a
difference! But speed is critical for us here.

Incidentally, what we have is a simulator written entirely in Python,
but we also provide optimized C versions of some parts of it. These C
parts must be entirely optional.

Here is a test program that shows a 4.6 time speedup simply by
switching from PyObject_GetAttrString to PyObject_GetAttr:
Thanks for the illustration. While I didn't run your code myself,
I did try to study it. Illustrations like that are very helpful.
Chris
* from http://topographica.org/

Sep 12 '08 #6

P: n/a
Chris <ce****@users.sourceforge.netwrites:
>In my experience, as long as you're
accessing simple slots, you should notice a difference.

(I'm not sure what you mean by a 'simple slot'.
I mean a slot defined by __slots__ = slot1, slot2, slot3, ..., without
descriptors or specific __getattribute__ code on top of that.
- GetAttr: 46 seconds (46 seconds)
outside the loop:
PyObject *name = PyString_FromString("attr_one");
I think you should be using PyString_InternFromString, but it will
probably still be slower than PyList_GET_ITEM, which is still much
simpler. The next optimization is the one proposed by Carl, which
should yield speed comparable to PyList_GET_ITEM. See my response in
that subthread for details.
Sep 12 '08 #7

P: n/a
Chris <ce****@users.sourceforge.netwrites:
>descr = GetAttrString(cls,"varname");
offset = descr->d_member->offset;
slotvar = (PyObject*)(((char*)obj)+offset)

Unfortunately, I am inexperienced at this kind of thing, so I wasn't
able to get something working. Maybe someone could tell me what's
wrong with the code below (it gives the error "'struct _object' has no
member named 'd_member'")?
You are getting that error because Carl forgot to cast the descriptor
to the appropriate C type, in this case PyMemberDescrObject. The last
line is also incorrect, I think. Try something like this:

PyObject *descr = PyObject_GetAttrString(x,"attr_one");
int offset = ((PyMemberDescrObject *) descr)->d_member->offset;
PyObject *slotvar = *(PyObject **)(((char *) obj) + offset);
Sep 12 '08 #8

P: n/a
Hrvoje Niksic <hn*****@xemacs.orgwrites:
Chris <ce****@users.sourceforge.netwrites:
>>descr = GetAttrString(cls,"varname");
offset = descr->d_member->offset;
slotvar = (PyObject*)(((char*)obj)+offset)

Unfortunately, I am inexperienced at this kind of thing, so I wasn't
able to get something working. Maybe someone could tell me what's
wrong with the code below (it gives the error "'struct _object' has no
member named 'd_member'")?

You are getting that error because Carl forgot to cast the descriptor
to the appropriate C type, in this case PyMemberDescrObject. The last
line is also incorrect, I think. Try something like this:

PyObject *descr = PyObject_GetAttrString(x,"attr_one");
Also note that x should be your object's type, i.e. o->ob_type cast to
PyObject *. The complete incantation would be:

// outside the loop
PyObject *descr = PyObject_GetAttrString((PyObject *) obj->ob_type,
"attr_one");
if (!descr)
return NULL;
int offset = ((PyMemberDescrObject *) descr)->d_member->offset;
Py_DECREF(descr);

// inside the loop
PyObject *value = *(PyObject **)((char *)obj + offset);
if (!value) {
PyErr_SetString(PyExc_AttributeError, "attribute missing");
return NULL;
}
// Remember to Py_INCREF(value) before using it and Py_DECREF it after
// using it. Otherwise if the object changes its slot to something
// else, you might be using a dead object.

With this code, accessing the value is practically free once the
offset is calculated. Here is a bench3 function updated to use this
strategy:

static PyObject *
bench3(PyObject *ignored, PyObject *obj)
{
PyObject *descr = PyObject_GetAttrString((PyObject *) obj->ob_type,
"abcdef");
if (!descr)
return NULL;

// Here you should also assert that PyObject_TypeCheck(descr,
// PyMemberDescr_Type) holds true. Since PyMemberDescr_Type is static,
// you'll have to get it by stashing away ob_type of a value known to be
// the descriptor. This is left as excercise to the reader.

int offset = ((PyMemberDescrObject *) descr)->d_member->offset;
Py_DECREF(descr);

int i;
PyObject *volatile attr; // 'volatile' for benchmarking, prevents gcc
// from optimizing away the loop
for (i = 0; i < 1000000; i++) {
attr = *(PyObject **)((char *)obj + offset);
}
Py_INCREF(attr);
return attr; // to prove that we retrieved the correct value
}
>>t0 = time.time(); attr.bench3(o); t1 = time.time()
1
>>t1-t0
0.00071597099304199219 # for 1,000,000 iterations, as cheap as it gets
Sep 12 '08 #9

P: n/a
On Sep 12, 10:01*am, Hrvoje Niksic <hnik...@xemacs.orgwrote:
Chris <ceb...@users.sourceforge.netwrites:
descr = GetAttrString(cls,"varname");
offset = descr->d_member->offset;
slotvar = (PyObject*)(((char*)obj)+offset)
Unfortunately, I am inexperienced at this kind of thing, so I wasn't
able to get something working. Maybe someone could tell me what's
wrong with the code below (it gives the error "'struct _object' has no
member named 'd_member'")?

You are getting that error because Carl forgot to cast the descriptor
to the appropriate C type, in this case PyMemberDescrObject. *The last
line is also incorrect, I think.
Yep, was in too much of a hurry to waste too much time showing how to
do something slightly dubious.

The offsetof() macro would help for the third line (in fact, it's the
recommended standard way since there are some C implmentions,
somewhere, that the pointer artithmetic method fails).
Carl Banks
Sep 12 '08 #10

P: n/a
Hrvoje Niksic <hniksic <atxemacs.orgwrites:
....
You are getting that error because Carl forgot to cast the descriptor
to the appropriate C type, in this case PyMemberDescrObject. The last
line is also incorrect, I think. Try something like this:
....

The code you gave was great, thanks! Now we have done exactly what we
wanted, and have fast access from C to two particular attributes of
instances of a particular class.

We use that fast access in optimized versions of pure-Python
components of our simulator.

Chris
Sep 16 '08 #11

This discussion thread is closed

Replies have been disabled for this discussion.