【问题标题】:How can I get a pointer to the data inside a generic PyObject*?如何获得指向通用 PyObject* 中数据的指针?
【发布时间】:2021-11-12 19:13:45
【问题描述】:

我正在尝试获取 PyObject* 中保存的数据的内存地址(具体来自 Python.h 3.8.2),以便我可以对缓冲区执行 memcpy。我只能弄清楚如何将数据从对象中复制出来,但只是获取指针却一无所获。假设我有这个对象data ...

PyObject* data = PyLong_FromLong(100L);

目前看来,将这些数据转移到缓冲区的唯一选择是将其复制出来,然后使用临时变量的地址执行 m​​emcpy ...

long temp = PyLong_AsLong(data);
memcpy(buffer, &temp, 8);

这已经完成了成千上万次,所以我认为如果我能够获取数据的内存地址并将其直接复制到我的缓冲区中会更快......

memcpy(buffer, data->address_to_data(), 8)

而不是将额外的副本复制到临时变量中。

有谁知道我是否/如何从 PyObject* 包装器中获取长值的内存地址?

感谢您的帮助!

【问题讨论】:

  • 你觉得PyLong_AsVoidPtr怎么用?
  • 一些值得指出的事情:1) PyLong 可以存储任意大的数字(即比 C long 大得多),因此实际上没有可以访问的内部 long . 2)“通用python对象”可以包含指向其他Python对象的指针之类的东西,在复制它们时需要小心。 3)如果您正在寻找对数值的快速访问,那么也许您应该将array.array 与缓冲区协议一起使用
  • @DavidW 感谢您的详细评论。您能否进一步扩展“array.array with the buffer protocol”。我正在寻找将值放入缓冲区的最快方法。

标签: python c++ boost-python python-c-api


【解决方案1】:

这似乎是一个 XY 问题(即您认为您需要在 C 级别从一堆 Python 对象中提取数据,但实际上您会受益于拥有一个暴露所有的 Python 对象> 你的数据)。

Python int 可以存储(几乎)任意大的数字:

>>> 1000**1000  # creates a very big int

即它不会在内部存储为 C long。 Internally it is stored 是大小为 ob_size 的整数数组 (ob_digits),格式有点奇怪,对你来说用处不大。但是,如果您真的想复制它,您可以将对象指针指向PyLongObject*,然后执行memcpy(&dest, my_int->ob_digit, sizeof(digit)*abs(my_int->ob_size));。我建议不要这样做,因为您很难使用这些数据。

显然,这只适用于您知道自己拥有 Python int 的情况。对于“通用PyObject*”这是行不通的,因为通用PyObject* 可以包含几乎任何数据。这包括需要所有权和/或引用计数的指针(这尤其适用于包含其他 PyObjects 的任何 PyObject)。


我认为您真正想要的是将数据存储在一个大型 C 整数数组中。这可以通过array.arraynumpy.array 或其他各种类来完成。

在 C 级别,这些对象支持 the buffer protocol,它们将内部数组公开给 C,从而允许从 C 访问、复制、操作等您的每个值。

一些未经测试的快速说明性代码:

Py_Buffer view;
view.format = "l"; // request an array of longs
if (PyObject_GetBuffer(obj, &view, PyBUF_CONTIG | PyBUF_FORMAT | PyBUF_WRITABLE ) == -1) {
   // failed
   return NULL;
}

// you want to check that view.ndim == 1 (for a simple 1D array)
long* data = (long*)view.buf;
// At this point you can access data as a C array of length view.len

// When you've finished;
PyBuffer_Release(view);

【讨论】:

    【解决方案2】:

    有一个内部 CPython 函数可以执行您想要的操作,称为 _PyLong_AsByteArray

    它似乎从名为ob_digit 的字段中读取了所需的字节,但我没有完全了解整个函数。

    【讨论】:

      【解决方案3】:

      这似乎是与数据结构抽象有关的设计问题。通常,希望给用户一个不透明的数据结构或指针。访问内部元素需要方法(或函数)调用。

      来自https://docs.python.org/3/c-api/long.html

      PyObject* PyLong_FromLong(long v)
      
      Return value: New reference.
      Return a new PyLongObject object from v, or NULL on failure.
      
      The current implementation keeps an array of integer objects for all integers between -5 and 256, when you create an int in that range you actually just get back a reference to the existing object.
      

      该调用可能会进行到 PyLongObject 的内部转换,该转换可以是指向其内部对象的链接。如果你在 -5 到 256 之间传递,它将用它的内部对象替换你的整数。对于其他人,将创建一个新对象。即使您找到内部存储器位置,也不能保证行为会保持一致。

      PyObject 似乎被设计成不透明的。照此处理。

      【讨论】:

      • 感谢您的详细回复,将PyObject* 长时间放入缓冲区的最佳方法是什么?我在原始帖子中遵循的步骤是我能得到的最好的吗?
      • 您使用的方法有什么问题? = As of now it seems my only option to get this data over to the buffer is to copy it out and then do a memcpy using the address of the temporary variable long temp = PyLong_AsLong(data); memcpy(缓冲区, &temp, 8);
      • 如果我没有从长值 PyLong_AsLong 构造到我的临时变量的额外副本,它会更快。理想情况下,会有一些 Py 函数将 long 值直接构造到缓冲区上。我知道很可能没有那个选项。我只是想确保我在原始帖子中的内容是我的最佳性能选择。
      • 这不是最好的选择——但不透明对象的性质要求缺乏直接的内存访问。它与对象的工作方式有关。可悲的是,您将不得不忍受这种性能打击。
      猜你喜欢
      • 2014-09-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-20
      • 1970-01-01
      • 2022-11-23
      • 1970-01-01
      相关资源
      最近更新 更多