PyAPI: use the vectorcall protocol for mathutils types

The Vectorcall protocol avoids creating a tuple, and also provides the
number of arguments in advance, providing a ~1.6x speedup for creation
of mathutils types.

Ref !146237
This commit is contained in:
Oxicid 2025-09-16 04:50:38 +00:00 • committed by Campbell Barton
parent d26a79b144
commit b2176bfdd7
5 changed files with 98 additions and 66 deletions

View file

@ -61,34 +61,37 @@ static PyObject *Color_to_tuple_ex(ColorObject *self, int ndigits)
/** \name Color Type: `__new__` / `mathutils.Color()`
* \{ */
static PyObject *Color_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
static PyObject *Color_vectorcall(PyObject *type,
PyObject *const *args,
const size_t nargsf,
PyObject *kwnames)
{
float col[3] = {0.0f, 0.0f, 0.0f};
if (kwds && PyDict_Size(kwds)) {
if (UNLIKELY(kwnames && PyDict_Size(kwnames))) {
PyErr_SetString(PyExc_TypeError,
"mathutils.Color(): "
"takes no keyword args");
return nullptr;
}
switch (PyTuple_GET_SIZE(args)) {
const size_t nargs = PyVectorcall_NARGS(nargsf);
switch (nargs) {
case 0:
break;
case 1:
if (mathutils_array_parse(
col, COLOR_SIZE, COLOR_SIZE, PyTuple_GET_ITEM(args, 0), "mathutils.Color()") == -1)
{
if (mathutils_array_parse(col, COLOR_SIZE, COLOR_SIZE, args[0], "mathutils.Color()") == -1) {
return nullptr;
}
break;
default:
PyErr_SetString(PyExc_TypeError,
"mathutils.Color(): "
"more than a single arg given");
PyErr_Format(PyExc_TypeError,
"mathutils.Color(): "
"takes at most 1 argument (%zd given)",
nargs);
return nullptr;
}
return Color_CreatePyObject(col, type);
return Color_CreatePyObject(col, (PyTypeObject *)type);
}
/** \} */
@ -1382,7 +1385,7 @@ PyTypeObject color_Type = {
/*tp_dictoffset*/ 0,
/*tp_init*/ nullptr,
/*tp_alloc*/ nullptr,
/*tp_new*/ Color_new,
/*tp_new*/ nullptr,
/*tp_free*/ nullptr,
/*tp_is_gc*/ (inquiry)BaseMathObject_is_gc,
/*tp_bases*/ nullptr,
@ -1393,7 +1396,7 @@ PyTypeObject color_Type = {
/*tp_del*/ nullptr,
/*tp_version_tag*/ 0,
/*tp_finalize*/ nullptr,
/*tp_vectorcall*/ nullptr,
/*tp_vectorcall*/ Color_vectorcall,
};
#ifdef MATH_STANDALONE

View file

@ -94,40 +94,49 @@ static PyObject *Euler_to_tuple_ex(EulerObject *self, int ndigits)
/** \name Euler Type: `__new__` / `mathutils.Euler()`
* \{ */
static PyObject *Euler_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
static PyObject *Euler_vectorcall(PyObject *type,
PyObject *const *args,
const size_t nargsf,
PyObject *kwnames)
{
PyObject *seq = nullptr;
const char *order_str = nullptr;
float eul[EULER_SIZE] = {0.0f, 0.0f, 0.0f};
short order = EULER_ORDER_XYZ;
if (kwds && PyDict_Size(kwds)) {
if (UNLIKELY(kwnames && PyDict_Size(kwnames))) {
PyErr_SetString(PyExc_TypeError,
"mathutils.Euler(): "
"takes no keyword args");
return nullptr;
}
if (!PyArg_ParseTuple(args, "|Os:mathutils.Euler", &seq, &order_str)) {
return nullptr;
}
switch (PyTuple_GET_SIZE(args)) {
const size_t nargs = PyVectorcall_NARGS(nargsf);
switch (nargs) {
case 0:
break;
case 2:
if ((order = euler_order_from_string(order_str, "mathutils.Euler()")) == -1) {
case 2: {
const char *order_str;
if (((order_str = PyUnicode_AsUTF8(args[1])) == nullptr) ||
((order = euler_order_from_string(order_str, "mathutils.Euler()")) == -1))
{
return nullptr;
}
ATTR_FALLTHROUGH;
}
case 1:
if (mathutils_array_parse(eul, EULER_SIZE, EULER_SIZE, seq, "mathutils.Euler()") == -1) {
if (mathutils_array_parse(eul, EULER_SIZE, EULER_SIZE, args[0], "mathutils.Euler()") == -1) {
return nullptr;
}
break;
default: {
PyErr_Format(PyExc_TypeError,
"mathutils.Euler(): "
"takes at most 2 arguments (%zd given)",
nargs);
return nullptr;
}
}
return Euler_CreatePyObject(eul, order, type);
return Euler_CreatePyObject(eul, order, (PyTypeObject *)type);
}
/** \} */
@ -926,7 +935,7 @@ PyTypeObject euler_Type = {
/*tp_dictoffset*/ 0,
/*tp_init*/ nullptr,
/*tp_alloc*/ nullptr,
/*tp_new*/ Euler_new,
/*tp_new*/ nullptr,
/*tp_free*/ nullptr,
/*tp_is_gc*/ (inquiry)BaseMathObject_is_gc,
/*tp_bases*/ nullptr,
@ -937,7 +946,7 @@ PyTypeObject euler_Type = {
/*tp_del*/ nullptr,
/*tp_version_tag*/ 0,
/*tp_finalize*/ nullptr,
/*tp_vectorcall*/ nullptr,
/*tp_vectorcall*/ Euler_vectorcall,
};
#ifdef MATH_STANDALONE

View file

@ -594,20 +594,24 @@ Mathutils_Callback mathutils_matrix_translation_cb = {
/** \name Matrix Type: `__new__` / `mathutils.Matrix()`
* \{ */
static PyObject *Matrix_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
static PyObject *Matrix_vectorcall(PyObject *type,
PyObject *const *args,
const size_t nargsf,
PyObject *kwnames)
{
if (kwds && PyDict_Size(kwds)) {
if (UNLIKELY(kwnames && PyDict_Size(kwnames))) {
PyErr_SetString(PyExc_TypeError,
"Matrix(): "
"takes no keyword args");
return nullptr;
}
switch (PyTuple_GET_SIZE(args)) {
switch (PyVectorcall_NARGS(nargsf)) {
case 0:
return Matrix_CreatePyObject(nullptr, 4, 4, type);
return Matrix_CreatePyObject(nullptr, 4, 4, (PyTypeObject *)type);
case 1: {
PyObject *arg = PyTuple_GET_ITEM(args, 0);
PyObject *arg = args[0];
/* Input is now as a sequence of rows so length of sequence
* is the number of rows */
@ -623,7 +627,8 @@ static PyObject *Matrix_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
if (col_num >= 2 && col_num <= 4) {
/* Sane row & col size, new matrix and assign as slice. */
PyObject *matrix = Matrix_CreatePyObject(nullptr, col_num, row_num, type);
PyObject *matrix = Matrix_CreatePyObject(
nullptr, col_num, row_num, (PyTypeObject *)type);
if (Matrix_ass_slice((MatrixObject *)matrix, 0, INT_MAX, arg) == 0) {
return matrix;
}
@ -637,7 +642,7 @@ static PyObject *Matrix_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
/* will overwrite error */
PyErr_SetString(PyExc_TypeError,
"Matrix(): "
"mathutils.Matrix(): "
"expects no args or a single arg containing 2-4 numeric sequences");
return nullptr;
}
@ -3564,7 +3569,7 @@ PyTypeObject matrix_Type = {
/*tp_dictoffset*/ 0,
/*tp_init*/ nullptr,
/*tp_alloc*/ nullptr,
/*tp_new*/ Matrix_new,
/*tp_new*/ nullptr,
/*tp_free*/ nullptr,
/*tp_is_gc*/ (inquiry)BaseMathObject_is_gc,
/*tp_bases*/ nullptr,
@ -3575,7 +3580,7 @@ PyTypeObject matrix_Type = {
/*tp_del*/ nullptr,
/*tp_version_tag*/ 0,
/*tp_finalize*/ nullptr,
/*tp_vectorcall*/ nullptr,
/*tp_vectorcall*/ Matrix_vectorcall,
};
#ifdef MATH_STANDALONE

View file

@ -104,32 +104,31 @@ static PyObject *Quaternion_to_tuple_ext(QuaternionObject *self, int ndigits)
/** \name Quaternion Type: `__new__` / `mathutils.Quaternion()`
* \{ */
static PyObject *Quaternion_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
static PyObject *Quaternion_vectorcall(PyObject *type,
PyObject *const *args,
const size_t nargsf,
PyObject *kwnames)
{
PyObject *seq = nullptr;
double angle = 0.0f;
float quat[QUAT_SIZE];
unit_qt(quat);
if (kwds && PyDict_Size(kwds)) {
if (UNLIKELY(kwnames && PyDict_Size(kwnames))) {
PyErr_SetString(PyExc_TypeError,
"mathutils.Quaternion(): "
"takes no keyword args");
return nullptr;
}
if (!PyArg_ParseTuple(args, "|Od:mathutils.Quaternion", &seq, &angle)) {
return nullptr;
}
switch (PyTuple_GET_SIZE(args)) {
const size_t nargs = PyVectorcall_NARGS(nargsf);
switch (nargs) {
case 0:
break;
case 1: {
int size;
const int size = mathutils_array_parse(
quat, 3, QUAT_SIZE, args[0], "mathutils.Quaternion()");
if ((size = mathutils_array_parse(quat, 3, QUAT_SIZE, seq, "mathutils.Quaternion()")) == -1)
{
if (UNLIKELY(size == -1)) {
return nullptr;
}
@ -146,16 +145,30 @@ static PyObject *Quaternion_new(PyTypeObject *type, PyObject *args, PyObject *kw
}
case 2: {
float axis[3];
if (mathutils_array_parse(axis, 3, 3, seq, "mathutils.Quaternion()") == -1) {
if (mathutils_array_parse(axis, 3, 3, args[0], "mathutils.Quaternion()") == -1) {
return nullptr;
}
angle = PyFloat_AsDouble(args[1]);
if (UNLIKELY(angle == -1.0 && PyErr_Occurred())) {
PyErr_Format(PyExc_TypeError,
"mathutils.Quaternion(): "
"angle must be a real number, not '%.200s'",
Py_TYPE(args[1])->tp_name);
return nullptr;
}
angle = angle_wrap_rad(angle); /* clamp because of precision issues */
axis_angle_to_quat(quat, axis, angle);
break;
/* PyArg_ParseTuple assures no more than 2 */
}
default: {
PyErr_Format(PyExc_TypeError,
"mathutils.Quaternion() "
"takes at most 2 arguments (%zd given)",
nargs);
return nullptr;
}
}
return Quaternion_CreatePyObject(quat, type);
return Quaternion_CreatePyObject(quat, (PyTypeObject *)type);
}
/** \} */
@ -1873,7 +1886,7 @@ PyTypeObject quaternion_Type = {
/*tp_dictoffset*/ 0,
/*tp_init*/ nullptr,
/*tp_alloc*/ nullptr,
/*tp_new*/ Quaternion_new,
/*tp_new*/ nullptr,
/*tp_free*/ nullptr,
/*tp_is_gc*/ (inquiry)BaseMathObject_is_gc,
/*tp_bases*/ nullptr,
@ -1884,7 +1897,7 @@ PyTypeObject quaternion_Type = {
/*tp_del*/ nullptr,
/*tp_version_tag*/ 0,
/*tp_finalize*/ nullptr,
/*tp_vectorcall*/ nullptr,
/*tp_vectorcall*/ Quaternion_vectorcall,
};
#ifdef MATH_STANDALONE

View file

@ -138,19 +138,22 @@ static PyObject *Vector_to_tuple_ex(VectorObject *self, int ndigits)
* Supports 2D, 3D, and 4D vector objects both int and float values
* accepted. Mixed float and int values accepted. Ints are parsed to float
*/
static PyObject *Vector_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
static PyObject *Vector_vectorcall(PyObject *type,
PyObject *const *args,
const size_t nargsf,
PyObject *kwnames)
{
float *vec = nullptr;
int vec_num = 3; /* default to a 3D vector */
if (kwds && PyDict_Size(kwds)) {
if (UNLIKELY(kwnames && PyDict_Size(kwnames))) {
PyErr_SetString(PyExc_TypeError,
"Vector(): "
"takes no keyword args");
return nullptr;
}
switch (PyTuple_GET_SIZE(args)) {
const size_t nargs = PyVectorcall_NARGS(nargsf);
switch (nargs) {
case 0:
vec = static_cast<float *>(PyMem_Malloc(vec_num * sizeof(float)));
@ -164,19 +167,18 @@ static PyObject *Vector_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
copy_vn_fl(vec, vec_num, 0.0f);
break;
case 1:
if ((vec_num = mathutils_array_parse_alloc(
&vec, 2, PyTuple_GET_ITEM(args, 0), "mathutils.Vector()")) == -1)
{
if ((vec_num = mathutils_array_parse_alloc(&vec, 2, args[0], "mathutils.Vector()")) == -1) {
return nullptr;
}
break;
default:
PyErr_SetString(PyExc_TypeError,
"mathutils.Vector(): "
"more than a single arg given");
PyErr_Format(PyExc_TypeError,
"mathutils.Vector(): "
"takes at most 1 argument (%zd given)",
nargs);
return nullptr;
}
return Vector_CreatePyObject_alloc(vec, vec_num, type);
return Vector_CreatePyObject_alloc(vec, vec_num, (PyTypeObject *)type);
}
/** \} */
@ -3483,7 +3485,7 @@ PyTypeObject vector_Type = {
/*tp_dictoffset*/ 0,
/*tp_init*/ nullptr,
/*tp_alloc*/ nullptr,
/*tp_new*/ Vector_new,
/*tp_new*/ nullptr,
/*tp_free*/ nullptr,
/*tp_is_gc*/ (inquiry)BaseMathObject_is_gc,
/*tp_bases*/ nullptr,
@ -3494,7 +3496,7 @@ PyTypeObject vector_Type = {
/*tp_del*/ nullptr,
/*tp_version_tag*/ 0,
/*tp_finalize*/ nullptr,
/*tp_vectorcall*/ nullptr,
/*tp_vectorcall*/ Vector_vectorcall,
};
#ifdef MATH_STANDALONE