Backporting METH_FASTCALL from Python 3.7 (#317)

* dict copy speedup

refer to bpo-31179 or python/cpython@boa7a037b8fde

* __build_class__() uses METH_FASTCALL

refer python/cpython@69de71b255
refer python/cpython@773dc6dd06

a single test related to __prepare__ fails.

* type_prepare uses METH_FASTCALL

refer python/cpython@d526cfe546
refer python/cpython@80ab22fa2c

the prepare-related test still fails.  It's just related to the error
message format though.

* separate into ParseStack and ParseStackAndKeywords

refer python/cpython@6518a93cb1
refer python/cpython@3e1fad6913
refer python/cpython@c0083fc47d

* Add _PyArg_NoStackKeywords

refer python/cpython@29d39cc8f5

* _PyStack_UnpackDict now returns int

refer python/cpython@998c20962c

* METH_FASTCALL changes to .inc files

done via python's Argument Clinic tool,
refer python/cpython@259f0e4437

* Added _PyArg_UnpackStack

refer python/cpython@fe54dda08

* Argument Clinic FASTCALL again

refer python/cpython@0c4a828ca

* Argument Clinic for ordered dictionary object

refer python/cpython@b05cbac052

* speed up getargs

refer python/cpython@1741441649

* FASTCALL for sorted, next, and getattr

refer python/cpython@5a60ecaa7a
refer python/cpython@fda6d0acf0
refer python/cpython@84b388bb80

* Optimize methoddescr_call

refer python/cpython@2a1b676d1f
refer python/cpython@c52572319c
refer python/cpython@35ecebe165
refer python/cpython@8128d5a491

* cleanup _PyMethodDef_RawFastCallDict

refer python/cpython@0a2e46835d
refer python/cpython@98ccba8344
refer python/cpython@c89ef828cf
refer python/cpython@250e4b0063

* print now uses METH_FASTCALL

refer python/cpython@c3858bd7c6
refer python/cpython@bd584f169f
refer python/cpython@06d34393c2

* _struct module now uses Argument Clinic

refer python/cpython@3f2d10132d

* make deque methods faster

refer python/cpython@dd407d5006

* recursive calls in PyObject_Call

refer python/cpython@7399a05965

only partially ported, because RawFastCallKeywords hasn't been ported

* add macros

refer python/cpython@68a001dd59

* all tests pass in MODE=dbg

* convert some internal functions to FASTCALL

__import__ might need to be changed later, if it is possible to backport
the METH_FASTCALL | METH_KEYWORDS flag distinction later.

* speed up unpickling

refer python/cpython@bee09aecc2

* added _PyMethodDef_RawFastCallKeywords

refer python/cpython@7399a05965

* PyCFunction_Call performance

refer python/cpython@12c5838dae

* avoid PyMethodObject in slots

main change in python/cpython@516b98161a
test_exceptions changed in python/cpython@331bbe6aaa
type_settattro changed in python/cpython@193f7e094f
_PyObject_CallFunctionVa changed in python/cpython@fe4ff83049

* fix refcount error found in MODE=dbg

all tests now pass in MODE=dbg
This commit is contained in:
Gautham 2021-11-13 04:56:57 +05:30 committed by GitHub
parent 6f658f058b
commit 7fe9e70117
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
71 changed files with 4154 additions and 2450 deletions

View file

@ -638,6 +638,51 @@ new_dict_with_shared_keys(PyDictKeysObject *keys)
return new_dict(keys, values);
}
static PyObject *
clone_combined_dict(PyDictObject *orig)
{
assert(PyDict_CheckExact(orig));
assert(orig->ma_values == NULL);
assert(orig->ma_keys->dk_refcnt == 1);
Py_ssize_t keys_size = _PyDict_KeysSize(orig->ma_keys);
PyDictKeysObject *keys = PyObject_Malloc(keys_size);
if (keys == NULL) {
PyErr_NoMemory();
return NULL;
}
memcpy(keys, orig->ma_keys, keys_size);
/* After copying key/value pairs, we need to incref all
keys and values and they are about to be co-owned by a
new dict object. */
PyDictKeyEntry *ep0 = DK_ENTRIES(keys);
Py_ssize_t n = keys->dk_nentries;
for (Py_ssize_t i = 0; i < n; i++) {
PyDictKeyEntry *entry = &ep0[i];
PyObject *value = entry->me_value;
if (value != NULL) {
Py_INCREF(value);
Py_INCREF(entry->me_key);
}
}
PyDictObject *new = (PyDictObject *)new_dict(keys, NULL);
if (new == NULL) {
/* In case of an error, `new_dict()` takes care of
cleaning up `keys`. */
return NULL;
}
new->ma_used = orig->ma_used;
assert(_PyDict_CheckConsistency(new));
if (_PyObject_GC_IS_TRACKED(orig)) {
/* Maintain tracking. */
_PyObject_GC_TRACK(new);
}
return (PyObject *)new;
}
PyObject *
PyDict_New(void)
{
@ -2656,6 +2701,12 @@ PyDict_Copy(PyObject *o)
return NULL;
}
mp = (PyDictObject *)o;
if (mp->ma_used == 0) {
/* The dict is empty; just return a new dict. */
return PyDict_New();
}
if (_PyDict_HasSplitTable(mp)) {
PyDictObject *split_copy;
Py_ssize_t size = USABLE_FRACTION(DK_SIZE(mp->ma_keys));
@ -2682,6 +2733,27 @@ PyDict_Copy(PyObject *o)
_PyObject_GC_TRACK(split_copy);
return (PyObject *)split_copy;
}
if (PyDict_CheckExact(mp) && mp->ma_values == NULL &&
(mp->ma_used >= (mp->ma_keys->dk_nentries * 2) / 3))
{
/* Use fast-copy if:
(1) 'mp' is an instance of a subclassed dict; and
(2) 'mp' is not a split-dict; and
(3) if 'mp' is non-compact ('del' operation does not resize dicts),
do fast-copy only if it has at most 1/3 non-used keys.
The last condition (3) is important to guard against a pathalogical
case when a large dict is almost emptied with multiple del/pop
operations and copied after that. In cases like this, we defer to
PyDict_Merge, which produces a compacted copy.
*/
return clone_combined_dict(mp);
}
copy = PyDict_New();
if (copy == NULL)
return NULL;
@ -2839,7 +2911,7 @@ dict___contains__(PyDictObject *self, PyObject *key)
}
static PyObject *
dict_get(PyDictObject *mp, PyObject *args)
dict_get(PyDictObject *mp, PyObject **args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *key;
PyObject *failobj = Py_None;
@ -2848,7 +2920,10 @@ dict_get(PyDictObject *mp, PyObject *args)
Py_ssize_t ix;
PyObject **value_addr;
if (!PyArg_UnpackTuple(args, "get", 1, 2, &key, &failobj))
if (!_PyArg_UnpackStack(args, nargs, "get", 1, 2, &key, &failobj))
return NULL;
if (!_PyArg_NoStackKeywords("get", kwnames))
return NULL;
if (!PyUnicode_CheckExact(key) ||
@ -2957,14 +3032,17 @@ PyDict_SetDefault(PyObject *d, PyObject *key, PyObject *defaultobj)
}
static PyObject *
dict_setdefault(PyDictObject *mp, PyObject *args)
dict_setdefault(PyDictObject *mp, PyObject **args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *key, *val;
PyObject *defaultobj = Py_None;
if (!PyArg_UnpackTuple(args, "setdefault", 1, 2, &key, &defaultobj))
if (!_PyArg_UnpackStack(args, nargs, "setdefault", 1, 2, &key, &defaultobj))
return NULL;
if(!_PyArg_NoStackKeywords("pop", kwnames))
return NULL;
val = PyDict_SetDefault((PyObject *)mp, key, defaultobj);
Py_XINCREF(val);
return val;
@ -2978,11 +3056,14 @@ dict_clear(PyDictObject *mp)
}
static PyObject *
dict_pop(PyDictObject *mp, PyObject *args)
dict_pop(PyDictObject *mp, PyObject **args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *key, *deflt = NULL;
if(!PyArg_UnpackTuple(args, "pop", 1, 2, &key, &deflt))
if(!_PyArg_UnpackStack(args, nargs, "pop", 1, 2, &key, &deflt))
return NULL;
if(!_PyArg_NoStackKeywords("pop", kwnames))
return NULL;
return _PyDict_Pop((PyObject*)mp, key, deflt);
@ -3171,11 +3252,11 @@ static PyMethodDef mapp_methods[] = {
getitem__doc__},
{"__sizeof__", (PyCFunction)dict_sizeof, METH_NOARGS,
sizeof__doc__},
{"get", (PyCFunction)dict_get, METH_VARARGS,
{"get", (PyCFunction)dict_get, METH_FASTCALL,
get__doc__},
{"setdefault", (PyCFunction)dict_setdefault, METH_VARARGS,
{"setdefault", (PyCFunction)dict_setdefault, METH_FASTCALL,
setdefault_doc__},
{"pop", (PyCFunction)dict_pop, METH_VARARGS,
{"pop", (PyCFunction)dict_pop, METH_FASTCALL,
pop__doc__},
{"popitem", (PyCFunction)dict_popitem, METH_NOARGS,
popitem__doc__},