【问题标题】:Copying bytes in Python from Numpy array into string or bytearray将 Python 中的字节从 Numpy 数组复制到字符串或字节数组中
【发布时间】:2015-02-05 10:40:17
【问题描述】:

我在 while 循环中从 UDP 套接字读取数据。我需要最有效的方法

1) 读取数据 (*)(这有点解决了,但赞赏 cmets)

2) 定期将(操纵的)数据转储到文件中 (**)(问题)

我预计 numpy 的“tostring”方法会出现瓶颈。让我们考虑以下一段(不完整的)代码:

import socket
import numpy

nbuf=4096
buf=numpy.zeros(nbuf,dtype=numpy.uint8) # i.e., an array of bytes
f=open('dump.data','w')

datasocket=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# ETC.. (code missing here) .. the datasocket is, of course, non-blocking

while True:
  gotsome=True
  try:
    N=datasocket.recv_into(buf) # no memory-allocation here .. (*)
  except(socket.error):
    # do nothing ..
    gotsome=False

  if (gotsome):
    # the bytes in "buf" will be manipulated in various ways ..
    # the following write is done frequently (not necessarily in each pass of the while loop):
    f.write(buf[:N].tostring())  # (**) The question: what is the most efficient way to do this?

f.close() 

现在,在 (**),据我所知:

1) buf[:N] 为一个长度为 N+1 的新数组对象分配内存,对吧? (也许不是)

.. 然后:

2) buf[:N].tostring() 为一个新的字符串分配内存,来自buf的字节被复制到这个字符串中

这似乎有很多内存分配和交换。在同一个循环中,以后我会读取几个socket,写入几个文件。

有没有办法只告诉 f.write 直接访问“buf”的内存地址从 0 到 N 字节并将它们写入磁盘?

即,本着缓冲区接口的精神这样做并避免这两个额外的内存分配?

P。 S. f.write(buf[:N].tostring()) 等价于 buf[:N].tofile(f)

【问题讨论】:

  • 你分析过你的代码吗?我可以想象,在写入文件时,一些额外的分配不会造成太大的伤害。

标签: python arrays numpy buffer


【解决方案1】:

基本上,听起来你想使用数组的tofile 方法或者直接使用ndarray.data 缓冲区对象。

对于您的确切用例,使用数组的data 缓冲区是最有效的,但是对于一般用途,您需要注意很多注意事项。我稍后会详细说明。


但是,首先让我回答您的几个问题并提供一些说明:

buf[:N] 为一个长度为 N+1 的新数组对象分配内存,对吧?

这取决于您所说的“新数组对象”是什么意思。无论涉及的数组大小如何,分配的额外内存都很少。

它确实为一个新的数组对象分配内存(几个字节),但它没有为数组的数据分配额外的内存。相反,它会创建一个共享原始数组数据缓冲区的“视图”。您对y = buf[:N] 所做的任何更改也会影响buf。

buf[:N].tostring() 为一个新的字符串分配内存,buf中的字节被复制到这个字符串中

是的,没错。

顺便说一句,您实际上可以采用相反的方式(字符串到数组)而不分配任何额外的内存:

somestring = 'This could be a big string'
arr = np.frombuffer(buffer(somestring), dtype=np.uint8)

但是,由于 python 字符串是不可变的,arr 将是只读的。


有没有办法告诉 f.write 直接访问“buf”的内存地址从 0 到 N 字节并将它们写入磁盘?

是的!

基本上,你会想要:

f.write(buf[:N].data)

这非常有效,适用于任何类似文件的对象。在这种情况下,这几乎肯定是您想要的。但是,有几个警告!

首先,请注意N 将在数组中的项目中,而不是直接以字节为单位。它们在您的示例代码中是等效的(由于 dtype=np.int8 或任何其他 8 位数据类型)。

如果你确实想写一些字节,你可以这样做

f.write(buf.data[:N])

...但是对arr.data 缓冲区进行切片会分配一个新字符串,因此它在功能上类似于buf[:N].tostring()。无论如何,请注意,对于大多数 dtype,执行 f.write(buf[:N].tostring()) 与执行 f.write(buf.data[:N]) 不同,但两者都会分配一个新字符串。

接下来,numpy 数组可以共享数据缓冲区。在您的示例情况下,您无需担心这一点,但一般来说,使用somearr.data 可能会因此而导致意外。

举个例子:

x = np.arange(10, dtype=np.uint8)
y = x[::2]

现在,y 与x 共享相同的内存缓冲区,但它在内存中并不连续(查看x.flags 与y.flags)。相反,它引用x 的内存缓冲区中的所有其他 项(比较x.strides 和y.strides)。

如果我们尝试访问y.data,我们会收到一条错误消息,告诉我们这不是内存中的连续数组,我们无法为其获取单段缓冲区:

In [5]: y.data
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-54-364eeabf8187> in <module>()
----> 1 y.data

AttributeError: cannot get single-segment buffer for discontiguous array

这是 numpy 数组具有 tofile 方法的很大一部分原因(它也早于 python 的 buffers,但这是另一回事)。

tofile 会将数组中的数据写入文件而不分配额外的内存。但是,因为它是在 C 级实现的,所以它只适用于真正的 file 对象,而不适用于类文件对象(例如套接字、StringIO 等)。

例如:

buf[:N].tofile(f)

但是,这是在 C 级别实现的,仅适用于实际的文件对象,不适用于套接字、StringIO 和其他类似文件的对象。

不过,这确实允许您使用任意数组索引。

buf[someslice].tofile(f)

将创建一个新视图(相同的内存缓冲区),并有效地将其写入磁盘。在您的确切情况下,它会比切片 arr.data 缓冲区并直接将其写入磁盘稍慢。 如果您更喜欢使用数组索引(而不是字节数),那么ndarray.tofile 方法将比f.write(arr.tostring()) 更有效。

【讨论】:

  • 感谢您的详尽回答。就是这样!
猜你喜欢
  • 2015-03-19
  • 1970-01-01
  • 2018-10-05
  • 1970-01-01
  • 1970-01-01
  • 2011-10-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多