我有一个np数组,其中包含Fractions表单中的元素以获得机器精度。我想应用线性代数程序,如高斯消除。这是我到目前为止的Cython代码(请注意,它只显示了获得上三角形的步骤,但实际上没有反射它。)
Python生成的数据:
size = 5
foo = np.array([[Fc(v).limit_denominator(100) for v in r]
for r in np.random.randn(size, size)])
identity = np.array([[Fc(v) for v in r] for r in np.identity(len(foo))])
m_id = np.concatenate([foo, identity], axis=1)
用Cython:
%%cython
import numpy as np
cimport numpy as np
from quicktions import Fraction as Fc
cimport cython
@cython.boundscheck(False)
@cython.wraparound(False)
@cython.nonecheck(False)
def invert_gaussian4(np.ndarray matrix):
cdef int matrix_size = matrix.shape[1] // 2
cdef int c_i
cdef int r_i
cdef int swap
for c_i in range(matrix_size - 1):
swap = np.argmax(np.abs(matrix[c_i:, c_i])) + c_i
matrix[[swap, c_i]] = matrix[[c_i, swap]]
row = matrix[c_i, :] / matrix[c_i, c_i]
for r_i in range(c_i+1, matrix_size):
del_row = row * matrix[r_i, c_i]
matrix[r_i, :] = matrix[r_i, :] - del_row
与Python相比,Cython功能的性能并没有那么大。我已经认识到循环中的np函数调用和分数元素是减慢代码的速度。关于如何更好地优化此代码的任何建议?
答案 0 :(得分:1)
你可以拥有速度,或者你可以保持极高的精确度。你需要做出决定。
您在评论中已经说过,这些数据用于“研究”但尚未指定该领域。在很多研究领域,所产生的数据并不准确。相反,它们是通过测量现实世界现象而获得的近似值。我们说每个值都有一些重要的数字。这些有意义的数字是通过计算传播的,最后,你应该将任何无关紧要的数字都抛弃。
虽然浮点数学确实涉及中间舍入,但这种舍入通常会保留足够多的有效数字,最终结果不会受到影响。例如,使用64位双精度IEEE 754浮点值(默认情况下Python执行),您的有效数字在基数2中有53个有效数字,其在基数10中的有效数字为approx. 15。如果您的实际数据只有五个有效数字,那么你不应该关心这个中间四舍五入的最合理的操作。
如果此段确实描述了您正在做的事情,那么您应该用标准浮点数替换您的分数对象。仅这一点就可以大大加快你的计算速度。
如果这不能准确地描述你在做什么(例如因为你的领域是各种各样的纯数学或其他理论学科),那么你可能别无选择,只能忍受缓慢。您可以查看SymPy,它旨在执行这些字段倾向于关注的符号操作。
如果您使用的是真实数据,但它有超过15个有效数字,那么您应该知道64位整数只能高达~10 ^ 18。这意味着你的分数对象很可能是使用任意精度的整数来实现的,这些整数非常慢。在这种情况下,您希望使用支持128位整数和/或浮点数的(超级计算)平台,并且您可能不希望使用Python进行编码(读取:预编译的Python二进制文件可能或可能对于这样一个平台来说并不存在,并且根据它的标准一致性,它们可能会也可能不会自己编译;无论性能最好是否有问题。
最后,你不应该编写自己的高斯消除程序。相反,请使用numpy.linalg.solve
。这可能会更快,更精确。
答案 1 :(得分:1)
这个答案不会解决问题,但会解释为什么你遇到问题并可能会给你一些线索,告诉你需要妥协的地方。基本上你有两个问题:
如果它们是由单个整数或浮点类型组成的数组,则Cython最适合numpy数组。您的数组由任意Python对象组成(因此在C中,数组的每个元素都是指向存储Python对象的单独内存位置的指针)。这意味着每个索引操作都需要递增和递减以用于引用计数。
这也意味着对元素的快速C级操作不可用。当您执行添加时,它必须在对象上对__add__
执行字典查找,然后调用该Python函数。类似地,调用np.abs
对数组的每个元素执行__abs__
字典查找,然后执行Python函数调用。类似地,调用argmax涉及对每个元素的比较运算符(可能是__lt__
)进行字典查找。显然这个东西很耗时,特别是比较纯C的数组,其中每个操作可能都接近一个处理器指令。
这样做的一种方法是创建两个具有固定大小整数类型(理想情况下为64位)的数组,以表示分数的分子和分母。 (注意:numpy数组的类型由dtype
给出。你需要在Cython中自己实现各种算术运算,但事实上它是一个单一的已知数据类型会给你一个显着的加速(或者你可以使用一个numpy结构化数组将它们存储在一个数组中)。
然而:
Fraction
类使用任意长度整数为了避免不得不舍入任何东西,分数类使用Python“长”整数,它可以保存尽可能大的数字(达到你的记忆的极限),但需要一个未知的内存量这样做。
这样做的结果是不可能在具有固定大小类型的连续数组中分配它们(这有利于索引速度)。第二个结果是不可能为它们生成“快速”C代码 - 在每次操作期间,它需要计算出当前数量有多大,注意溢出,可能分配更多内存。
对此最好的解决方案就是接受您需要在某处进行舍入并使用固定大小的数据类型。第二种解决方案可能是明确的“快速路径”和“慢速路径”。存储4个数组:分子小,分子大,分母小,分母大,小的是dtype=np.int64
,大的是通用的dtype=object
。如果可能的话,使用小的,只使用发生溢出的大的。如果您的大部分数据都是“小”,这可能会更快。任何一个选项都需要自己编写大量的算术代码。