And*_*ker 9 python ctypes numpy
是否有一种高性能方法可以将 numpy 数组转换为 FORTRAN 有序 ctypes 数组,理想情况下不需要副本,并且不会触发与步幅相关的问题?
\nimport numpy as np\n\n# Sample data\nn = 10000\nA = np.zeros((n,n), dtype=np.int8)\nA[0,1] = 1\n\ndef slow_conversion(A):\n return np.ctypeslib.as_ctypes(np.ascontiguousarray(A.T))\n\nassert slow_conversion(A)[1][0] == 1\n
Run Code Online (Sandbox Code Playgroud)\n仅调用as_ctypes的性能分析:
\n%%timeit\nnp.ctypeslib.as_ctypes(A)\n
Run Code Online (Sandbox Code Playgroud)\n3.35 \xc2\xb5s \xc2\xb1 每个循环 10.5 ns(意味着 \xc2\xb1 标准偏差 7 次运行,每次 100000 次循环)
\n所提供的(慢速)转换的性能分析
\n%%timeit\nslow_conversion(A)\n
Run Code Online (Sandbox Code Playgroud)\n206 ms \xc2\xb1 每个循环 10.4 ms(平均 \xc2\xb1 标准偏差 7 次运行,每次 1 次循环)
\n理想的结果是获得与调用类似的性能as_ctypes
。
默认情况下,Numpy 创建 C 序数组(因为它是用 C 编写的),即行优先数组。进行转置A.T
会创建一个数组视图,其中步长是相反的(即没有复制)。话虽如此,np.ascontiguousarray
进行复制是因为数组现在不再连续,并且复制成本很高。这就是为什么slow_conversion
慢的原因。yourarray.flags['F_CONTIGUOUS']
请注意,可以使用和 通过检查来测试连续性yourarray.strides
。另请注意yourarray.flags
,并yourarray.__array_interface__
提供有关数组是否已被复制的信息以及有关步幅的信息。
np.asfortranarray
返回有关文档的内存中按 Fortran 顺序排列的数组。如果需要,它可以执行复制。事实上,np.asfortranarray(A)
复制而不复制np.asfortranarray(A.T)
。您可以检查该函数的C 代码以获取有关此行为的更多信息。由于两者都被视为 FORTRAN 连续,因此在这种情况下最好使用np.asfortranarray(A.T)
不进行任何复制。
关于 ctypes,它处理以行优先顺序存储的 C 数组,而不是以列优先顺序存储的 FORTRAN 数组。此外,与 FORTRAN 数组不同,C 数组本身不支持步幅。这意味着行基本上是存储在内存中的连续数据的内存视图。由于slow_conversion(A)[1][0] == 1
要求为 true,这意味着返回的对象第二行的第一项应该为 1,因此这些列必须连续存储在内存中。问题是初始数组不是 FORTRAN 连续的而是 C 连续的,因此需要转置。转置非常昂贵(尽管 Numpy 实现不是最优的)。
假设您不想支付复制/转置的开销,则需要放宽该问题。有几个可能的选择:
np.zeros((n,n), dtype=np.int8, order='F')
。这将创建一个具有转置步长的 C 数组,以便其行为类似于 FORTRAN 数组,其中对列进行的计算速度很快(请记住,Numpy 是用 C 编写的,因此行优先有序数组是参考)。这样,ctypes 中的第一行实际上是一列。请注意,出于性能考虑,在混合 C 和 FORTRAN 有序数组时应非常小心,因为非连续访问速度要慢得多。A.ctypes.data_as(POINTER(c_double))
,使用 提取步长A.strides
,使用 提取形状A.shape
。话虽这么说,这个解决方案似乎并不是真正的便携/标准。标准方法似乎是在 FORTRAN 中使用 C 绑定。我对此不是很熟悉,但您可以在此答案中找到完整的示例。最后一个解决方案是使用快速转置算法手动转置数据。就地转置比异地转置更快(通常是 2 倍),但这需要一个平方数组,并且它会改变稍后不应使用的输入数组(除非可以在转置数组上进行操作) 。快速转置不能直接使用 Numpy 编写。一种解决方案是在 Numba 中执行(这是一个次优的异地解决方案,但已经相当快了),或者在 C 中或直接在 FORTRAN 中执行(在所有情况下都使用包装函数)。这应该比 Numpy 快得多,但仍然比基本的 ctypes 包装慢得多。
正如您所指出的,np.ctypeslib.as_ctypes(...)
速度很快。
你的计算的瓶颈是np.ascontiguousarray(A.T)
- 这相当于np.asfortranarray(A)
同样慢。
这让我相信仅使用 numpy 无法使这变得更快。我的意思是,由于已经存在一个完整的专用函数来执行此操作(np.asfortranarray
) - 我们假设它已经过优化以具有最佳性能。
这样的话,问题就解决了!您可以获得与调用一样的性能,as_ctypes
因为数组在内存中已经按照所需的顺序排列:
n = 10000\nA = np.zeros((n, n), dtype=np.int8, order=\'F\')\nA[0, 1] = 1\n\ndef conversion(A):\n return np.ctypeslib.as_ctypes(np.ascontiguousarray(A.T))\n\nassert conversion(A)[1][0] == 1\n
Run Code Online (Sandbox Code Playgroud)\n%%timeit\nconversion(A)\n\n# 4.76 \xc2\xb5s \xc2\xb1 68.4 ns per loop (mean \xc2\xb1 std. dev. of 7 runs, 100,000 loops each)\n
Run Code Online (Sandbox Code Playgroud)\n感谢@J\xc3\xa9r\xc3\xb4meRichard 和@StephanSchlecht 激发了这个想法。阅读@J\xc3\xa9r\xc3\xb4meRichard\ 的答案以获得更多见解!
\n 归档时间: |
|
查看次数: |
906 次 |
最近记录: |