Skip to content

Commit 3fba64b

Browse files
committed
add simple member access benchmark
1 parent 737c620 commit 3fba64b

File tree

4 files changed

+206
-2
lines changed

4 files changed

+206
-2
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: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
code = """
2+
#include <Python.h>
3+
#include "structmember.h"
4+
5+
typedef struct {
6+
PyObject_HEAD
7+
int number;
8+
} ObjectWithMember;
9+
10+
PyObject* ObjectWithMember_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
11+
ObjectWithMember* self = (ObjectWithMember *)type->tp_alloc(type, 0);
12+
if (self != NULL) {
13+
self->number = 0;
14+
}
15+
return (PyObject *)self;
16+
}
17+
18+
int ObjectWithMember_init(ObjectWithMember* self, PyObject* args, PyObject* kwds) {
19+
if (!PyArg_ParseTuple(args, "i", &self->number)) {
20+
return -1;
21+
}
22+
return 0;
23+
}
24+
25+
static PyMemberDef ObjectWithMember_members[] = {
26+
{"number", T_INT, offsetof(ObjectWithMember, number), 0, ""},
27+
{NULL}
28+
};
29+
30+
static PyTypeObject ObjectWithMemberType = {
31+
PyVarObject_HEAD_INIT(NULL, 0)
32+
"module.ObjectWithMember", /* tp_name */
33+
sizeof(ObjectWithMember), /* tp_basicsize */
34+
0, /* tp_itemsize */
35+
0, /* tp_dealloc */
36+
0, /* tp_print */
37+
0, /* tp_getattr */
38+
0, /* tp_setattr */
39+
0, /* tp_reserved */
40+
0, /* tp_repr */
41+
0, /* tp_as_number */
42+
0, /* tp_as_sequence */
43+
0, /* tp_as_mapping */
44+
0, /* tp_hash */
45+
0, /* tp_call */
46+
0, /* tp_str */
47+
0, /* tp_getattro */
48+
0, /* tp_setattro */
49+
0, /* tp_as_buffer */
50+
Py_TPFLAGS_DEFAULT |
51+
Py_TPFLAGS_BASETYPE, /* tp_flags */
52+
"", /* tp_doc */
53+
0, /* tp_traverse */
54+
0, /* tp_clear */
55+
0, /* tp_richcompare */
56+
0, /* tp_weaklistoffset */
57+
0, /* tp_iter */
58+
0, /* tp_iternext */
59+
0, /* tp_methods */
60+
ObjectWithMember_members, /* tp_members */
61+
0, /* tp_getset */
62+
0, /* tp_base */
63+
0, /* tp_dict */
64+
0, /* tp_descr_get */
65+
0, /* tp_descr_set */
66+
0, /* tp_dictoffset */
67+
(initproc)ObjectWithMember_init, /* tp_init */
68+
0, /* tp_alloc */
69+
ObjectWithMember_new, /* tp_new */
70+
};
71+
72+
static PyModuleDef module = {
73+
PyModuleDef_HEAD_INIT,
74+
"module",
75+
"",
76+
-1,
77+
NULL, NULL, NULL, NULL, NULL
78+
};
79+
80+
PyMODINIT_FUNC
81+
PyInit_c_member_access_module(void) {
82+
if (PyType_Ready(&ObjectWithMemberType) < 0) {
83+
return NULL;
84+
}
85+
86+
PyObject* m = PyModule_Create(&module);
87+
if (m == NULL) {
88+
return NULL;
89+
}
90+
91+
Py_INCREF(&ObjectWithMemberType);
92+
PyModule_AddObject(m, "ObjectWithMember", (PyObject *)&ObjectWithMemberType);
93+
return m;
94+
}
95+
"""
96+
97+
98+
ccompile("c_member_access_module", code)
99+
import c_member_access_module
100+
101+
102+
def do_stuff(foo):
103+
for i in range(50000):
104+
local_a = foo.number + 1
105+
foo.number = local_a % 5
106+
107+
return foo.number
108+
109+
110+
def measure(num):
111+
for i in range(num):
112+
result = do_stuff(c_member_access_module.ObjectWithMember(42))
113+
114+
print(result)
115+
116+
117+
def __benchmark__(num=50000):
118+
measure(num)
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Copyright (c) 2017, 2018, Oracle and/or its affiliates.
2+
# Copyright (c) 2013, Regents of the University of California
3+
#
4+
# All rights reserved.
5+
#
6+
# Redistribution and use in source and binary forms, with or without modification, are
7+
# permitted provided that the following conditions are met:
8+
#
9+
# 1. Redistributions of source code must retain the above copyright notice, this list of
10+
# conditions and the following disclaimer.
11+
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of
12+
# conditions and the following disclaimer in the documentation and/or other materials provided
13+
# with the distribution.
14+
#
15+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
16+
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
17+
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
18+
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
20+
# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
21+
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
22+
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
23+
# OF THE POSSIBILITY OF SUCH DAMAGE.
24+
# micro benchmark: attribute access
25+
26+
iteration = 50000 # 50000
27+
28+
29+
class Foo(object):
30+
def __init__(self, a):
31+
self._a = a
32+
33+
@property
34+
def a(self):
35+
return self._a
36+
37+
@a.setter
38+
def a(self, value):
39+
self._a = value
40+
41+
42+
def do_stuff(foo):
43+
for i in range(iteration):
44+
local_a = foo.a + 1
45+
foo.a = local_a % 5
46+
47+
return foo.a
48+
49+
50+
def measure(num):
51+
for i in range(num): # 50000
52+
result = do_stuff(Foo(42))
53+
54+
print(result)
55+
56+
57+
def __benchmark__(num=5000):
58+
measure(num)

mx.graalpython/mx_graalpython_bench_param.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@
8888
'special-add-int': ITER_15 + ['5'],
8989
'special-add': ITER_15 + ['5'],
9090
'special-len': ITER_10 + ['5'],
91+
'member_access': ITER_10 + ['5'],
92+
'c_member_access': ITER_10 + ['5'],
9193
}
9294

9395

0 commit comments

Comments
 (0)