Skip to content

Commit 158c102

Browse files
committed
bpo-40137: Micro-optimize _PyType_GetModuleByDef()
Make _PyType_GetModuleByDef() 2.3 ns faster. Benchmark: 43.2 ns +- 0.7 ns -> 40.9 ns +- 1.0 ns: 1.05x faster ./python -m pyperf timeit \ --duplicate=4096 -s "from functools import lru_cache; f = lru_cache(lambda: 42)" \ "f()" Changes: * _PyType_GetModuleByDef(): add fast-path for tp_mro[0] and use _PyType_HasFeature(). * Add a new pycore_moduleobject.h internal C API header file. * Add _PyModule_GetDef() and _PyModule_GetState() which can be inlined without LTO and don't check invalid argument at runtime. * Replace PyModule_GetState() with _PyModule_GetState() in _abc, _array, _operator, _pickle, _queue, _random, _struct and os extension modules. The _array, _queue and _struct extensions are now built with Py_BUILD_CORE_MODULE macro defined.
1 parent 8fa1489 commit 158c102

17 files changed

+93
-33
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#ifndef Py_INTERNAL_MODULEOBJECT_H
2+
#define Py_INTERNAL_MODULEOBJECT_H
3+
#ifdef __cplusplus
4+
extern "C" {
5+
#endif
6+
7+
#ifndef Py_BUILD_CORE
8+
# error "this header requires Py_BUILD_CORE define"
9+
#endif
10+
11+
typedef struct {
12+
PyObject_HEAD
13+
PyObject *md_dict;
14+
struct PyModuleDef *md_def;
15+
void *md_state;
16+
PyObject *md_weaklist;
17+
PyObject *md_name; /* for logging purposes after md_dict is cleared */
18+
} PyModuleObject;
19+
20+
static inline PyModuleDef* _PyModule_GetDef(PyObject *m) {
21+
assert(PyModule_Check(m));
22+
return ((PyModuleObject *)m)->md_def;
23+
}
24+
25+
static inline void* _PyModule_GetState(PyObject* m) {
26+
assert(PyModule_Check(m));
27+
return ((PyModuleObject *)m)->md_state;
28+
}
29+
30+
31+
#ifdef __cplusplus
32+
}
33+
#endif
34+
#endif /* !Py_INTERNAL_MODULEOBJECT_H */

Makefile.pre.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,6 +1160,7 @@ PYTHON_HEADERS= \
11601160
$(srcdir)/Include/internal/pycore_interp.h \
11611161
$(srcdir)/Include/internal/pycore_list.h \
11621162
$(srcdir)/Include/internal/pycore_long.h \
1163+
$(srcdir)/Include/internal/pycore_moduleobject.h \
11631164
$(srcdir)/Include/internal/pycore_object.h \
11641165
$(srcdir)/Include/internal/pycore_pathconfig.h \
11651166
$(srcdir)/Include/internal/pycore_pyarena.h \
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Micro-optimize _PyType_GetModuleByDef() to make it 2.3 ns faster (43.2 ns +-
2+
0.7 ns -> 40.9 ns +- 1.0 ns). Patch by Victor Stinner.

Modules/_abc.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* ABCMeta implementation */
22

33
#include "Python.h"
4+
#include "pycore_moduleobject.h" // _PyModule_GetState()
45
#include "clinic/_abc.c.h"
56

67
/*[clinic input]
@@ -27,7 +28,7 @@ typedef struct {
2728
static inline _abcmodule_state*
2829
get_abc_state(PyObject *module)
2930
{
30-
void *state = PyModule_GetState(module);
31+
void *state = _PyModule_GetState(module);
3132
assert(state != NULL);
3233
return (_abcmodule_state *)state;
3334
}

Modules/_functoolsmodule.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "Python.h"
22
#include "pycore_long.h" // _PyLong_GetZero()
3+
#include "pycore_moduleobject.h" // _PyModule_GetState()
34
#include "pycore_object.h" // _PyObject_GC_TRACK
45
#include "pycore_pystate.h" // _PyThreadState_GET()
56
#include "pycore_tuple.h" // _PyTuple_ITEMS()
@@ -35,7 +36,7 @@ typedef struct _functools_state {
3536
static inline _functools_state *
3637
get_functools_state(PyObject *module)
3738
{
38-
void *state = PyModule_GetState(module);
39+
void *state = _PyModule_GetState(module);
3940
assert(state != NULL);
4041
return (_functools_state *)state;
4142
}
@@ -52,8 +53,7 @@ get_functools_state_by_type(PyTypeObject *type)
5253
if (module == NULL) {
5354
return NULL;
5455
}
55-
_functools_state *state = get_functools_state(module);
56-
return state;
56+
return get_functools_state(module);
5757
}
5858

5959
static PyObject *

Modules/_operator.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11

22
#include "Python.h"
3-
3+
#include "pycore_moduleobject.h" // _PyModule_GetState()
44
#include "clinic/_operator.c.h"
55

66
typedef struct {
@@ -12,7 +12,7 @@ typedef struct {
1212
static inline _operator_state*
1313
get_operator_state(PyObject *module)
1414
{
15-
void *state = PyModule_GetState(module);
15+
void *state = _PyModule_GetState(module);
1616
assert(state != NULL);
1717
return (_operator_state *)state;
1818
}

Modules/_pickle.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#endif
1010

1111
#include "Python.h"
12+
#include "pycore_moduleobject.h" // _PyModule_GetState()
1213
#include "structmember.h" // PyMemberDef
1314

1415
PyDoc_STRVAR(pickle_module_doc,
@@ -182,7 +183,7 @@ static struct PyModuleDef _picklemodule;
182183
static PickleState *
183184
_Pickle_GetState(PyObject *module)
184185
{
185-
return (PickleState *)PyModule_GetState(module);
186+
return (PickleState *)_PyModule_GetState(module);
186187
}
187188

188189
/* Find the module instance imported in the currently running sub-interpreter

Modules/_queuemodule.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "Python.h"
2+
#include "pycore_moduleobject.h" // _PyModule_GetState()
23
#include "structmember.h" // PyMemberDef
34
#include <stddef.h> // offsetof()
45

@@ -10,7 +11,7 @@ typedef struct {
1011
static simplequeue_state *
1112
simplequeue_get_state(PyObject *module)
1213
{
13-
simplequeue_state *state = PyModule_GetState(module);
14+
simplequeue_state *state = _PyModule_GetState(module);
1415
assert(state);
1516
return state;
1617
}

Modules/_randommodule.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
/* ---------------------------------------------------------------*/
6868

6969
#include "Python.h"
70+
#include "pycore_moduleobject.h" // _PyModule_GetState()
7071
#ifdef HAVE_PROCESS_H
7172
# include <process.h> // getpid()
7273
#endif
@@ -86,7 +87,7 @@ typedef struct {
8687
static inline _randomstate*
8788
get_random_state(PyObject *module)
8889
{
89-
void *state = PyModule_GetState(module);
90+
void *state = _PyModule_GetState(module);
9091
assert(state != NULL);
9192
return (_randomstate *)state;
9293
}
@@ -538,7 +539,7 @@ random_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
538539

539540
if (PyTuple_GET_SIZE(args) == 1)
540541
arg = PyTuple_GET_ITEM(args, 0);
541-
542+
542543
tmp = random_seed(self, arg);
543544
if (tmp == NULL) {
544545
Py_DECREF(self);

Modules/_struct.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#define PY_SSIZE_T_CLEAN
77

88
#include "Python.h"
9+
#include "pycore_moduleobject.h" // _PyModule_GetState()
910
#include "structmember.h" // PyMemberDef
1011
#include <ctype.h>
1112

@@ -24,7 +25,7 @@ typedef struct {
2425
static inline _structmodulestate*
2526
get_struct_state(PyObject *module)
2627
{
27-
void *state = PyModule_GetState(module);
28+
void *state = _PyModule_GetState(module);
2829
assert(state != NULL);
2930
return (_structmodulestate *)state;
3031
}

Modules/arraymodule.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#define PY_SSIZE_T_CLEAN
77
#include "Python.h"
8+
#include "pycore_moduleobject.h" // _PyModule_GetState()
89
#include "structmember.h" // PyMemberDef
910
#include <stddef.h> // offsetof()
1011

@@ -63,7 +64,7 @@ typedef struct {
6364
static array_state *
6465
get_array_state(PyObject *module)
6566
{
66-
return (array_state *)PyModule_GetState(module);
67+
return (array_state *)_PyModule_GetState(module);
6768
}
6869

6970
#define find_array_state_by_type(tp) \

Modules/posixmodule.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
#include "Python.h"
1313
#include "pycore_fileutils.h"
14+
#include "pycore_moduleobject.h" // _PyModule_GetState()
1415
#ifdef MS_WINDOWS
1516
/* include <windows.h> early to avoid conflict with pycore_condvar.h:
1617
@@ -994,7 +995,7 @@ typedef struct {
994995
static inline _posixstate*
995996
get_posix_state(PyObject *module)
996997
{
997-
void *state = PyModule_GetState(module);
998+
void *state = _PyModule_GetState(module);
998999
assert(state != NULL);
9991000
return (_posixstate *)state;
10001001
}

Objects/moduleobject.c

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "Python.h"
55
#include "pycore_interp.h" // PyInterpreterState.importlib
66
#include "pycore_pystate.h" // _PyInterpreterState_GET()
7+
#include "pycore_moduleobject.h" // _PyModule_GetDef()
78
#include "structmember.h" // PyMemberDef
89

910
static Py_ssize_t max_module_number;
@@ -12,15 +13,6 @@ _Py_IDENTIFIER(__doc__);
1213
_Py_IDENTIFIER(__name__);
1314
_Py_IDENTIFIER(__spec__);
1415

15-
typedef struct {
16-
PyObject_HEAD
17-
PyObject *md_dict;
18-
struct PyModuleDef *md_def;
19-
void *md_state;
20-
PyObject *md_weaklist;
21-
PyObject *md_name; /* for logging purposes after md_dict is cleared */
22-
} PyModuleObject;
23-
2416
static PyMemberDef module_members[] = {
2517
{"__dict__", T_OBJECT, offsetof(PyModuleObject, md_dict), READONLY},
2618
{0}
@@ -556,7 +548,7 @@ PyModule_GetDef(PyObject* m)
556548
PyErr_BadArgument();
557549
return NULL;
558550
}
559-
return ((PyModuleObject *)m)->md_def;
551+
return _PyModule_GetDef(m);
560552
}
561553

562554
void*
@@ -566,7 +558,7 @@ PyModule_GetState(PyObject* m)
566558
PyErr_BadArgument();
567559
return NULL;
568560
}
569-
return ((PyModuleObject *)m)->md_state;
561+
return _PyModule_GetState(m);
570562
}
571563

572564
void

Objects/typeobject.c

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "pycore_call.h"
55
#include "pycore_compile.h" // _Py_Mangle()
66
#include "pycore_initconfig.h"
7+
#include "pycore_moduleobject.h" // _PyModule_GetDef()
78
#include "pycore_object.h"
89
#include "pycore_pyerrors.h"
910
#include "pycore_pystate.h" // _PyThreadState_GET()
@@ -3591,10 +3592,25 @@ _PyType_GetModuleByDef(PyTypeObject *type, struct PyModuleDef *def)
35913592
{
35923593
assert(PyType_Check(type));
35933594
assert(type->tp_mro);
3594-
int i;
3595-
for (i = 0; i < PyTuple_GET_SIZE(type->tp_mro); i++) {
3596-
PyObject *super = PyTuple_GET_ITEM(type->tp_mro, i);
3597-
if (!PyType_HasFeature((PyTypeObject *)super, Py_TPFLAGS_HEAPTYPE)) {
3595+
3596+
// Fast path for type->tp_mro[0]=type. Calling _PyType_GetModuleByDef() on
3597+
// the defining type is the most common type.
3598+
assert(PyTuple_GET_ITEM(type->tp_mro, 0) == type);
3599+
// A static type cannot inherit from a heap type, since heap types are
3600+
// created at runtime. _PyType_GetModuleByDef() is used on heap types
3601+
// created by PyType_FromModuleAndSpec(), and on their subclasses.
3602+
assert(_PyType_HasFeature((PyTypeObject *)type, Py_TPFLAGS_HEAPTYPE));
3603+
PyHeapTypeObject *ht = (PyHeapTypeObject*)type;
3604+
if (ht->ht_module && _PyModule_GetDef(ht->ht_module) == def) {
3605+
return ht->ht_module;
3606+
}
3607+
3608+
// Slow path
3609+
PyObject *mro = type->tp_mro;
3610+
Py_ssize_t len = PyTuple_GET_SIZE(mro);
3611+
for (Py_ssize_t i = 1; i < len; i++) {
3612+
PyObject *super = PyTuple_GET_ITEM(mro, i);
3613+
if (!_PyType_HasFeature((PyTypeObject *)super, Py_TPFLAGS_HEAPTYPE)) {
35983614
/* Currently, there's no way for static types to inherit
35993615
* from heap types, but to allow that possibility,
36003616
* we `continue` rather than `break`.
@@ -3603,11 +3619,12 @@ _PyType_GetModuleByDef(PyTypeObject *type, struct PyModuleDef *def)
36033619
*/
36043620
continue;
36053621
}
3606-
PyHeapTypeObject *ht = (PyHeapTypeObject*)super;
3607-
if (ht->ht_module && PyModule_GetDef(ht->ht_module) == def) {
3622+
ht = (PyHeapTypeObject*)super;
3623+
if (ht->ht_module && _PyModule_GetDef(ht->ht_module) == def) {
36083624
return ht->ht_module;
36093625
}
36103626
}
3627+
36113628
PyErr_Format(
36123629
PyExc_TypeError,
36133630
"_PyType_GetModuleByDef: No superclass of '%s' has the given module",

PCbuild/pythoncore.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@
199199
<ClInclude Include="..\Include\internal\pycore_interp.h" />
200200
<ClInclude Include="..\Include\internal\pycore_list.h" />
201201
<ClInclude Include="..\Include\internal\pycore_long.h" />
202+
<ClInclude Include="..\Include\internal\pycore_moduleobject.h" />
202203
<ClInclude Include="..\Include\internal\pycore_object.h" />
203204
<ClInclude Include="..\Include\internal\pycore_pathconfig.h" />
204205
<ClInclude Include="..\Include\internal\pycore_pyarena.h" />

PCbuild/pythoncore.vcxproj.filters

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,9 @@
558558
<ClInclude Include="..\Include\internal\pycore_long.h">
559559
<Filter>Include\internal</Filter>
560560
</ClInclude>
561+
<ClInclude Include="..\Include\internal\pycore_moduleobject.h">
562+
<Filter>Include\internal</Filter>
563+
</ClInclude>
561564
<ClInclude Include="..\Include\internal\pycore_object.h">
562565
<Filter>Include\internal</Filter>
563566
</ClInclude>

setup.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -866,7 +866,8 @@ def detect_simple_extensions(self):
866866
#
867867

868868
# array objects
869-
self.add(Extension('array', ['arraymodule.c']))
869+
self.add(Extension('array', ['arraymodule.c'],
870+
extra_compile_args=['-DPy_BUILD_CORE_MODULE']))
870871

871872
# Context Variables
872873
self.add(Extension('_contextvars', ['_contextvarsmodule.c']))
@@ -933,7 +934,8 @@ def detect_simple_extensions(self):
933934
# _abc speedups
934935
self.add(Extension("_abc", ["_abc.c"]))
935936
# _queue module
936-
self.add(Extension("_queue", ["_queuemodule.c"]))
937+
self.add(Extension("_queue", ["_queuemodule.c"],
938+
extra_compile_args=['-DPy_BUILD_CORE_MODULE']))
937939
# _statistics module
938940
self.add(Extension("_statistics", ["_statisticsmodule.c"]))
939941

@@ -2711,7 +2713,8 @@ class DummyProcess:
27112713
'install_lib': PyBuildInstallLib},
27122714
# The struct module is defined here, because build_ext won't be
27132715
# called unless there's at least one extension module defined.
2714-
ext_modules=[Extension('_struct', ['_struct.c'])],
2716+
ext_modules=[Extension('_struct', ['_struct.c'],
2717+
extra_compile_args=['-DPy_BUILD_CORE_MODULE'])],
27152718

27162719
# If you change the scripts installed here, you also need to
27172720
# check the PyBuildScripts command above, and change the links

0 commit comments

Comments
 (0)