属性アクセスのパフォーマンス
tp_memberだと遅くね?
通常版
bench1.py
class Noddy(object): first = 2 last = 1 noddy = Noddy() d = dict() d["A"] = 1 def test1(): return d["A"] def test2(): return noddy.first if __name__=='__main__': from timeit import Timer t = Timer("test1()", "from __main__ import test1") r1 = t.repeat() print "dict %s " % r1 t = Timer("test2()", "from __main__ import test2") r2 = t.repeat() print "attr %s " % r2 print sum(r1)/ sum(r2)
結果
dict [0.93600106239318848, 0.90631294250488281, 0.9040980339050293]
attr [0.89296197891235352, 0.90040087699890137, 0.94674801826477051]
1.00229960207
通常の属性アクセスはそこそこ速い。辞書引きとほぼ同等。
但しread onlyとかにしたい場合は更に処理が入るので確実に遅くなる。
CでNoddyを作ってみるとどうなるか。
node.c
#include <Python.h> #include "structmember.h" typedef struct { PyObject_HEAD PyObject *first; /* first name */ PyObject *last; /* last name */ int number; } Noddy; static void Noddy_dealloc(Noddy* self) { Py_XDECREF(self->first); Py_XDECREF(self->last); self->ob_type->tp_free((PyObject*)self); } static PyObject * Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { Noddy *self; self = (Noddy *)type->tp_alloc(type, 0); if (self != NULL) { self->first = PyString_FromString("2"); if (self->first == NULL) { Py_DECREF(self); return NULL; } self->last = PyString_FromString(""); if (self->last == NULL) { Py_DECREF(self); return NULL; } self->number = 0; } return (PyObject *)self; } static int Noddy_init(Noddy *self, PyObject *args, PyObject *kwds) { PyObject *first=NULL, *last=NULL, *tmp; static char *kwlist[] = {"first", "last", "number", NULL}; if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist, &first, &last, &self->number)) return -1; if (first) { tmp = self->first; Py_INCREF(first); self->first = first; Py_XDECREF(tmp); } if (last) { tmp = self->last; Py_INCREF(last); self->last = last; Py_XDECREF(tmp); } return 0; } static PyMemberDef Noddy_members[] = { {"first", T_OBJECT_EX, offsetof(Noddy, first), RO, "first name"}, {"last", T_OBJECT_EX, offsetof(Noddy, last), RO, "last name"}, {NULL} /* Sentinel */ }; static PyTypeObject NoddyType = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ "noddy.Noddy", /*tp_name*/ sizeof(Noddy), /*tp_basicsize*/ 0, /*tp_itemsize*/ (destructor)Noddy_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 */ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ "Noddy objects", /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ Noddy_members, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)Noddy_init, /* tp_init */ 0, /* tp_alloc */ Noddy_new, /* tp_new */ }; static PyMethodDef module_methods[] = { {NULL} /* Sentinel */ }; #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ #define PyMODINIT_FUNC void #endif PyMODINIT_FUNC initnoddy(void) { PyObject* m; if (PyType_Ready(&NoddyType) < 0) return; m = Py_InitModule3("noddy", module_methods, "Example module that creates an extension type."); if (m == NULL) return; Py_INCREF(&NoddyType); PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType); }
bench2.py
from noddy import Noddy noddy = Noddy() d = dict() d["A"] = 1 def test1(): return d["A"] def test2(): return noddy.first if __name__=='__main__': from timeit import Timer t = Timer("test1()", "from __main__ import test1") r1 = t.repeat() print "dict %s " % r1 t = Timer("test2()", "from __main__ import test2") r2 = t.repeat() print "attr %s " % r2 print sum(r1)/ sum(r2)
結果
dict [0.99621200561523438, 0.96173810958862305, 0.94377803802490234]
attr [1.1010780334472656, 1.0777368545532227, 1.0918948650360107]
0.887186076519
tp_memberにすると遅くなる。
ROとか制御してる分少し処理が入るからかな。。。
mroのキャッシュには乗っかてるとは思うんだが。。。
高速にアクセスしたいものがわかってるならば直引きしてしまった方がいいな。
tp_getattroで優先引きをしてしまう。
#include <Python.h> #include "structmember.h" typedef struct { PyObject_HEAD PyObject *first; /* first name */ PyObject *last; /* last name */ int number; } Noddy; static void Noddy_dealloc(Noddy* self) { Py_XDECREF(self->first); Py_XDECREF(self->last); self->ob_type->tp_free((PyObject*)self); } static PyObject * Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { Noddy *self; self = (Noddy *)type->tp_alloc(type, 0); if (self != NULL) { self->first = PyString_FromString("2"); if (self->first == NULL) { Py_DECREF(self); return NULL; } self->last = PyString_FromString(""); if (self->last == NULL) { Py_DECREF(self); return NULL; } self->number = 0; } return (PyObject *)self; } static int Noddy_init(Noddy *self, PyObject *args, PyObject *kwds) { PyObject *first=NULL, *last=NULL, *tmp; static char *kwlist[] = {"first", "last", "number", NULL}; if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist, &first, &last, &self->number)) return -1; if (first) { tmp = self->first; Py_INCREF(first); self->first = first; Py_XDECREF(tmp); } if (last) { tmp = self->last; Py_INCREF(last); self->last = last; Py_XDECREF(tmp); } return 0; } static PyMemberDef Noddy_members[] = { {"first", T_OBJECT_EX, offsetof(Noddy, first), RO, "first name"}, {"last", T_OBJECT_EX, offsetof(Noddy, last), RO, "last name"}, {NULL} /* Sentinel */ }; PyObject * _GetAttr(PyObject *obj, PyObject *name) { Noddy *self = (Noddy *)obj; if(strcmp(PyString_AS_STRING(name), "first") == 0){ Py_INCREF(self->first); return self->first; }else{ return PyObject_GenericGetAttr(obj, name); } Py_RETURN_NONE; } static PyTypeObject NoddyType = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ "noddy.Noddy", /*tp_name*/ sizeof(Noddy), /*tp_basicsize*/ 0, /*tp_itemsize*/ (destructor)Noddy_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 */ 0, /*tp_call*/ 0, /*tp_str*/ _GetAttr, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ "Noddy objects", /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ Noddy_members, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)Noddy_init, /* tp_init */ 0, /* tp_alloc */ Noddy_new, /* tp_new */ }; static PyMethodDef module_methods[] = { {NULL} /* Sentinel */ }; #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ #define PyMODINIT_FUNC void #endif PyMODINIT_FUNC initnoddy(void) { PyObject* m; if (PyType_Ready(&NoddyType) < 0) return; m = Py_InitModule3("noddy", module_methods, "Example module that creates an extension type."); if (m == NULL) return; Py_INCREF(&NoddyType); PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType); }
bench2.pyで計測しなおす。
結果
dict [0.93455696105957031, 0.90142107009887695, 0.90178704261779785]
attr [0.89447379112243652, 0.89505982398986816, 0.89075517654418945]
1.02144406315
ちょっとだけ改善。
まあほぼ誤差だろーけどねー。