2024-09-14 05:33:01 -04:00
|
|
|
|
PEP: 757
|
|
|
|
|
Title: C API to import-export Python integers
|
|
|
|
|
Author: Sergey B Kirpichev <skirpichev@gmail.com>,
|
|
|
|
|
Victor Stinner <vstinner@python.org>
|
|
|
|
|
PEP-Delegate: C API Working Group
|
2024-09-14 08:26:39 -04:00
|
|
|
|
Discussions-To: https://discuss.python.org/t/63895
|
2024-09-14 05:33:01 -04:00
|
|
|
|
Status: Draft
|
|
|
|
|
Type: Standards Track
|
|
|
|
|
Created: 13-Sep-2024
|
|
|
|
|
Python-Version: 3.14
|
2024-09-14 08:26:39 -04:00
|
|
|
|
Post-History: `14-Sep-2024 <https://discuss.python.org/t/63895>`__
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
|
|
|
|
.. highlight:: c
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Abstract
|
|
|
|
|
========
|
|
|
|
|
|
2024-09-14 08:26:39 -04:00
|
|
|
|
Add a new C API to import and export Python integers, :class:`int` objects:
|
2024-09-19 07:02:45 -04:00
|
|
|
|
especially :c:func:`PyLongWriter_Create()` and :c:func:`PyLong_Export()` functions.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rationale
|
|
|
|
|
=========
|
|
|
|
|
|
|
|
|
|
Projects such as `gmpy2 <https://github.com/aleaxit/gmpy>`_, `SAGE
|
|
|
|
|
<https://www.sagemath.org/>`_ and `Python-FLINT
|
|
|
|
|
<https://github.com/flintlib/python-flint>`_ access directly Python
|
2024-09-19 07:02:45 -04:00
|
|
|
|
"internals" (the :c:type:`PyLongObject` structure) or use an inefficient
|
2024-09-14 05:33:01 -04:00
|
|
|
|
temporary format (hex strings for Python-FLINT) to import and
|
2024-09-14 08:26:39 -04:00
|
|
|
|
export Python :class:`int` objects. The Python :class:`int` implementation
|
2024-09-14 05:33:01 -04:00
|
|
|
|
changed in Python 3.12 to add a tag and "compact values".
|
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
In the 3.13 alpha 1 release, the private undocumented :c:func:`!_PyLong_New()`
|
2024-09-14 05:33:01 -04:00
|
|
|
|
function had been removed, but it is being used by these projects to
|
|
|
|
|
import Python integers. The private function has been restored in 3.13
|
|
|
|
|
alpha 2.
|
|
|
|
|
|
|
|
|
|
A public efficient abstraction is needed to interface Python with these
|
|
|
|
|
projects without exposing implementation details. It would allow Python
|
|
|
|
|
to change its internals without breaking these projects. For example,
|
|
|
|
|
implementation for gmpy2 was changed recently for CPython 3.9 and
|
|
|
|
|
for CPython 3.12.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Specification
|
|
|
|
|
=============
|
|
|
|
|
|
|
|
|
|
Layout API
|
|
|
|
|
----------
|
|
|
|
|
|
2024-10-15 05:43:41 -04:00
|
|
|
|
Data needed by `GMP <https://gmplib.org/>`_-like `import
|
|
|
|
|
<https://gmplib.org/manual/Integer-Import-and-Export#index-mpz_005fimport>`_-`export
|
|
|
|
|
<https://gmplib.org/manual/Integer-Import-and-Export#index-mpz_005fexport>`_
|
|
|
|
|
functions.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
.. c:struct:: PyLongLayout
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-10-15 05:43:41 -04:00
|
|
|
|
Layout of an array of "digits" ("limbs" in the GMP terminology), used to
|
|
|
|
|
represent absolute value for arbitrary precision integers.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
Use :c:func:`PyLong_GetNativeLayout` to get the native layout of Python
|
2024-10-15 05:43:41 -04:00
|
|
|
|
:class:`int` objects, used internally for integers with "big enough"
|
|
|
|
|
absolute value.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
See also :data:`sys.int_info` which exposes similar information to Python.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
.. c:member:: uint8_t bits_per_digit
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
Bits per digit.
|
|
|
|
|
|
|
|
|
|
.. c:member:: uint8_t digit_size
|
|
|
|
|
|
|
|
|
|
Digit size in bytes.
|
|
|
|
|
|
|
|
|
|
.. c:member:: int8_t digits_order
|
|
|
|
|
|
|
|
|
|
Digits order:
|
|
|
|
|
|
|
|
|
|
- ``1`` for most significant digit first
|
|
|
|
|
- ``-1`` for least significant digit first
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 17:28:22 -04:00
|
|
|
|
.. c:member:: int8_t endianness
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
Digit endianness:
|
2024-09-14 08:26:39 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
- ``1`` for most significant byte first (big endian)
|
|
|
|
|
- ``-1`` for least significant first (little endian)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.. c:function:: const PyLongLayout* PyLong_GetNativeLayout(void)
|
|
|
|
|
|
|
|
|
|
Get the native layout of Python :class:`int` objects.
|
|
|
|
|
|
|
|
|
|
See the :c:struct:`PyLongLayout` structure.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 17:28:22 -04:00
|
|
|
|
The function must not be called before Python initialization nor after
|
|
|
|
|
Python finalization. The returned layout is valid until Python is
|
|
|
|
|
finalized. The layout is the same for all Python sub-interpreters and
|
|
|
|
|
so it can be cached.
|
2024-09-18 07:59:58 -04:00
|
|
|
|
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
|
|
|
|
Export API
|
|
|
|
|
----------
|
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
.. c:struct:: PyLongExport
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
Export of a Python :class:`int` object.
|
2024-09-17 11:38:13 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
There are two cases:
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
* If :c:member:`digits` is ``NULL``, only use the :c:member:`value` member.
|
|
|
|
|
Calling :c:func:`PyLong_FreeExport` is optional in this case.
|
|
|
|
|
* If :c:member:`digits` is not ``NULL``, use :c:member:`negative`,
|
|
|
|
|
:c:member:`ndigits` and :c:member:`digits` members.
|
|
|
|
|
Calling :c:func:`PyLong_FreeExport` is mandatory in this case.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
.. c:member:: int64_t value
|
2024-09-16 07:20:45 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
The native integer value of the exported :class:`int` object.
|
|
|
|
|
Only valid if :c:member:`digits` is ``NULL``.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
.. c:member:: uint8_t negative
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
1 if the number is negative, 0 otherwise.
|
|
|
|
|
Only valid if :c:member:`digits` is not ``NULL``.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
.. c:member:: Py_ssize_t ndigits
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
Number of digits in :c:member:`digits` array.
|
|
|
|
|
Only valid if :c:member:`digits` is not ``NULL``.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
.. c:member:: const void *digits
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
Read-only array of unsigned digits. Can be ``NULL``.
|
2024-09-14 08:26:39 -04:00
|
|
|
|
|
2024-09-20 09:36:32 -04:00
|
|
|
|
If :c:member:`digits` not ``NULL``, a private field of the
|
|
|
|
|
:c:struct:`PyLongExport` structure stores a strong reference to the Python
|
|
|
|
|
:class:`int` object to make sure that that structure remains valid until
|
|
|
|
|
:c:func:`PyLong_FreeExport()` is called.
|
|
|
|
|
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
.. c:function:: int PyLong_Export(PyObject *obj, PyLongExport *export_long)
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
Export a Python :class:`int` object.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
On success, set *\*export_long* and return 0.
|
|
|
|
|
On error, set an exception and return -1.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 17:28:22 -04:00
|
|
|
|
If *export_long->digits* is not ``NULL``, :c:func:`PyLong_FreeExport` must be
|
2024-09-19 07:02:45 -04:00
|
|
|
|
called when the export is no longer needed.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-10-15 05:43:41 -04:00
|
|
|
|
|
|
|
|
|
On CPython 3.14, no memory copy is needed in :c:func:`PyLong_Export`, it's just
|
|
|
|
|
a thin wrapper to expose Python :class:`int` internal digits array.
|
2024-09-17 11:38:13 -04:00
|
|
|
|
|
2024-09-14 08:26:39 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
.. c:function:: void PyLong_FreeExport(PyLongExport *export_long)
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
Release the export *export_long* created by :c:func:`PyLong_Export`.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Import API
|
|
|
|
|
----------
|
|
|
|
|
|
2024-09-19 17:28:22 -04:00
|
|
|
|
The :c:type:`PyLongWriter` API can be used to import an integer:
|
|
|
|
|
create a Python :class:`int` object from a digits array.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
.. c:struct:: PyLongWriter
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
A Python :class:`int` writer instance.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-10-15 05:43:41 -04:00
|
|
|
|
The instance must be destroyed by :c:func:`PyLongWriter_Finish` or
|
|
|
|
|
:c:func:`PyLongWriter_Discard`.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-14 08:26:39 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
.. c:function:: PyLongWriter* PyLongWriter_Create(int negative, Py_ssize_t ndigits, void **digits)
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
Create a :c:type:`PyLongWriter`.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
On success, set *\*digits* and return a writer.
|
|
|
|
|
On error, set an exception and return ``NULL``.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
*negative* is ``1`` if the number is negative, or ``0`` otherwise.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
*ndigits* is the number of digits in the *digits* array. It must be
|
|
|
|
|
greater than or equal to 0.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-10-15 05:43:41 -04:00
|
|
|
|
The caller can either initialize the array of digits *digits* and then call
|
|
|
|
|
:c:func:`PyLongWriter_Finish` to get a Python :class:`int`, or call
|
|
|
|
|
:c:func:`PyLongWriter_Discard` to destroy the writer instance. Digits must
|
|
|
|
|
be in the range [``0``; ``(1 << sys.int_info.bits_per_digit) - 1``]. Unused
|
|
|
|
|
digits must be set to ``0``.
|
|
|
|
|
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-10-15 05:43:41 -04:00
|
|
|
|
On CPython 3.14, the :c:func:`PyLongWriter_Create` implementation is a thin
|
|
|
|
|
wrapper to the private :c:func:`!_PyLong_New()` function.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
.. c:function:: PyObject* PyLongWriter_Finish(PyLongWriter *writer)
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
Finish a :c:type:`PyLongWriter` created by :c:func:`PyLongWriter_Create`.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
On success, return a Python :class:`int` object.
|
|
|
|
|
On error, set an exception and return ``NULL``.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-19 17:28:22 -04:00
|
|
|
|
The function takes care of normalizing the digits and converts the
|
|
|
|
|
object to a compact integer if needed.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-14 08:26:39 -04:00
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
.. c:function:: void PyLongWriter_Discard(PyLongWriter *writer)
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-10-15 05:43:41 -04:00
|
|
|
|
Discard a :c:type:`PyLongWriter` created by :c:func:`PyLongWriter_Create`.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
|
|
|
|
|
2024-09-18 07:59:58 -04:00
|
|
|
|
Optimize import for small integers
|
|
|
|
|
==================================
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-18 07:59:58 -04:00
|
|
|
|
Proposed import API is efficient for large integers. Compared to
|
|
|
|
|
accessing directly Python internals, the proposed import API can have a
|
|
|
|
|
significant performance overhead on small integers.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
|
|
|
|
For small integers of a few digits (for example, 1 or 2 digits), existing APIs
|
2024-09-18 07:59:58 -04:00
|
|
|
|
can be used:
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-18 07:59:58 -04:00
|
|
|
|
* :external+py3.14:c:func:`PyLong_FromUInt64()`;
|
|
|
|
|
* :c:func:`PyLong_FromLong()`;
|
|
|
|
|
* :c:func:`PyLong_FromNativeBytes()`.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Implementation
|
|
|
|
|
==============
|
|
|
|
|
|
|
|
|
|
* CPython:
|
|
|
|
|
|
|
|
|
|
* https://github.com/python/cpython/pull/121339
|
|
|
|
|
* https://github.com/vstinner/cpython/pull/5
|
|
|
|
|
|
|
|
|
|
* gmpy:
|
|
|
|
|
|
|
|
|
|
* https://github.com/aleaxit/gmpy/pull/495
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Benchmarks
|
|
|
|
|
==========
|
|
|
|
|
|
2024-09-20 09:36:32 -04:00
|
|
|
|
Code::
|
|
|
|
|
|
|
|
|
|
/* Query parameters of Python’s internal representation of integers. */
|
|
|
|
|
const PyLongLayout *layout = PyLong_GetNativeLayout();
|
|
|
|
|
|
|
|
|
|
size_t int_digit_size = layout->digit_size;
|
|
|
|
|
int int_digits_order = layout->digits_order;
|
|
|
|
|
size_t int_bits_per_digit = layout->bits_per_digit;
|
|
|
|
|
size_t int_nails = int_digit_size*8 - int_bits_per_digit;
|
|
|
|
|
int int_endianness = layout->endianness;
|
|
|
|
|
|
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
Export: :c:func:`PyLong_Export()` with gmpy2
|
|
|
|
|
--------------------------------------------
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
|
|
|
|
Code::
|
|
|
|
|
|
2024-10-15 08:14:30 -04:00
|
|
|
|
static int
|
2024-09-14 05:33:01 -04:00
|
|
|
|
mpz_set_PyLong(mpz_t z, PyObject *obj)
|
|
|
|
|
{
|
2024-09-17 11:38:13 -04:00
|
|
|
|
static PyLongExport long_export;
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-10-15 08:14:30 -04:00
|
|
|
|
if (PyLong_Export(obj, &long_export) < 0) {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-17 11:38:13 -04:00
|
|
|
|
if (long_export.digits) {
|
2024-09-20 09:36:32 -04:00
|
|
|
|
mpz_import(z, long_export.ndigits, int_digits_order, int_digit_size,
|
|
|
|
|
int_endianness, int_nails, long_export.digits);
|
2024-09-14 05:33:01 -04:00
|
|
|
|
if (long_export.negative) {
|
|
|
|
|
mpz_neg(z, z);
|
|
|
|
|
}
|
2024-09-17 11:38:13 -04:00
|
|
|
|
PyLong_FreeExport(&long_export);
|
2024-09-14 05:33:01 -04:00
|
|
|
|
}
|
|
|
|
|
else {
|
2024-09-20 09:36:32 -04:00
|
|
|
|
const int64_t value = long_export.value;
|
|
|
|
|
|
|
|
|
|
if (LONG_MIN <= value && value <= LONG_MAX) {
|
|
|
|
|
mpz_set_si(z, value);
|
2024-09-17 11:38:13 -04:00
|
|
|
|
}
|
|
|
|
|
else {
|
2024-09-20 09:36:32 -04:00
|
|
|
|
mpz_import(z, 1, -1, sizeof(int64_t), 0, 0, &value);
|
|
|
|
|
if (value < 0) {
|
2024-09-17 11:38:13 -04:00
|
|
|
|
mpz_t tmp;
|
|
|
|
|
mpz_init(tmp);
|
2024-09-19 07:02:45 -04:00
|
|
|
|
mpz_ui_pow_ui(tmp, 2, 64);
|
2024-09-17 11:38:13 -04:00
|
|
|
|
mpz_sub(z, z, tmp);
|
|
|
|
|
mpz_clear(tmp);
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-09-14 05:33:01 -04:00
|
|
|
|
}
|
2024-10-15 08:14:30 -04:00
|
|
|
|
return 0;
|
2024-09-14 05:33:01 -04:00
|
|
|
|
}
|
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
Reference code: `mpz_set_PyLong() in the gmpy2 master for commit 9177648
|
|
|
|
|
<https://github.com/aleaxit/gmpy/blob/9177648c23f5c507e46b81c1eb7d527c79c96f00/src/gmpy2_convert_gmp.c#L42-L69>`_.
|
|
|
|
|
|
2024-09-14 05:33:01 -04:00
|
|
|
|
Benchmark:
|
|
|
|
|
|
|
|
|
|
.. code-block:: py
|
|
|
|
|
|
|
|
|
|
import pyperf
|
|
|
|
|
from gmpy2 import mpz
|
|
|
|
|
|
|
|
|
|
runner = pyperf.Runner()
|
|
|
|
|
runner.bench_func('1<<7', mpz, 1 << 7)
|
|
|
|
|
runner.bench_func('1<<38', mpz, 1 << 38)
|
|
|
|
|
runner.bench_func('1<<300', mpz, 1 << 300)
|
|
|
|
|
runner.bench_func('1<<3000', mpz, 1 << 3000)
|
|
|
|
|
|
|
|
|
|
Results on Linux Fedora 40 with CPU isolation, Python built in release
|
|
|
|
|
mode:
|
|
|
|
|
|
|
|
|
|
+----------------+---------+-----------------------+
|
|
|
|
|
| Benchmark | ref | pep757 |
|
|
|
|
|
+================+=========+=======================+
|
2024-09-18 07:59:58 -04:00
|
|
|
|
| 1<<7 | 91.3 ns | 89.9 ns: 1.02x faster |
|
2024-09-14 05:33:01 -04:00
|
|
|
|
+----------------+---------+-----------------------+
|
2024-09-18 07:59:58 -04:00
|
|
|
|
| 1<<38 | 120 ns | 94.9 ns: 1.27x faster |
|
2024-09-14 05:33:01 -04:00
|
|
|
|
+----------------+---------+-----------------------+
|
2024-09-18 07:59:58 -04:00
|
|
|
|
| 1<<300 | 196 ns | 203 ns: 1.04x slower |
|
2024-09-14 05:33:01 -04:00
|
|
|
|
+----------------+---------+-----------------------+
|
2024-09-18 07:59:58 -04:00
|
|
|
|
| 1<<3000 | 939 ns | 945 ns: 1.01x slower |
|
2024-09-14 05:33:01 -04:00
|
|
|
|
+----------------+---------+-----------------------+
|
2024-09-18 07:59:58 -04:00
|
|
|
|
| Geometric mean | (ref) | 1.05x faster |
|
2024-09-14 05:33:01 -04:00
|
|
|
|
+----------------+---------+-----------------------+
|
|
|
|
|
|
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
Import: :c:func:`PyLongWriter_Create()` with gmpy2
|
|
|
|
|
--------------------------------------------------
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
|
|
|
|
Code::
|
|
|
|
|
|
|
|
|
|
static PyObject *
|
|
|
|
|
GMPy_PyLong_From_MPZ(MPZ_Object *obj, CTXT_Object *context)
|
|
|
|
|
{
|
|
|
|
|
if (mpz_fits_slong_p(obj->z)) {
|
|
|
|
|
return PyLong_FromLong(mpz_get_si(obj->z));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t size = (mpz_sizeinbase(obj->z, 2) +
|
2024-09-20 09:36:32 -04:00
|
|
|
|
int_bits_per_digit - 1) / int_bits_per_digit;
|
2024-09-14 05:33:01 -04:00
|
|
|
|
void *digits;
|
|
|
|
|
PyLongWriter *writer = PyLongWriter_Create(mpz_sgn(obj->z) < 0, size,
|
|
|
|
|
&digits);
|
|
|
|
|
if (writer == NULL) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-20 09:36:32 -04:00
|
|
|
|
mpz_export(digits, NULL, int_digits_order, int_digit_size,
|
|
|
|
|
int_endianness, int_nails, obj->z);
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
|
|
|
|
return PyLongWriter_Finish(writer);
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-19 07:02:45 -04:00
|
|
|
|
Reference code: `GMPy_PyLong_From_MPZ() in the gmpy2 master for commit 9177648
|
|
|
|
|
<https://github.com/aleaxit/gmpy/blob/9177648c23f5c507e46b81c1eb7d527c79c96f00/src/gmpy2_convert_gmp.c#L128-L156>`_.
|
|
|
|
|
|
2024-09-14 05:33:01 -04:00
|
|
|
|
Benchmark:
|
|
|
|
|
|
|
|
|
|
.. code-block:: py
|
|
|
|
|
|
|
|
|
|
import pyperf
|
|
|
|
|
from gmpy2 import mpz
|
|
|
|
|
|
|
|
|
|
runner = pyperf.Runner()
|
|
|
|
|
runner.bench_func('1<<7', int, mpz(1 << 7))
|
|
|
|
|
runner.bench_func('1<<38', int, mpz(1 << 38))
|
|
|
|
|
runner.bench_func('1<<300', int, mpz(1 << 300))
|
|
|
|
|
runner.bench_func('1<<3000', int, mpz(1 << 3000))
|
|
|
|
|
|
|
|
|
|
Results on Linux Fedora 40 with CPU isolation, Python built in release
|
|
|
|
|
mode:
|
|
|
|
|
|
2024-09-18 07:59:58 -04:00
|
|
|
|
+----------------+---------+-----------------------+
|
|
|
|
|
| Benchmark | ref | pep757 |
|
|
|
|
|
+================+=========+=======================+
|
|
|
|
|
| 1<<7 | 56.7 ns | 56.2 ns: 1.01x faster |
|
|
|
|
|
+----------------+---------+-----------------------+
|
|
|
|
|
| 1<<300 | 191 ns | 213 ns: 1.12x slower |
|
|
|
|
|
+----------------+---------+-----------------------+
|
|
|
|
|
| Geometric mean | (ref) | 1.03x slower |
|
|
|
|
|
+----------------+---------+-----------------------+
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
2024-09-18 07:59:58 -04:00
|
|
|
|
Benchmark hidden because not significant (2): 1<<38, 1<<3000.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Backwards Compatibility
|
|
|
|
|
=======================
|
|
|
|
|
|
|
|
|
|
There is no impact on the backward compatibility, only new APIs are
|
|
|
|
|
added.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rejected Ideas
|
|
|
|
|
==============
|
|
|
|
|
|
|
|
|
|
Support arbitrary layout
|
|
|
|
|
------------------------
|
|
|
|
|
|
|
|
|
|
It would be convenient to support arbitrary layout to import-export
|
|
|
|
|
Python integers.
|
|
|
|
|
|
|
|
|
|
For example, it was proposed to add a *layout* parameter to
|
2024-09-19 07:02:45 -04:00
|
|
|
|
:c:func:`PyLongWriter_Create()` and a *layout* member to the
|
|
|
|
|
:c:struct:`PyLongExport` structure.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
Python "native" layout.
|
|
|
|
|
|
|
|
|
|
If later there are use cases for arbitrary layouts, new APIs can be
|
|
|
|
|
added.
|
|
|
|
|
|
|
|
|
|
|
2024-10-04 08:51:19 -04:00
|
|
|
|
Don't add :c:func:`PyLong_GetNativeLayout` function
|
|
|
|
|
---------------------------------------------------
|
|
|
|
|
|
|
|
|
|
Currently, most required information for :class:`int` import/export is already
|
|
|
|
|
available via :c:func:`PyLong_GetInfo()` (and :data:`sys.int_info`). We also
|
|
|
|
|
can add more (like order of digits), this interface doesn't poses any
|
|
|
|
|
constraints on future evolution of the :c:type:`PyLongObject`.
|
|
|
|
|
|
|
|
|
|
The problem is that the :c:func:`PyLong_GetInfo()` returns a Python object,
|
|
|
|
|
:term:`named tuple`, not a convenient C structure and that might distract
|
|
|
|
|
people from using it in favor e.g. of current semi-private macros like
|
|
|
|
|
:c:macro:`!PyLong_SHIFT` and :c:macro:`!PyLong_BASE`.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Provide mpz_import/export-like API instead
|
|
|
|
|
------------------------------------------
|
|
|
|
|
|
|
|
|
|
The other approach to import/export data from :class:`int` objects might be
|
|
|
|
|
following: expect, that C extensions provide contiguous buffers that CPython
|
|
|
|
|
then exports (or imports) the *absolute* value of an integer.
|
|
|
|
|
|
|
|
|
|
API example::
|
|
|
|
|
|
|
|
|
|
struct PyLongLayout {
|
|
|
|
|
uint8_t bits_per_digit;
|
|
|
|
|
uint8_t digit_size;
|
|
|
|
|
int8_t digits_order;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
size_t PyLong_GetDigitsNeeded(PyLongObject *obj, PyLongLayout layout);
|
|
|
|
|
int PyLong_Export(PyLongObject *obj, PyLongLayout layout, void *buffer);
|
|
|
|
|
PyLongObject *PyLong_Import(PyLongLayout layout, void *buffer);
|
|
|
|
|
|
|
|
|
|
This might work for the GMP, as this it has :c:func:`!mpz_limbs_read()` and
|
|
|
|
|
:c:func:`!mpz_limbs_write()` functions, that can provide required "buffers".
|
|
|
|
|
|
|
|
|
|
The major drawback of this approach is that it's much more complex on the
|
|
|
|
|
CPython side (i.e. actual conversion between different layouts).
|
|
|
|
|
|
|
|
|
|
|
2024-09-14 05:33:01 -04:00
|
|
|
|
Discussions
|
|
|
|
|
===========
|
|
|
|
|
|
2024-09-19 17:28:22 -04:00
|
|
|
|
* Discourse: `PEP 757 – C API to import-export Python integers
|
|
|
|
|
<https://discuss.python.org/t/63895>`_
|
|
|
|
|
* `C API Working Group decision issue #35
|
|
|
|
|
<https://github.com/capi-workgroup/decisions/issues/35>`_
|
|
|
|
|
* `Pull request #121339
|
|
|
|
|
<https://github.com/python/cpython/pull/121339>`_
|
|
|
|
|
* `Issue #102471
|
|
|
|
|
<https://github.com/python/cpython/issues/102471>`_:
|
|
|
|
|
The C-API for Python to C integer conversion is, to be frank, a mess.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
* `Add public function PyLong_GetDigits()
|
|
|
|
|
<https://github.com/capi-workgroup/decisions/issues/31>`_
|
|
|
|
|
* `Consider restoring _PyLong_New() function as public
|
|
|
|
|
<https://github.com/python/cpython/issues/111415>`_
|
2024-09-19 17:28:22 -04:00
|
|
|
|
* `Pull request gh-106320
|
|
|
|
|
<https://github.com/python/cpython/pull/108604>`_:
|
|
|
|
|
Remove private _PyLong_New() function.
|
2024-09-14 05:33:01 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Copyright
|
|
|
|
|
=========
|
|
|
|
|
|
|
|
|
|
This document is placed in the public domain or under the
|
|
|
|
|
CC0-1.0-Universal license, whichever is more permissive.
|