https://github.com/python/cpython/commit/61fdee34eed9ea6f03190a5ddd7bb0c7ccc87be9
commit: 61fdee34eed9ea6f03190a5ddd7bb0c7ccc87be9
branch: main
author: Cody Maloney <[email protected]>
committer: cmaloney <[email protected]>
date: 2026-11-08T12:22:40-08:00
summary:

gh-87613: Use AC VC for io.BytesIO (#160855)

BytesIO is commonly constructed in the test suite and gets significantly
faster to construct. Argument error messages are unchanged.

pyperf microbenchmark (PGO+LTO, run serially):

| Benchmark                    | base    | bytesio               |
|------------------------------|:-------:|:---------------------:|
| BytesIO(bytes)               | 54.2 ns | 27.0 ns: 2.01x faster |
| BytesIO(bytearray)           | 76.7 ns | 38.6 ns: 1.99x faster |
| BytesIO(initial_bytes=bytes) | 56.5 ns | 34.8 ns: 1.63x faster |
| BytesIO()                    | 40.9 ns | 25.4 ns: 1.61x faster |
| Geometric mean               | (ref)   | 1.48x faster          |

Benchmark hidden because not significant (2): `BytesIO.__new__`,
subclass(bytes)

Benchmark code:

```python
"""BytesIO() construction: AC @vectorcall vs tp_new + tp_init."""
import pyperf

runner = pyperf.Runner()

setup = "from io import BytesIO; b = b'abcdefgh'"
runner.timeit('BytesIO()', 'BytesIO()', setup)
runner.timeit('BytesIO(bytes)', 'BytesIO(b)', setup)
runner.timeit('BytesIO(bytearray)', 'BytesIO(x)', setup + '; x = bytearray(b)')
runner.timeit('BytesIO(initial_bytes=bytes)', 'BytesIO(initial_bytes=b)', setup)
runner.timeit('subclass(bytes)', 'S(b)', setup + '\nclass S(BytesIO): pass')
runner.timeit('BytesIO.__new__', 'new(BytesIO)',
              setup + '; new = BytesIO.__new__')
```

Co-authored-by: Claude Opus 5.5 <[email protected]>

files:
M Modules/_io/bytesio.c
M Modules/_io/clinic/bytesio.c.h

diff --git a/Modules/_io/bytesio.c b/Modules/_io/bytesio.c
index 2571da4538363f1..9bec70acebb3598 100644
--- a/Modules/_io/bytesio.c
+++ b/Modules/_io/bytesio.c
@@ -1118,6 +1118,7 @@ bytesio_new(PyTypeObject *type, PyObject *args, PyObject 
*kwds)
 }
 
 /*[clinic input]
+@vectorcall
 @critical_section
 _io.BytesIO.__init__
     initial_bytes as initvalue: object(c_default="NULL") = b''
@@ -1127,7 +1128,7 @@ Buffered I/O implementation using an in-memory bytes 
buffer.
 
 static int
 _io_BytesIO___init___impl(bytesio *self, PyObject *initvalue)
-/*[clinic end generated code: output=65c0c51e24c5b621 input=3da5a74ee4c4f1ac]*/
+/*[clinic end generated code: output=65c0c51e24c5b621 input=a6dc38082204df28]*/
 {
     if (FT_ATOMIC_LOAD_SSIZE_RELAXED(self->exports) > 0) {
         PyErr_SetString(PyExc_BufferError,
@@ -1256,6 +1257,7 @@ static PyType_Slot bytesio_slots[] = {
     {Py_tp_getset, bytesio_getsetlist},
     {Py_tp_init, _io_BytesIO___init__},
     {Py_tp_new, bytesio_new},
+    {Py_tp_vectorcall, _io_BytesIO_vectorcall},
     {0, NULL},
 };
 
diff --git a/Modules/_io/clinic/bytesio.c.h b/Modules/_io/clinic/bytesio.c.h
index b2305e35ca81e75..e48d71397367daf 100644
--- a/Modules/_io/clinic/bytesio.c.h
+++ b/Modules/_io/clinic/bytesio.c.h
@@ -4,11 +4,11 @@ preserve
 
 #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
 #  include "pycore_gc.h"          // PyGC_Head
-#  include "pycore_runtime.h"     // _Py_ID()
 #endif
 #include "pycore_abstract.h"      // _Py_convert_optional_to_ssize_t()
 #include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION()
 #include "pycore_modsupport.h"    // _PyArg_CheckPositional()
+#include "pycore_runtime.h"       // _Py_SINGLETON()
 
 PyDoc_STRVAR(_io_BytesIO_closed__doc__,
 "True if the file is closed.");
@@ -716,7 +716,8 @@ static int
 _io_BytesIO___init___impl(bytesio *self, PyObject *initvalue);
 
 static int
-_io_BytesIO___init__(PyObject *self, PyObject *args, PyObject *kwargs)
+_io_BytesIO___init___helper(PyObject *self, PyObject *const *args,
+    Py_ssize_t nargs, Py_ssize_t nkw, PyObject *kwargs, PyObject *kwnames)
 {
     int return_value = -1;
     #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
@@ -748,11 +749,10 @@ _io_BytesIO___init__(PyObject *self, PyObject *args, 
PyObject *kwargs)
     #undef KWTUPLE
     PyObject *argsbuf[1];
     PyObject * const *fastargs;
-    Py_ssize_t nargs = PyTuple_GET_SIZE(args);
-    Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 0;
+    Py_ssize_t noptargs = nargs + nkw - 0;
     PyObject *initvalue = NULL;
 
-    fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, 
kwargs, NULL, &_parser,
+    fastargs = _PyArg_UnpackKeywords(args, nargs, kwargs, kwnames, &_parser,
             /*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
     if (!fastargs) {
         goto exit;
@@ -770,6 +770,67 @@ _io_BytesIO___init__(PyObject *self, PyObject *args, 
PyObject *kwargs)
     return return_value;
 }
 
+static int
+_io_BytesIO___init__(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+    return _io_BytesIO___init___helper(self, _PyTuple_CAST(args)->ob_item,
+        PyTuple_GET_SIZE(args),
+        kwargs ? PyDict_GET_SIZE(kwargs) : 0,
+        kwargs, NULL);
+}
+
+static PyObject *
+_io_BytesIO_vectorcall(PyObject *type, PyObject *const *args,
+    size_t nargsf, PyObject *kwnames)
+{
+    PyObject *return_value = NULL;
+    Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
+    PyObject *self;
+    int _result;
+    PyObject *initvalue = NULL;
+
+    assert(_PyType_CAST(type)->tp_init == _io_BytesIO___init__);
+    /* Make sure the type object is immutable: the generated
+     * vectorcall doesn't deal e.g. with users reassigning __init__. */
+    assert(PyType_HasFeature(_PyType_CAST(type), Py_TPFLAGS_IMMUTABLETYPE));
+    if (kwnames != NULL || nargs > 1) {
+        self = _PyType_CAST(type)->tp_new(_PyType_CAST(type),
+            (PyObject *)&_Py_SINGLETON(tuple_empty), NULL);
+        if (self == NULL) {
+            return NULL;
+        }
+        _result = _io_BytesIO___init___helper(self, args, nargs,
+            kwnames ? PyTuple_GET_SIZE(kwnames) : 0,
+            NULL, kwnames);
+        if (_result != 0) {
+            Py_DECREF(self);
+            return NULL;
+        }
+        return self;
+    }
+    if (nargs < 1) {
+        goto skip_optional;
+    }
+    initvalue = args[0];
+skip_optional:
+    self = _PyType_CAST(type)->tp_new(_PyType_CAST(type),
+        (PyObject *)&_Py_SINGLETON(tuple_empty), NULL);
+    if (self == NULL) {
+        goto exit;
+    }
+    Py_BEGIN_CRITICAL_SECTION(self);
+    _result = _io_BytesIO___init___impl((bytesio *)self, initvalue);
+    Py_END_CRITICAL_SECTION();
+    if (_result != 0) {
+        Py_DECREF(self);
+        goto exit;
+    }
+    return_value = self;
+
+exit:
+    return return_value;
+}
+
 PyDoc_STRVAR(_io_BytesIO___sizeof____doc__,
 "__sizeof__($self, /)\n"
 "--\n"
@@ -795,4 +856,4 @@ _io_BytesIO___sizeof__(PyObject *self, PyObject 
*Py_UNUSED(ignored))
 }
 #define _IO_BYTESIO_CLOSED_GETSETDEF {"closed", 
(getter)_io_BytesIO_closed_get, (setter)NULL, _io_BytesIO_closed__doc__},
 
-/*[clinic end generated code: output=a2bc96c358d6e240 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=b9d367ad8e0b44d2 input=a9049054013a1b77]*/

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to