PEP 757: use PyLong_Export (#3970)

This commit is contained in:
Sergey B Kirpichev 2024-09-17 18:38:13 +03:00 committed by GitHub
parent b6cf6d47f3
commit b4e7700a56
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 49 additions and 39 deletions

View File

@ -17,8 +17,7 @@ Abstract
======== ========
Add a new C API to import and export Python integers, :class:`int` objects: Add a new C API to import and export Python integers, :class:`int` objects:
especially ``PyLongWriter_Create()`` and ``PyLong_AsDigitArray()`` especially ``PyLongWriter_Create()`` and ``PyLong_Export()`` functions.
functions.
Rationale Rationale
@ -89,9 +88,12 @@ Export API
Export a Python integer as a digits array:: Export a Python integer as a digits array::
typedef struct PyLong_DigitArray { typedef struct PyLongExport {
// use value, if digits set to NULL.
int64_t value;
// 1 if the number is negative, 0 otherwise. // 1 if the number is negative, 0 otherwise.
int negative; uint8_t negative;
// Number of digits in the 'digits' array. // Number of digits in the 'digits' array.
Py_ssize_t ndigits; Py_ssize_t ndigits;
@ -101,49 +103,47 @@ Export a Python integer as a digits array::
// Member used internally, must not be used for other purpose. // Member used internally, must not be used for other purpose.
Py_uintptr_t _reserved; Py_uintptr_t _reserved;
} PyLong_DigitArray; } PyLongExport;
PyAPI_FUNC(int) PyLong_AsDigitArray( int PyLong_Export(PyObject *obj, PyLongExport *array);
PyObject *obj,
PyLong_DigitArray *array);
PyAPI_FUNC(void) PyLong_FreeDigitArray(
PyLong_DigitArray *array);
On CPython 3.14, no memory copy is needed, it's just a thin wrapper to On CPython 3.14, no memory copy is needed, it's just a thin wrapper to
expose Python int internal digits array. expose Python int internal digits array.
``PyLong_DigitArray.obj`` stores a strong reference to the Python ``PyLongExport._reserved``, if ``digits`` not ``NULL``, stores a strong
:class:`int` object to make sure that that structure remains valid until reference to the Python :class:`int` object to make sure that that structure
``PyLong_FreeDigitArray()`` is called. remains valid until ``PyLong_FreeExport()`` is called.
PyLong_AsDigitArray() PyLong_Export()
^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^
API:: API::
int PyLong_AsDigitArray(PyObject *obj, PyLong_DigitArray *array) int PyLong_Export(PyObject *obj, PyLongExport *array)
Export a Python :class:`int` object as a digits array. Export a Python :class:`int` object as a digits array.
On success, set *\*array* and return 0. On success, set *\*array* and return 0.
On error, set an exception and return -1. On error, set an exception and return -1.
This function always succeeds if *obj* is a Python :class:`int` object or a If ``array->digits`` set to ``NULL``, caller must use instead ``array->value``
subclass. to get value of an :class:`int` object.
``PyLong_FreeDigitArray()`` must be called once done with using CPython implementation detail: This function always succeeds if *obj* is a
*array*. Python :class:`int` object or a subclass.
``PyLong_FreeExport()`` must be called once done with using *array*.
PyLong_FreeDigitArray() PyLong_FreeExport()
^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
API:: API::
void PyLong_FreeDigitArray(PyLong_DigitArray *array) void PyLong_FreeExport(PyLongExport *array)
Release the export *array* created by ``PyLong_AsDigitArray()``. Free the export *array* created by ``PyLong_Export()``.
Import API Import API
@ -223,7 +223,7 @@ directly Python internals, the proposed API can have a significant
performance overhead on small integers. performance overhead on small integers.
For small integers of a few digits (for example, 1 or 2 digits), existing APIs For small integers of a few digits (for example, 1 or 2 digits), existing APIs
can be used. Examples to import / export: can be used
* :external+py3.14:c:func:`PyLong_FromUInt64()` / :external+py3.14:c:func:`PyLong_AsUInt64()`; * :external+py3.14:c:func:`PyLong_FromUInt64()` / :external+py3.14:c:func:`PyLong_AsUInt64()`;
* :c:func:`PyLong_FromLong()` / :c:func:`PyLong_AsLong()` or :c:func:`PyLong_AsInt()`; * :c:func:`PyLong_FromLong()` / :c:func:`PyLong_AsLong()` or :c:func:`PyLong_AsInt()`;
@ -248,33 +248,43 @@ Implementation
Benchmarks Benchmarks
========== ==========
Export: PyLong_AsDigitArray() with gmpy2 Export: PyLong_Export() with gmpy2
---------------------------------------- ----------------------------------
Code:: Code::
static void static void
mpz_set_PyLong(mpz_t z, PyObject *obj) mpz_set_PyLong(mpz_t z, PyObject *obj)
{ {
int overflow; const PyLongLayout* layout = PyLong_GetNativeLayout();
long val = PyLong_AsLongAndOverflow(obj, &overflow); static PyLongExport long_export;
if (overflow) { PyLong_Export(obj, &long_export);
const PyLongLayout* layout = PyLong_GetNativeLayout(); if (long_export.digits) {
static PyLong_DigitArray long_export; mpz_import(z, long_export.ndigits, layout->digits_order,
layout->digit_size, layout->endian,
PyLong_AsDigitArray(obj, &long_export);
mpz_import(z, long_export.ndigits, layout->endian,
layout->digit_size, layout->digits_order,
layout->digit_size*8 - layout->bits_per_digit, layout->digit_size*8 - layout->bits_per_digit,
long_export.digits); long_export.digits);
if (long_export.negative) { if (long_export.negative) {
mpz_neg(z, z); mpz_neg(z, z);
} }
PyLong_FreeDigitArray(&long_export); PyLong_FreeExport(&long_export);
} }
else { else {
mpz_set_si(z, val); if (LONG_MIN <= long_export.value && long_export.value <= LONG_MAX) {
mpz_set_si(z, long_export.value);
}
else {
mpz_import(z, 1, -1, sizeof(int64_t), 0, 0,
&long_export.value);
if (long_export.value < 0) {
mpz_t tmp;
mpz_init(tmp);
mpz_ui_pow_ui(tmp, 2, 8*sizeof(size_t));
mpz_sub(z, z, tmp);
mpz_clear(tmp);
}
}
} }
} }
@ -403,7 +413,7 @@ Python integers.
For example, it was proposed to add a *layout* parameter to For example, it was proposed to add a *layout* parameter to
``PyLongWriter_Create()`` and a *layout* member to the ``PyLongWriter_Create()`` and a *layout* member to the
``PyLong_DigitArray`` structure. ``PyLongExport`` structure.
The problem is that it's more complex to implement and not really The problem is that it's more complex to implement and not really
needed. What's strictly needed is only an API to import-export using the needed. What's strictly needed is only an API to import-export using the