Skip to content

Commit 1d04ab1

Browse files
committed
[GR-11489] Improve performance of native member access
PullRequest: graalpython/176
2 parents 1287b63 + 9e8341c commit 1d04ab1

File tree

17 files changed

+467
-140
lines changed

17 files changed

+467
-140
lines changed

graalpython/benchmarks/src/harness.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,36 @@
4242
import sys
4343
from time import time
4444

45+
4546
_HRULE = '-'.join(['' for i in range(80)])
4647
ATTR_BENCHMARK = '__benchmark__'
4748
ATTR_PROCESS_ARGS = '__process_args__'
4849

4950

51+
def ccompile(name, code):
52+
from importlib import invalidate_caches
53+
from distutils.core import setup, Extension
54+
__dir__ = __file__.rpartition("/")[0]
55+
source_file = '%s/%s.c' % (__dir__, name)
56+
with open(source_file, "w") as f:
57+
f.write(code)
58+
module = Extension(name, sources=[source_file])
59+
args = ['--quiet', 'build', 'install_lib', '-f', '--install-dir=%s' % __dir__]
60+
setup(
61+
script_name='setup',
62+
script_args=args,
63+
name=name,
64+
version='1.0',
65+
description='',
66+
ext_modules=[module]
67+
)
68+
# IMPORTANT:
69+
# Invalidate caches after creating the native module.
70+
# FileFinder caches directory contents, and the check for directory
71+
# changes has whole-second precision, so it can miss quick updates.
72+
invalidate_caches()
73+
74+
5075
def _as_int(value):
5176
if isinstance(value, (list, tuple)):
5277
value = value[0]
@@ -92,6 +117,7 @@ def get_bench_module(bench_file):
92117
bench_module = type(sys)(name, bench_file)
93118
with _io.FileIO(bench_file, "r") as f:
94119
bench_module.__file__ = bench_file
120+
bench_module.ccompile = ccompile
95121
exec(compile(f.readall(), bench_file, "exec"), bench_module.__dict__)
96122
return bench_module
97123

@@ -111,7 +137,7 @@ def run(self):
111137
else:
112138
print("### %s, %s warmup iterations, %s bench iterations " % (self.bench_module.__name__, self.warmup, self.iterations))
113139

114-
# process the args if the processor function is defined
140+
# process the args if the processor function is defined
115141
args = self._call_attr(ATTR_PROCESS_ARGS, *self.bench_args)
116142
if args is None:
117143
# default args processor considers all args as ints
@@ -154,7 +180,7 @@ def run_benchmark(prog, args):
154180
elif arg == '-w':
155181
i += 1
156182
warmup = _as_int(args[i])
157-
elif arg.startswith("--warmup"):
183+
elif arg.startswith("--warmup"):
158184
warmup = _as_int(arg.split("=")[1])
159185
elif bench_file is None:
160186
bench_file = arg
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
2+
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
3+
#
4+
# The Universal Permissive License (UPL), Version 1.0
5+
#
6+
# Subject to the condition set forth below, permission is hereby granted to any
7+
# person obtaining a copy of this software, associated documentation and/or
8+
# data (collectively the "Software"), free of charge and under any and all
9+
# copyright rights in the Software, and any and all patent rights owned or
10+
# freely licensable by each licensor hereunder covering either (i) the
11+
# unmodified Software as contributed to or provided by such licensor, or (ii)
12+
# the Larger Works (as defined below), to deal in both
13+
#
14+
# (a) the Software, and
15+
#
16+
# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
17+
# one is included with the Software each a "Larger Work" to which the Software
18+
# is contributed by such licensors),
19+
#
20+
# without restriction, including without limitation the rights to copy, create
21+
# derivative works of, display, perform, and distribute the Software and make,
22+
# use, sell, offer for sale, import, export, have made, and have sold the
23+
# Software and the Larger Work(s), and to sublicense the foregoing rights on
24+
# either these or other terms.
25+
#
26+
# This license is subject to the following condition:
27+
#
28+
# The above copyright notice and either this complete permission notice or at a
29+
# minimum a reference to the UPL must be included in all copies or substantial
30+
# portions of the Software.
31+
#
32+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
33+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
34+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
35+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
36+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
37+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
38+
# SOFTWARE.
39+
40+
code = """
41+
#include <Python.h>
42+
#include "structmember.h"
43+
44+
typedef struct {
45+
PyObject_HEAD
46+
int number;
47+
} ObjectWithMember;
48+
49+
PyObject* ObjectWithMember_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
50+
ObjectWithMember* self = (ObjectWithMember *)type->tp_alloc(type, 0);
51+
if (self != NULL) {
52+
self->number = 0;
53+
}
54+
return (PyObject *)self;
55+
}
56+
57+
int ObjectWithMember_init(ObjectWithMember* self, PyObject* args, PyObject* kwds) {
58+
if (!PyArg_ParseTuple(args, "i", &self->number)) {
59+
return -1;
60+
}
61+
return 0;
62+
}
63+
64+
static PyMemberDef ObjectWithMember_members[] = {
65+
{"number", T_INT, offsetof(ObjectWithMember, number), 0, ""},
66+
{NULL}
67+
};
68+
69+
static PyTypeObject ObjectWithMemberType = {
70+
PyVarObject_HEAD_INIT(NULL, 0)
71+
"module.ObjectWithMember", /* tp_name */
72+
sizeof(ObjectWithMember), /* tp_basicsize */
73+
0, /* tp_itemsize */
74+
0, /* tp_dealloc */
75+
0, /* tp_print */
76+
0, /* tp_getattr */
77+
0, /* tp_setattr */
78+
0, /* tp_reserved */
79+
0, /* tp_repr */
80+
0, /* tp_as_number */
81+
0, /* tp_as_sequence */
82+
0, /* tp_as_mapping */
83+
0, /* tp_hash */
84+
0, /* tp_call */
85+
0, /* tp_str */
86+
0, /* tp_getattro */
87+
0, /* tp_setattro */
88+
0, /* tp_as_buffer */
89+
Py_TPFLAGS_DEFAULT |
90+
Py_TPFLAGS_BASETYPE, /* tp_flags */
91+
"", /* tp_doc */
92+
0, /* tp_traverse */
93+
0, /* tp_clear */
94+
0, /* tp_richcompare */
95+
0, /* tp_weaklistoffset */
96+
0, /* tp_iter */
97+
0, /* tp_iternext */
98+
0, /* tp_methods */
99+
ObjectWithMember_members, /* tp_members */
100+
0, /* tp_getset */
101+
0, /* tp_base */
102+
0, /* tp_dict */
103+
0, /* tp_descr_get */
104+
0, /* tp_descr_set */
105+
0, /* tp_dictoffset */
106+
(initproc)ObjectWithMember_init, /* tp_init */
107+
0, /* tp_alloc */
108+
ObjectWithMember_new, /* tp_new */
109+
};
110+
111+
static PyModuleDef module = {
112+
PyModuleDef_HEAD_INIT,
113+
"module",
114+
"",
115+
-1,
116+
NULL, NULL, NULL, NULL, NULL
117+
};
118+
119+
PyMODINIT_FUNC
120+
PyInit_c_member_access_module(void) {
121+
if (PyType_Ready(&ObjectWithMemberType) < 0) {
122+
return NULL;
123+
}
124+
125+
PyObject* m = PyModule_Create(&module);
126+
if (m == NULL) {
127+
return NULL;
128+
}
129+
130+
Py_INCREF(&ObjectWithMemberType);
131+
PyModule_AddObject(m, "ObjectWithMember", (PyObject *)&ObjectWithMemberType);
132+
return m;
133+
}
134+
"""
135+
136+
137+
ccompile("c_member_access_module", code)
138+
import c_member_access_module
139+
140+
141+
def do_stuff(foo):
142+
for i in range(50000):
143+
local_a = foo.number + 1
144+
foo.number = local_a % 5
145+
146+
return foo.number
147+
148+
149+
def measure(num):
150+
for i in range(num):
151+
result = do_stuff(c_member_access_module.ObjectWithMember(42))
152+
153+
print(result)
154+
155+
156+
def __benchmark__(num=50000):
157+
measure(num)
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
2+
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
3+
#
4+
# The Universal Permissive License (UPL), Version 1.0
5+
#
6+
# Subject to the condition set forth below, permission is hereby granted to any
7+
# person obtaining a copy of this software, associated documentation and/or
8+
# data (collectively the "Software"), free of charge and under any and all
9+
# copyright rights in the Software, and any and all patent rights owned or
10+
# freely licensable by each licensor hereunder covering either (i) the
11+
# unmodified Software as contributed to or provided by such licensor, or (ii)
12+
# the Larger Works (as defined below), to deal in both
13+
#
14+
# (a) the Software, and
15+
#
16+
# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
17+
# one is included with the Software each a "Larger Work" to which the Software
18+
# is contributed by such licensors),
19+
#
20+
# without restriction, including without limitation the rights to copy, create
21+
# derivative works of, display, perform, and distribute the Software and make,
22+
# use, sell, offer for sale, import, export, have made, and have sold the
23+
# Software and the Larger Work(s), and to sublicense the foregoing rights on
24+
# either these or other terms.
25+
#
26+
# This license is subject to the following condition:
27+
#
28+
# The above copyright notice and either this complete permission notice or at a
29+
# minimum a reference to the UPL must be included in all copies or substantial
30+
# portions of the Software.
31+
#
32+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
33+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
34+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
35+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
36+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
37+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
38+
# SOFTWARE.
39+
40+
class Foo(object):
41+
def __init__(self, a):
42+
self._a = a
43+
44+
@property
45+
def a(self):
46+
return self._a
47+
48+
@a.setter
49+
def a(self, value):
50+
self._a = value
51+
52+
53+
def do_stuff(foo):
54+
for i in range(50000):
55+
local_a = foo.a + 1
56+
foo.a = local_a % 5
57+
return foo.a
58+
59+
60+
def measure(num):
61+
for i in range(num):
62+
result = do_stuff(Foo(42))
63+
print(result)
64+
65+
66+
def __benchmark__(num=5000):
67+
measure(num)

0 commit comments

Comments
 (0)