Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize pg_Two(Ints/Floats)FromObj #3214

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 117 additions & 26 deletions src_c/base.c
Original file line number Diff line number Diff line change
Expand Up @@ -496,24 +496,19 @@ pg_base_get_init(PyObject *self, PyObject *_null)
static int
pg_IntFromObj(PyObject *obj, int *val)
{
int tmp_val;

if (PyFloat_Check(obj)) {
/* Python3.8 complains with deprecation warnings if we pass
* floats to PyLong_AsLong.
*/
double dv = PyFloat_AsDouble(obj);
tmp_val = (int)dv;
*val = (int)PyFloat_AS_DOUBLE(obj);
}
else {
tmp_val = PyLong_AsLong(obj);
}

if (tmp_val == -1 && PyErr_Occurred()) {
PyErr_Clear();
return 0;
*val = PyLong_AsLong(obj);
if (*val == -1 && PyErr_Occurred()) {
PyErr_Clear();
return 0;
}
}
*val = tmp_val;
return 1;
}

Expand All @@ -535,30 +530,79 @@ pg_IntFromObjIndex(PyObject *obj, int _index, int *val)
static int
pg_TwoIntsFromObj(PyObject *obj, int *val1, int *val2)
{
if (PyTuple_Check(obj) && PyTuple_Size(obj) == 1) {
// First, lets check the size. This returns -1 if invalid and may set an
// error.
Py_ssize_t obj_size = PySequence_Size(obj);

// If the object is a tuple of one element, try that one element.
if (obj_size == 1 && PyTuple_Check(obj)) {
return pg_TwoIntsFromObj(PyTuple_GET_ITEM(obj, 0), val1, val2);
}
if (!PySequence_Check(obj) || PySequence_Length(obj) != 2) {

// Otherwise lets make sure this is a legit sequence and has two elements.
// Some objects can passing PySequence_Size but fail PySequence_Check
// (like sets)
if (obj_size != 2 || !PySequence_Check(obj)) {
PyErr_Clear(); // Clear the potential error from PySequence_Size
return 0;
}

// Now we can extract the items, using this macro because we know
// obj is a PySequence.
PyObject *item1 = PySequence_ITEM(obj, 0);
PyObject *item2 = PySequence_ITEM(obj, 1);

// If either item is NULL lets get out of here
if (item1 == NULL || item2 == NULL) {
Py_XDECREF(item1);
Py_XDECREF(item2);
PyErr_Clear();
return 0;
}
if (!pg_IntFromObjIndex(obj, 0, val1) ||
!pg_IntFromObjIndex(obj, 1, val2)) {

// Fastest way to extract numbers I tested (in Python 3.13) is to extract
// Python floats as doubles with the below macro, and get everything else
// through PyLong_AsLong, using C casting to turn into the final type.
if (PyFloat_Check(item1)) {
*val1 = (int)PyFloat_AS_DOUBLE(item1);
}
else {
*val1 = PyLong_AsLong(item1);
}

if (PyFloat_Check(item2)) {
*val2 = (int)PyFloat_AS_DOUBLE(item2);
}
else {
*val2 = PyLong_AsLong(item2);
}

// This catches the case where either of the PyLong_AsLong's failed
if ((*val1 == -1 || *val2 == -1) && PyErr_Occurred()) {
PyErr_Clear();
Py_DECREF(item1);
Py_DECREF(item2);
return 0;
}

Py_DECREF(item1);
Py_DECREF(item2);
return 1;
}

static int
pg_FloatFromObj(PyObject *obj, float *val)
{
float f = (float)PyFloat_AsDouble(obj);

if (f == -1 && PyErr_Occurred()) {
PyErr_Clear();
return 0;
if (PyFloat_Check(obj)) {
*val = (float)PyFloat_AS_DOUBLE(obj);
}
else {
*val = (float)PyLong_AsLong(obj);
if (*val == -1.0f && PyErr_Occurred()) {
PyErr_Clear();
return 0;
}
}

*val = f;
return 1;
}

Expand All @@ -580,16 +624,63 @@ pg_FloatFromObjIndex(PyObject *obj, int _index, float *val)
static int
pg_TwoFloatsFromObj(PyObject *obj, float *val1, float *val2)
{
if (PyTuple_Check(obj) && PyTuple_Size(obj) == 1) {
// First, lets check the size. This returns -1 if invalid and may set an
// error.
Py_ssize_t obj_size = PySequence_Size(obj);

// If the object is a tuple of one element, try that one element.
if (obj_size == 1 && PyTuple_Check(obj)) {
return pg_TwoFloatsFromObj(PyTuple_GET_ITEM(obj, 0), val1, val2);
}
if (!PySequence_Check(obj) || PySequence_Length(obj) != 2) {

// Otherwise lets make sure this is a legit sequence and has two elements.
// Some objects can passing PySequence_Size but fail PySequence_Check
// (like sets)
if (obj_size != 2 || !PySequence_Check(obj)) {
PyErr_Clear(); // Clear the potential error from PySequence_Size
return 0;
}

// Now we can extract the items, using this macro because we know
// obj is a PySequence.
PyObject *item1 = PySequence_ITEM(obj, 0);
PyObject *item2 = PySequence_ITEM(obj, 1);

// If either item is NULL lets get out of here
if (item1 == NULL || item2 == NULL) {
Py_XDECREF(item1);
Py_XDECREF(item2);
PyErr_Clear();
return 0;
}
if (!pg_FloatFromObjIndex(obj, 0, val1) ||
!pg_FloatFromObjIndex(obj, 1, val2)) {

// Fastest way to extract numbers I tested (in Python 3.13) is to extract
// Python floats as doubles with the below macro, and get everything else
// through PyLong_AsLong, using C casting to turn into the final type.
if (PyFloat_Check(item1)) {
*val1 = (float)PyFloat_AS_DOUBLE(item1);
}
else {
*val1 = (float)PyLong_AsLong(item1);
}

if (PyFloat_Check(item2)) {
*val2 = (float)PyFloat_AS_DOUBLE(item2);
}
else {
*val2 = (float)PyLong_AsLong(item2);
}

// This catches the case where either of the PyLong_AsLong's failed
if ((*val1 == -1.0f || *val2 == -1.0f) && PyErr_Occurred()) {
PyErr_Clear();
Py_DECREF(item1);
Py_DECREF(item2);
return 0;
}

Py_DECREF(item1);
Py_DECREF(item2);
return 1;
}

Expand Down
Loading