Skip to content

Commit c808cb7

Browse files
committed
Optimize PyListWriter_FromBuffer()
1 parent 995f2d5 commit c808cb7

5 files changed

Lines changed: 112 additions & 64 deletions

File tree

‎Include/cpython/listobject.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,4 @@ PyAPI_FUNC(int) PyListWriter_FromFormat(
8383
...);
8484
PyAPI_FUNC(int) PyListWriter_FromBuffer(
8585
PyListWriter *writer,
86-
Py_buffer *buf);
86+
const Py_buffer *buf);

‎Include/internal/pycore_list.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ union _PyStackRef;
7979
PyAPI_FUNC(PyObject *)_PyList_FromStackRefStealOnSuccess(const union _PyStackRef *src, Py_ssize_t n);
8080
PyAPI_FUNC(PyObject *)_PyList_AsTupleAndClear(PyListObject *v);
8181

82+
extern void _PyListWriter_ResetSize(
83+
PyListWriter *writer,
84+
Py_ssize_t size);
85+
8286
#ifdef __cplusplus
8387
}
8488
#endif

‎Lib/test/test_capi/test_list.py‎

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
from test import support
66
from test.support import import_helper
77
from test.support import threading_helper
8+
try:
9+
import _testbuffer
10+
except ImportError:
11+
_testbuffer = None
812

913
_testcapi = import_helper.import_module('_testcapi')
1014
_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
@@ -429,6 +433,7 @@ def test_extend(self):
429433
# Test PyListWriter_Extend()
430434
self.check_extend('extend')
431435

436+
def test_extend_iter_error(self):
432437
class IterError:
433438
def __init__(self):
434439
self.index = 1
@@ -443,7 +448,8 @@ def __next__(self):
443448
raise ValueError("iter error")
444449
return value
445450

446-
# On error, restore the writer to its previous state
451+
# On error, restore the writer to its previous state:
452+
# discard added items
447453
iterable = IterError()
448454
writer = self.create_writer(0)
449455
writer.append("before")
@@ -495,6 +501,33 @@ def test_from_buffer(self):
495501
writer.from_buffer(memoryview(numbers))
496502
self.list_equal(writer.finish(), [1.5, 3.0, 5.0])
497503

504+
def test_from_buffer_memoryerror(self):
505+
# Inject MemoryError in the middle of the array: reset the writer
506+
# to its previous state, discard added float objects
507+
numbers = array.array('d', [2.0] * 128)
508+
view = memoryview(numbers)
509+
writer = self.create_writer(0)
510+
with self.assertRaises(MemoryError):
511+
with support.inject_memory_error_cm(start=len(numbers) // 2):
512+
writer.from_buffer(view)
513+
self.list_equal(writer.finish(), [])
514+
515+
@unittest.skipIf(_testbuffer is None, 'need _testbuffer')
516+
def test_from_buffer_ndarray(self):
517+
# PyListWriter_FromBuffer() requires 1 dimension
518+
array_0d = _testbuffer.ndarray(ord(b"a"), format='B',
519+
shape=(), strides=())
520+
array_2d = _testbuffer.ndarray(list(b"abcdefgh"), format='B',
521+
shape=(4, 2), strides=(2, 1))
522+
523+
for array in (array_0d, array_2d):
524+
with self.subTest(array=array):
525+
writer = self.create_writer(0)
526+
errmsg = 'require a buffer of 1 dimension, got 2 dimentions'
527+
with self.assertRaisesRegex(ValueError, errmsg):
528+
writer.from_buffer(array_2d)
529+
self.list_equal(writer.finish(), [])
530+
498531
def test_empty(self):
499532
list_get_allocated = _testcapi._list_get_allocated
500533
empty_tuple = ()

‎Objects/listobject.c‎

Lines changed: 25 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4553,6 +4553,28 @@ PyListWriter_Append(PyListWriter *writer, PyObject *item)
45534553
}
45544554

45554555

4556+
void
4557+
_PyListWriter_ResetSize(PyListWriter *writer, Py_ssize_t size)
4558+
{
4559+
assert(listwriter_check_consistency(writer));
4560+
PyListObject *list = _PyList_CAST(writer);
4561+
4562+
Py_ssize_t new_size = Py_SIZE(list);
4563+
assert(0 <= size && size <= new_size);
4564+
4565+
Py_SET_SIZE(list, size);
4566+
4567+
// Discard added items
4568+
PyObject **items = list->ob_item;
4569+
for (Py_ssize_t i = size; i < new_size; i++) {
4570+
Py_DECREF(items[i]);
4571+
#ifdef Py_DEBUG
4572+
items[i] = NULL;
4573+
#endif
4574+
}
4575+
}
4576+
4577+
45564578
// Similar to _list_extend() but don't lock the writer list.
45574579
// If you update this function, update also _list_extend().
45584580
int
@@ -4605,16 +4627,9 @@ PyListWriter_Extend(PyListWriter *writer, PyObject *iterable)
46054627
}
46064628

46074629
if (res < 0 && Py_SIZE(self) != old_size) {
4608-
// On error, reset the writer to its previous state
4609-
Py_ssize_t new_size = Py_SIZE(self);
4610-
Py_SET_SIZE(self, old_size);
4611-
PyObject **items = self->ob_item;
4612-
for (Py_ssize_t i = old_size; i < new_size; i++) {
4613-
Py_DECREF(items[i]);
4614-
#ifdef Py_DEBUG
4615-
items[i] = NULL;
4616-
#endif
4617-
}
4630+
// On error, reset the writer to its previous state:
4631+
// discard added items
4632+
_PyListWriter_ResetSize(writer, old_size);
46184633
}
46194634

46204635
return res;
@@ -4706,25 +4721,3 @@ PyListWriter_FromFormat(PyListWriter *writer, const char *format, ...)
47064721
}
47074722
return res;
47084723
}
4709-
4710-
4711-
int
4712-
PyListWriter_FromBuffer(PyListWriter *writer, Py_buffer *view)
4713-
{
4714-
extern int _PyMemoryView_ToListWriter(PyObject *self,
4715-
PyListWriter *writer);
4716-
4717-
if (view->ndim != 1) {
4718-
PyErr_SetString(PyExc_ValueError, "require a buffer of 1 dimension");
4719-
return -1;
4720-
}
4721-
4722-
PyObject *mview = PyMemoryView_FromBuffer(view);
4723-
if (mview == NULL) {
4724-
return -1;
4725-
}
4726-
4727-
int res = _PyMemoryView_ToListWriter(mview, writer);
4728-
Py_DECREF(mview);
4729-
return res;
4730-
}

‎Objects/memoryobject.c‎

Lines changed: 48 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include "Python.h"
1414
#include "pycore_abstract.h" // _PyIndex_Check()
15+
#include "pycore_list.h" // _PyListWriter_ResetSize()
1516
#include "pycore_memoryobject.h" // _PyManagedBuffer_Type
1617
#include "pycore_object.h" // _PyObject_GC_UNTRACK()
1718
#include "pycore_strhex.h" // _Py_strhex_with_sep()
@@ -1842,7 +1843,9 @@ unpack_single(PyMemoryViewObject *self, const char *ptr, const char *fmt)
18421843
unsigned char uc;
18431844
void *p;
18441845

1845-
CHECK_RELEASED_AGAIN(self);
1846+
if (self != NULL) {
1847+
CHECK_RELEASED_AGAIN(self);
1848+
}
18461849

18471850
#if PY_LITTLE_ENDIAN
18481851
int endian = 1;
@@ -2303,24 +2306,6 @@ tolist_base(PyMemoryViewObject *self, const char *ptr, const Py_ssize_t *shape,
23032306
return lst;
23042307
}
23052308

2306-
static int
2307-
tolistwriter(PyListWriter *writer, PyMemoryViewObject *self, const char *ptr,
2308-
const Py_ssize_t *shape, const Py_ssize_t *strides,
2309-
const Py_ssize_t *suboffsets, const char *fmt)
2310-
{
2311-
for (Py_ssize_t i = 0; i < shape[0]; ptr+=strides[0], i++) {
2312-
const char *xptr = ADJUST_PTR(ptr, suboffsets, 0);
2313-
PyObject *item = unpack_single(self, xptr, fmt);
2314-
if (item == NULL) {
2315-
return -1;
2316-
}
2317-
if (PyListWriter_AppendSteal(writer, item) < 0) {
2318-
return -1;
2319-
}
2320-
}
2321-
return 0;
2322-
}
2323-
23242309
/* Unpack a multi-dimensional array into a nested list.
23252310
Assumption: ndim >= 1. */
23262311
static PyObject *
@@ -2394,21 +2379,54 @@ memoryview_tolist_impl(PyMemoryViewObject *self)
23942379

23952380

23962381
int
2397-
_PyMemoryView_ToListWriter(PyObject *mview, PyListWriter *writer)
2382+
PyListWriter_FromBuffer(PyListWriter *writer, const Py_buffer *view)
23982383
{
2399-
assert(PyMemoryView_Check(mview));
2400-
PyMemoryViewObject *self = (PyMemoryViewObject *)mview;
2401-
const Py_buffer *view = &self->view;
2402-
assert(view->ndim == 1);
2403-
2404-
const char *fmt = adjust_fmt(view);
2405-
if (fmt == NULL) {
2384+
if (view->ndim != 1) {
2385+
PyErr_Format(PyExc_ValueError,
2386+
"require a buffer of 1 dimension, got %i dimentions",
2387+
view->ndim);
24062388
return -1;
24072389
}
24082390

2409-
return tolistwriter(writer, self, view->buf, view->shape,
2410-
view->strides, view->suboffsets,
2411-
fmt);
2391+
const char *fmt;
2392+
if (view->format != NULL) {
2393+
fmt = adjust_fmt(view);
2394+
if (fmt == NULL) {
2395+
return -1;
2396+
}
2397+
}
2398+
else {
2399+
// Same default value than init_shared_values()
2400+
fmt = "B";
2401+
}
2402+
2403+
PyListObject *list = _PyList_CAST(writer);
2404+
Py_ssize_t old_writer_size = Py_SIZE(list);
2405+
2406+
const char *ptr = view->buf;
2407+
const Py_ssize_t shape = view->shape ? view->shape[0] : view->len;
2408+
const Py_ssize_t strides = view->strides ? view->strides[0] : view->itemsize;
2409+
const Py_ssize_t *suboffsets = view->suboffsets;
2410+
2411+
for (Py_ssize_t i = 0; i < shape; ptr += strides, i++) {
2412+
const char *xptr = ADJUST_PTR(ptr, suboffsets, 0);
2413+
PyObject *item = unpack_single(NULL, xptr, fmt);
2414+
if (item == NULL) {
2415+
goto error;
2416+
}
2417+
if (PyListWriter_AppendSteal(writer, item) < 0) {
2418+
goto error;
2419+
}
2420+
}
2421+
return 0;
2422+
2423+
error:
2424+
if (Py_SIZE(list) != old_writer_size) {
2425+
// On error, reset the writer to its previous state:
2426+
// discard added items
2427+
_PyListWriter_ResetSize(writer, old_writer_size);
2428+
}
2429+
return -1;
24122430
}
24132431

24142432

0 commit comments

Comments
 (0)