Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
48d758c
use memcpy in list repat and list inplace repeat
eendebakpt Mar 22, 2022
e32f554
use memcpy in tuple repeat
eendebakpt Mar 22, 2022
3408397
fix debug build
eendebakpt Mar 22, 2022
2ddedf9
remove double ref counting
eendebakpt Mar 22, 2022
74e7cc0
📜🤖 Added by blurb_it.
blurb-it[bot] Mar 22, 2022
493bb9d
make implementations of list repeat and tuple repeat similar
eendebakpt Mar 22, 2022
4f292a4
eliminate duplicated code
eendebakpt Mar 28, 2022
5445cfb
fix ci
eendebakpt Mar 28, 2022
3d287c7
remove Py_INCREF_n from public api
eendebakpt Apr 22, 2022
640643e
take multiplication out of the loop
eendebakpt Apr 22, 2022
7b5e5b2
Merge branch 'main' into performance/list_repeat_v2_specialized
eendebakpt May 11, 2022
66cc8ae
fix formatting of news item
eendebakpt May 11, 2022
30baa6f
Merge branch 'main' into performance/list_repeat_v2_specialized
eendebakpt May 17, 2022
820add4
Merge branch 'main' into performance/list_repeat_v2_specialized
eendebakpt May 20, 2022
5750f01
address review comments
eendebakpt May 25, 2022
798e732
revert check on input_size==0
eendebakpt May 25, 2022
ae0c64e
Merge branch 'main' into performance/list_repeat_v2_specialized
eendebakpt Jun 2, 2022
70c7fce
address review comment to rename Py_RefcntAdd to _PyRefcntAdd
eendebakpt Jun 7, 2022
18ebad8
Merge branch 'main' into performance/list_repeat_v2_specialized
eendebakpt Jun 7, 2022
be45ed9
Merge branch 'main' into performance/list_repeat_v2_specialized
eendebakpt Jun 24, 2022
e9065f6
Merge branch 'main' into performance/list_repeat_v2_specialized
eendebakpt Jul 4, 2022
d47f6d7
Apply suggestions from code review
eendebakpt Jul 12, 2022
4584415
Merge branch 'main' into performance/list_repeat_v2_specialized
eendebakpt Jul 12, 2022
f6e6bdf
Update Misc/NEWS.d/next/Core and Builtins/2022-03-22-13-12-27.bpo-470…
eendebakpt Jul 12, 2022
a8c32ac
Apply suggestions from code review
eendebakpt Jul 22, 2022
8f152f2
Merge branch 'main' into performance/list_repeat_v2_specialized
eendebakpt Jul 22, 2022
4211b00
Apply suggestions from code review
eendebakpt Jul 24, 2022
7056599
pep7
eendebakpt Jul 24, 2022
53fa880
move assert to _Py_memory_repeat
eendebakpt Jul 24, 2022
516f091
Merge branch 'main' into performance/list_repeat_v2_specialized
sweeneyde Jul 25, 2022
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
13 changes: 13 additions & 0 deletions Include/internal/pycore_list.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,19 @@ _PyList_AppendTakeRef(PyListObject *self, PyObject *newitem)
return _PyList_AppendTakeRefListResize(self, newitem);
}

// Repeat the bytes of a buffer in place
static inline void
_Py_memory_repeat(char* dest, Py_ssize_t len_dest, Py_ssize_t len_src)
{
assert(len_src > 0);
Py_ssize_t copied = len_src;
while (copied < len_dest) {
Py_ssize_t bytes_to_copy = Py_MIN(copied, len_dest - copied);
memcpy(dest + copied, dest, bytes_to_copy);
copied += bytes_to_copy;
}
}

typedef struct {
PyObject_HEAD
Py_ssize_t it_index;
Expand Down
10 changes: 10 additions & 0 deletions Include/internal/pycore_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ PyAPI_FUNC(void) _Py_NO_RETURN _Py_FatalRefcountErrorFunc(
#define _Py_FatalRefcountError(message) \
_Py_FatalRefcountErrorFunc(__func__, (message))

// Increment reference count by n
static inline void _Py_RefcntAdd(PyObject* op, Py_ssize_t n)
{
#ifdef Py_REF_DEBUG
_Py_RefTotal += n;
#endif
op->ob_refcnt += n;
}
#define _Py_RefcntAdd(op, n) _Py_RefcntAdd(_PyObject_CAST(op), n)

static inline void
_Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improve performance of repetition of :class:`list` and :class:`tuple` by using ``memcpy`` to copy data and performing the reference increments in one step.
73 changes: 31 additions & 42 deletions Objects/listobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -551,47 +551,41 @@ list_concat(PyListObject *a, PyObject *bb)
static PyObject *
list_repeat(PyListObject *a, Py_ssize_t n)
{
Py_ssize_t size;
PyListObject *np;
if (n < 0)
n = 0;
if (n > 0 && Py_SIZE(a) > PY_SSIZE_T_MAX / n)
return PyErr_NoMemory();
size = Py_SIZE(a) * n;
if (size == 0)
const Py_ssize_t input_size = Py_SIZE(a);
if (input_size == 0 || n <= 0)
return PyList_New(0);
np = (PyListObject *) list_new_prealloc(size);
assert(n > 0);

if (input_size > PY_SSIZE_T_MAX / n)
return PyErr_NoMemory();
Py_ssize_t output_size = input_size * n;

PyListObject *np = (PyListObject *) list_new_prealloc(output_size);
if (np == NULL)
return NULL;

PyObject **dest = np->ob_item;
PyObject **dest_end = dest + size;
if (Py_SIZE(a) == 1) {
if (input_size == 1) {
PyObject *elem = a->ob_item[0];
Py_SET_REFCNT(elem, Py_REFCNT(elem) + n);
#ifdef Py_REF_DEBUG
_Py_RefTotal += n;
#endif
_Py_RefcntAdd(elem, n);
PyObject **dest_end = dest + output_size;
while (dest < dest_end) {
*dest++ = elem;
}
}
else {
PyObject **src = a->ob_item;
PyObject **src_end = src + Py_SIZE(a);
PyObject **src_end = src + input_size;
while (src < src_end) {
Py_SET_REFCNT(*src, Py_REFCNT(*src) + n);
#ifdef Py_REF_DEBUG
_Py_RefTotal += n;
#endif
*dest++ = *src++;
}
// Now src chases after dest in the same buffer
src = np->ob_item;
while (dest < dest_end) {
_Py_RefcntAdd(*src, n);
*dest++ = *src++;
}

_Py_memory_repeat((char *)np->ob_item, sizeof(PyObject *)*output_size,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self: this cannot overflow because list_new_prealloc uses PyMem_New, and so this multiplication already succeeded at some point.

sizeof(PyObject *)*input_size);
}
Py_SET_SIZE(np, size);

Py_SET_SIZE(np, output_size);
return (PyObject *) np;
}

Expand Down Expand Up @@ -743,12 +737,8 @@ PyList_SetSlice(PyObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyObject *v)
static PyObject *
list_inplace_repeat(PyListObject *self, Py_ssize_t n)
{
PyObject **items;
Py_ssize_t size, i, j, p;


size = PyList_GET_SIZE(self);
if (size == 0 || n == 1) {
Py_ssize_t input_size = PyList_GET_SIZE(self);
if (input_size == 0 || n == 1) {
Py_INCREF(self);
return (PyObject *)self;
}
Expand All @@ -759,22 +749,21 @@ list_inplace_repeat(PyListObject *self, Py_ssize_t n)
return (PyObject *)self;
}

if (size > PY_SSIZE_T_MAX / n) {
if (input_size > PY_SSIZE_T_MAX / n) {
return PyErr_NoMemory();
}
Py_ssize_t output_size = input_size * n;

if (list_resize(self, size*n) < 0)
if (list_resize(self, output_size) < 0)
return NULL;

p = size;
items = self->ob_item;
for (i = 1; i < n; i++) { /* Start counting at 1, not 0 */
for (j = 0; j < size; j++) {
PyObject *o = items[j];
Py_INCREF(o);
items[p++] = o;
}
PyObject **items = self->ob_item;
for (Py_ssize_t j = 0; j < input_size; j++) {
_Py_RefcntAdd(items[j], n-1);
}
_Py_memory_repeat((char *)items, sizeof(PyObject *)*output_size,
sizeof(PyObject *)*input_size);

Py_INCREF(self);
return (PyObject *)self;
}
Expand Down
41 changes: 18 additions & 23 deletions Objects/tupleobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -495,52 +495,47 @@ tupleconcat(PyTupleObject *a, PyObject *bb)
static PyObject *
tuplerepeat(PyTupleObject *a, Py_ssize_t n)
{
Py_ssize_t size;
PyTupleObject *np;
if (Py_SIZE(a) == 0 || n == 1) {
const Py_ssize_t input_size = Py_SIZE(a);
if (input_size == 0 || n == 1) {
if (PyTuple_CheckExact(a)) {
/* Since tuples are immutable, we can return a shared
copy in this case */
Py_INCREF(a);
return (PyObject *)a;
}
}
if (Py_SIZE(a) == 0 || n <= 0) {
if (input_size == 0 || n <= 0) {
return tuple_get_empty();
}
if (n > PY_SSIZE_T_MAX / Py_SIZE(a))
assert(n>0);

if (input_size > PY_SSIZE_T_MAX / n)
return PyErr_NoMemory();
size = Py_SIZE(a) * n;
np = tuple_alloc(size);
Py_ssize_t output_size = input_size * n;

PyTupleObject *np = tuple_alloc(output_size);
if (np == NULL)
return NULL;

PyObject **dest = np->ob_item;
PyObject **dest_end = dest + size;
if (Py_SIZE(a) == 1) {
if (input_size == 1) {
PyObject *elem = a->ob_item[0];
Py_SET_REFCNT(elem, Py_REFCNT(elem) + n);
#ifdef Py_REF_DEBUG
_Py_RefTotal += n;
#endif
_Py_RefcntAdd(elem, n);
PyObject **dest_end = dest + output_size;
while (dest < dest_end) {
*dest++ = elem;
}
}
else {
PyObject **src = a->ob_item;
PyObject **src_end = src + Py_SIZE(a);
PyObject **src_end = src + input_size;
while (src < src_end) {
Py_SET_REFCNT(*src, Py_REFCNT(*src) + n);
#ifdef Py_REF_DEBUG
_Py_RefTotal += n;
#endif
*dest++ = *src++;
}
// Now src chases after dest in the same buffer
src = np->ob_item;
while (dest < dest_end) {
_Py_RefcntAdd(*src, n);
*dest++ = *src++;
}

_Py_memory_repeat((char *)np->ob_item, sizeof(PyObject *)*output_size,
sizeof(PyObject *)*input_size);
}
_PyObject_GC_TRACK(np);
return (PyObject *) np;
Expand Down