Skip to content

Commit d898d27

Browse files
committed
gh-132108: Add Buffer Protocol support to int.from_bytes
Speed up conversion from `bytes-like` objects like `bytearray` while keeping conversion from `bytes` stable. On a `--with-lto --enable-optimizaitons` build on my 64 bit Linux box: new: from_bytes_flags: Mean +- std dev: 28.6 ns +- 0.5 ns bench_convert[bytes]: Mean +- std dev: 50.4 ns +- 1.4 ns bench_convert[bytearray]: Mean +- std dev: 51.3 ns +- 0.7 ns old: from_bytes_flags: Mean +- std dev: 28.1 ns +- 1.1 ns bench_convert[bytes]: Mean +- std dev: 50.3 ns +- 4.3 ns bench_convert[bytearray]: Mean +- std dev: 64.7 ns +- 0.9 ns Benchmark code: ```python import pyperf import time def from_bytes_flags(loops): range_it = range(loops) t0 = time.perf_counter() for _ in range_it: int.from_bytes(b'\x00\x10', byteorder='big') int.from_bytes(b'\x00\x10', byteorder='little') int.from_bytes(b'\xfc\x00', byteorder='big', signed=True) int.from_bytes(b'\xfc\x00', byteorder='big', signed=False) int.from_bytes([255, 0, 0], byteorder='big') return time.perf_counter() - t0 sample_bytes = [ b'', b'\x00', b'\x01', b'\x7f', b'\x80', b'\xff', b'\x01\x00', b'\x7f\xff', b'\x80\x00', b'\xff\xff', b'\x01\x00\x00', ] sample_bytearray = [bytearray(v) for v in sample_bytes] def bench_convert(loops, values): range_it = range(loops) t0 = time.perf_counter() for _ in range_it: for val in values: int.from_bytes(val) return time.perf_counter() - t0 runner = pyperf.Runner() runner.bench_time_func('from_bytes_flags', from_bytes_flags, inner_loops=10) runner.bench_time_func('bench_convert[bytes]', bench_convert, sample_bytes, inner_loops=10) runner.bench_time_func('bench_convert[bytearray]', bench_convert, sample_bytearray, inner_loops=10) ```
1 parent 231a50f commit d898d27

2 files changed

Lines changed: 23 additions & 8 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Speed up :meth:`int.from_bytes` when passed a bytes-like object such as a
2+
:class:`bytearray`.

Objects/longobject.c

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6439,6 +6439,7 @@ int_from_bytes_impl(PyTypeObject *type, PyObject *bytes_obj,
64396439
{
64406440
int little_endian;
64416441
PyObject *long_obj, *bytes;
6442+
Py_buffer view;
64426443

64436444
if (byteorder == NULL)
64446445
little_endian = 0;
@@ -6452,14 +6453,26 @@ int_from_bytes_impl(PyTypeObject *type, PyObject *bytes_obj,
64526453
return NULL;
64536454
}
64546455

6455-
bytes = PyObject_Bytes(bytes_obj);
6456-
if (bytes == NULL)
6457-
return NULL;
6458-
6459-
long_obj = _PyLong_FromByteArray(
6460-
(unsigned char *)PyBytes_AS_STRING(bytes), Py_SIZE(bytes),
6461-
little_endian, is_signed);
6462-
Py_DECREF(bytes);
6456+
/* Use buffer protocol to avoid copies. */
6457+
if (PyObject_CheckBuffer(bytes_obj)) {
6458+
if (PyObject_GetBuffer(bytes_obj, &view, PyBUF_SIMPLE) != 0) {
6459+
return NULL;
6460+
}
6461+
long_obj = _PyLong_FromByteArray(view.buf, view.len, little_endian,
6462+
is_signed);
6463+
PyBuffer_Release(&view);
6464+
}
6465+
else {
6466+
/* fallback: Construct a bytes then convert. */
6467+
bytes = PyObject_Bytes(bytes_obj);
6468+
if (bytes == NULL) {
6469+
return NULL;
6470+
}
6471+
long_obj = _PyLong_FromByteArray(
6472+
(unsigned char *)PyBytes_AS_STRING(bytes), Py_SIZE(bytes),
6473+
little_endian, is_signed);
6474+
Py_DECREF(bytes);
6475+
}
64636476

64646477
if (long_obj != NULL && type != &PyLong_Type) {
64656478
Py_SETREF(long_obj, PyObject_CallOneArg((PyObject *)type, long_obj));

0 commit comments

Comments
 (0)