【问题标题】:Python/ctypes file handle difference between Mac OS X and UbuntuMac OS X 和 Ubuntu 之间的 Python/ctypes 文件句柄差异
【发布时间】:2012-11-12 23:41:17
【问题描述】:

我目前正在尝试在我的 linux 机器 (Ubuntu 12.04.1 LTS) 和我的新 Mac (OS X 10.7.4) 之间移植一些代码,并且在使用 python 的 ctypes 模块访问 C 时遇到了一些令人困惑的行为Mac 上的标准库。

为了说明问题,下面是一个最小的例子:

import ctypes as C
import numpy as np

libc = C.CDLL("/usr/lib/libc.dylib")   #/usr/lib/libc.so.6 on ubuntu

np.arange(10,dtype="ubyte").tofile("test.bin") # create some test data

buffer_array = np.empty(10,dtype="ubyte") # create a reading buffer

buffer_array_c = np.ctypeslib.as_ctypes(buffer_array) # get the ctypes version of the buffer 

c_file = libc.fopen("test.bin","r") # open the file through libc   

libc.fread(buffer_array_c, 1, 10, c_file) # read from the file

libc.fclose(c_file)

print "Desired output:"
print np.fromfile("test.bin",dtype="ubyte")
print
print "Actual output:"
print buffer_array

在 Linux 上,这按预期工作,产生以下结果:

Desired output:
[0 1 2 3 4 5 6 7 8 9]

Actual output:
[0 1 2 3 4 5 6 7 8 9]

然而,在 Mac 上,我只是得到 `Segmentation fault: 11'。

我已经对此进行了一些实验,将 fopen 调用替换为:

py_file = open("test.bin","r")

c_file = C.pythonapi.PyFile_AsFile(C.py_object(py_file))

这也适用于 Linux,但不适用于 Mac。

我认为问题来自使用 c_file 调用 fread,就好像我编写了一个最小的 C 函数来打开文件然后使用先前分配的缓冲区调用 fread,代码按预期执行。

我通常不是 Mac 用户,所以问题可能很明显,但任何帮助都会非常有用。

作为参考,我正在使用:

Python 2.7.3、Numpy 1.4.0 和 Ctypes 1.1.0

编辑:

为了提供一些背景信息,我正在尝试使用快速方法将非常大的二进制文件(约 40-200 GB)逐个读取到 python 中。正如评论者在下面指出的那样,直接访问标准库 fread 和 fwrite 函数并没有真正提高性能。这是真的,但我不知道为什么。如果我要使用 numpy.fromfile 以块的形式读取一个大文件,我不会在每次读取时创建一个新的内存分配吗?

解决方案:

问题似乎源于文件句柄存储的 64 位/32 位差异。 解决方法就是在使用前明确设置每个 c 函数的 restype 和 argtypes。

即在 64 位机器上,我们将其放在 C.CDLL 调用之后:

lib.fopen.restype = C.c_long
lib.fread.argtypes = [C.c_void_p, C.c_size_t, C.c_size_t, C.c_long]
lib.fclose.argtypes = [C.c_long]

在 32 位机器上:

lib.fopen.restype = C.c_int
lib.fread.argtypes = [C.c_void_p, C.c_size_t, C.c_size_t, C.c_int]
lib.fclose.argtypes = [C.c_int]

【问题讨论】:

  • 我会拼命说“你做错了”。除非我遗漏了一些至关重要的东西,否则您不太可能在手动编写 IO 时看到任何显着的性能改进。您能否为您的任务提供更多背景信息?
  • 您在尝试解决方案之前是否进行了分析?我怀疑多重分配的开销是如此之大,以至于您必须使用 libc 调用。
  • @Bakuriu:正在读取/写入的大部分数据都是 1 位格式。所以我需要在解包信息之前将数据作为字节读取(在这种情况下是很多时间序列)。在单个缓冲区内完成这一切会带来一定的便利。我发布的并不是一个“解决方案”本身,它更像是一个 ctypes 学习实验。
  • 您可以使用py_file = open("test.bin","rb"); py_file.readinto(buffer_array)。它返回读入缓冲区的字节数。
  • @eryksun:我喜欢 readinto 函数,但由于文档字符串读取 ``readinto() -> 未记录,我对使用它持谨慎态度。不要使用这个;它可能会消失。''。我不知道在 Python 3 中是否仍然如此,但现在 np.fromfile 的运行速度大约是 readinto 的 90%。

标签: python ctypes


【解决方案1】:

您是在尝试 32 位 Ubuntu 还是 64 位 OS/X?我认为问题在于您的 libc.fopen() 版本返回一个 C“int”,它几乎总是一个 32 位值 --- 但真正的 fopen() 返回一个指针。所以在 64 位操作系统上,你得到的 c_file 被截断为 32 位整数。在 32 位操作系统上,它无论如何都可以工作,因为 32 位整数可以传递回 fread() 和 fclose(),这将再次将其解释为指针。要修复它,您需要声明 libc.fopen() 的 restype。

(我只能推荐 CFFI 作为具有更合理默认值的 ctypes 的替代品,但作为作者之一,我当然不赞成 :-)

【讨论】:

  • 为什么ctypes cast the result to c_int(callproc.c, GetResult) 而不是c_longPyIntObject使用的类型?
  • 谁知道?我最好的选择是,在 Win32(最初开发 ctypes 的地方)上这并不重要,从现在开始就没有太多考虑了。
  • @ArminRigo:很好的答案,立即解决了问题。也对 CFFI 表示赞赏。我一直在寻找一个可以让我使用 distutils 安装通用 c 扩展的包。
猜你喜欢
  • 2011-11-07
  • 2010-11-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-13
  • 2017-03-02
相关资源
最近更新 更多