-
-
Notifications
You must be signed in to change notification settings - Fork 35k
bpo-34151: Improve performance of some list operations #8332
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
Changes from all commits
efce592
b5da6db
50f71bd
c38c50c
604ae8b
5a6de07
3d685b1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| Performance of list concatenation, repetition and slicing operations is | ||
| slightly improved. Patch by Sergey Fedoseev. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -180,6 +180,23 @@ PyList_New(Py_ssize_t size) | |
| return (PyObject *) op; | ||
| } | ||
|
|
||
| static PyObject * | ||
| list_new_prealloc(Py_ssize_t size) | ||
| { | ||
| PyListObject *op = (PyListObject *) PyList_New(0); | ||
| if (size == 0 || op == NULL) { | ||
| return (PyObject *) op; | ||
| } | ||
| assert(op->ob_item == NULL); | ||
| op->ob_item = PyMem_New(PyObject *, size); | ||
| if (op->ob_item == NULL) { | ||
| Py_DECREF(op); | ||
| return PyErr_NoMemory(); | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From this point, it is possible that a GC collection will access the list. It would be safer to not call _PyObject_GC_TRACK(op) before the list is fully initialized. I suggest to modify list_new_prealloc() to avoid _PyObject_GC_TRACK(op), but call in the caller. Maybe you can rewrite the code so PyList_New() calls list_new_prealloc(), and not the opposite?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you elaborate on what do you mean exactly by not "fully initialized" here? I think I could rewrite the whole patch using existing
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. list_dealloc() will crash if it's called on the list just after list_new_prealloc(), since items are uninitialized, whereas list_dealloc() calls Py_XDECREF() on each item. But the main issue is list_traverse(). If the garbage collection is triggered, list_traverse() will dereference uninitialized object pointers and so crash.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. list_traverse doesn’t touch preallocated area. |
||
| op->allocated = size; | ||
| return (PyObject *) op; | ||
| } | ||
|
|
||
| Py_ssize_t | ||
| PyList_Size(PyObject *op) | ||
| { | ||
|
|
@@ -438,7 +455,7 @@ list_slice(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh) | |
| else if (ihigh > Py_SIZE(a)) | ||
| ihigh = Py_SIZE(a); | ||
| len = ihigh - ilow; | ||
| np = (PyListObject *) PyList_New(len); | ||
| np = (PyListObject *) list_new_prealloc(len); | ||
| if (np == NULL) | ||
| return NULL; | ||
|
|
||
|
|
@@ -449,6 +466,7 @@ list_slice(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh) | |
| Py_INCREF(v); | ||
| dest[i] = v; | ||
| } | ||
| Py_SIZE(np) = len; | ||
| return (PyObject *)np; | ||
| } | ||
|
|
||
|
|
@@ -479,7 +497,7 @@ list_concat(PyListObject *a, PyObject *bb) | |
| if (Py_SIZE(a) > PY_SSIZE_T_MAX - Py_SIZE(b)) | ||
| return PyErr_NoMemory(); | ||
| size = Py_SIZE(a) + Py_SIZE(b); | ||
| np = (PyListObject *) PyList_New(size); | ||
| np = (PyListObject *) list_new_prealloc(size); | ||
| if (np == NULL) { | ||
| return NULL; | ||
| } | ||
|
|
@@ -497,6 +515,7 @@ list_concat(PyListObject *a, PyObject *bb) | |
| Py_INCREF(v); | ||
| dest[i] = v; | ||
| } | ||
| Py_SIZE(np) = size; | ||
| return (PyObject *)np; | ||
| #undef b | ||
| } | ||
|
|
@@ -516,28 +535,30 @@ list_repeat(PyListObject *a, Py_ssize_t n) | |
| size = Py_SIZE(a) * n; | ||
| if (size == 0) | ||
| return PyList_New(0); | ||
| np = (PyListObject *) PyList_New(size); | ||
| np = (PyListObject *) list_new_prealloc(size); | ||
| if (np == NULL) | ||
| return NULL; | ||
|
|
||
| items = np->ob_item; | ||
| if (Py_SIZE(a) == 1) { | ||
| items = np->ob_item; | ||
| elem = a->ob_item[0]; | ||
| for (i = 0; i < n; i++) { | ||
| items[i] = elem; | ||
| Py_INCREF(elem); | ||
| } | ||
| return (PyObject *) np; | ||
| } | ||
| p = np->ob_item; | ||
| items = a->ob_item; | ||
| for (i = 0; i < n; i++) { | ||
| for (j = 0; j < Py_SIZE(a); j++) { | ||
| *p = items[j]; | ||
| Py_INCREF(*p); | ||
| p++; | ||
| } | ||
| else { | ||
| p = np->ob_item; | ||
| items = a->ob_item; | ||
| for (i = 0; i < n; i++) { | ||
| for (j = 0; j < Py_SIZE(a); j++) { | ||
| *p = items[j]; | ||
| Py_INCREF(*p); | ||
| p++; | ||
| } | ||
| } | ||
| } | ||
| Py_SIZE(np) = size; | ||
| return (PyObject *) np; | ||
| } | ||
|
|
||
|
|
@@ -2738,7 +2759,7 @@ list_subscript(PyListObject* self, PyObject* item) | |
| return list_slice(self, start, stop); | ||
| } | ||
| else { | ||
| result = PyList_New(slicelength); | ||
| result = list_new_prealloc(slicelength); | ||
| if (!result) return NULL; | ||
|
|
||
| src = self->ob_item; | ||
|
|
@@ -2749,7 +2770,7 @@ list_subscript(PyListObject* self, PyObject* item) | |
| Py_INCREF(it); | ||
| dest[i] = it; | ||
| } | ||
|
|
||
| Py_SIZE(result) = slicelength; | ||
| return result; | ||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please add a short comment to explain when you consider that this function is more appropriate than PyList_New()? I understand that it should be preferred when the caller will always immediately fill all allocated list items