关于性能:如何在SymPy中加速慢矩阵乘法?

How to accelerate slow matrix multiplication in SymPy?

我当时正在写一个工具来用SymPy解决特定的递归方程,结果发现涉及矩阵乘法的步骤之一花费的时间特别长。 例如,如果我在iPython控制台中尝试以下操作,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
In [1]: from sympy import *

In [2]: A = Matrix(500, 500, lambda i,j: 2 + abs(i-j) if i-j in [-1, 0, 1] else 0)

In [3]: A[0:7, 0:7]
Out[3]:
Matrix([
[2, 3, 0, 0, 0, 0, 0],
[3, 2, 3, 0, 0, 0, 0],
[0, 3, 2, 3, 0, 0, 0],
[0, 0, 3, 2, 3, 0, 0],
[0, 0, 0, 3, 2, 3, 0],
[0, 0, 0, 0, 3, 2, 3],
[0, 0, 0, 0, 0, 3, 2]])

In [4]: A * A
...

我从来没有等待足够长的时间来完成它。

我欣赏符号处理比数值计算要慢得多,但这似乎很荒谬。 使用八度或其他线性代数包可以在几分之一秒内执行此计算。

有谁有经验将SymPy的矩阵类用于带有Rational条目的约1000行和列?


我没有使用sympy进行矩阵运算,但是我可以用这段代码来再现您遇到的缓慢情况。看来sympy中的矩阵运算不是那么好。

我将建议您使用numpy,它具有出色的矩阵运算并且非常快。这是numpy代码的副本,该代码在我的笔记本电脑上不到1秒就能完成乘法运算:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
In [1]: import numpy as np

In [2]: A = np.matrix([[2 + abs(i-j) if i-j in [-1, 0, 1] else 0 for i in range(0, 500)] for j in range(0, 500)])

In [3]: A[0:7,0:7]
Out[3]:
matrix([[2, 3, 0, 0, 0, 0, 0],
        [3, 2, 3, 0, 0, 0, 0],
        [0, 3, 2, 3, 0, 0, 0],
        [0, 0, 3, 2, 3, 0, 0],
        [0, 0, 0, 3, 2, 3, 0],
        [0, 0, 0, 0, 3, 2, 3],
        [0, 0, 0, 0, 0, 3, 2]])

In [4]: A * A
Out[4]:
matrix([[13, 12,  9, ...,  0,  0,  0],
        [12, 22, 12, ...,  0,  0,  0],
        [ 9, 12, 22, ...,  0,  0,  0],
        ...,
        [ 0,  0,  0, ..., 22, 12,  9],
        [ 0,  0,  0, ..., 12, 22, 12],
        [ 0,  0,  0, ...,  9, 12, 13]])

为什么不使用sympy的稀疏矩阵而不是密集矩阵?解决(线性)递归时出现的矩阵通常是稀疏的。一种技术为您提供了一个矩阵,在第一个超对角线上带有1,在除底行(递归系数所在的位置)以外的其他所有位置为零。


任意精度都是必须的,速度是一个不错的选择

有一些用于此类目的的pythonic类,您可能会对您的任意精度计算感兴趣

  • decimal.Decimal()

  • fractions.Fraction( numerator = 0, denominator = 1 )

这些对于精确的计算是必不可少的,对于大型天文学,DEP模拟或其他领域而言,在随着时间/事件/递归的长标度以及对标准数字表示形式的类似威胁下,精确度绝不能随计算策略的进步而降低。

我个人使用decimal.Decimal()(在加密序列随机性分析中的精度高达5.000.000位),但是我并不专注于'em" inside" numpy矩阵。

Numpy可以在其matrix-dataStructures(dtype = decimal.Decimal语法等)中包含这些类型,但是需要进行测试,以验证其处理这些可爱的pythonic类的实例时的向量化函数操作以及整体速度。

性能

作为对标准的numpy速度的初步观察(" dense"对于这个比例来说很有趣)2x2 decimal.Decimal -s:

1
2
3
4
5
6
>>> aClk.start();m*m;aClk.stop()
array([[Decimal('0.01524157875323883675019051999'),
        Decimal('5.502209507697009702374335655')],
       [Decimal('1.524157875323883675019051999'),
        Decimal('11.94939027587381419881628113')]], dtype=object)
5732L # 5.7 msec_______________________________________________________________

dtype = numpy.float96上的相同

1
2
3
4
>>> aClk.start();f*f;aClk.stop()
array([[ 0.042788046,  0.74206772],
       [ 0.10081096,  0.46544855]], dtype=float96)
2979L # 2.9 msec_______________________________________________________________

对于500 x 500完全填充的dtype = fractions.Fraction

1
2
3
4
5
6
7
>>> aClk.start();M*M;aClk.stop()
array([[Fraction(9, 64), Fraction(1, 4), Fraction(64, 25), ...,
        Fraction(64, 81), Fraction(16, 81), Fraction(36, 1)],
        ..,
       [Fraction(1, 1), Fraction(9, 4), Fraction(4, 1), ...,
        Fraction(1, 4), Fraction(25, 36), Fraction(1, 1)]], dtype=object)
2692088L # 2.7 sec_<<<_Fraction_______________________________vs. 19 msec float96

对于500 x 500完全填充的密集dtype = decimal.Decimal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> aClk.start();D*D;aClk.stop()
array([[Decimal('0.140625'), Decimal('0.25'), Decimal('2.56'), ...,
        Decimal('0.7901234567901234567901234568'),
        Decimal('0.1975308641975308641975308642'), Decimal('36')],
       [Decimal('3.24'), Decimal('0.25'), Decimal('0.25'), ...,
        Decimal('0.02040816326530612244897959185'), Decimal('0.04'),
        Decimal('0.1111111111111111111111111111')],
       [Decimal('0.1111111111111111111111111111'), Decimal('0.25'),
        Decimal('2.25'), ..., Decimal('0.5102040816326530612244897959'),
        Decimal('0.25'), Decimal('0.0625')],
       ...,
       [Decimal('0'), Decimal('5.444444444444444444444444443'),
        Decimal('16'), ..., Decimal('25'), Decimal('0.81'), Decimal('0.04')],
       [Decimal('1'), Decimal('7.111111111111111111111111113'),
        Decimal('1'), ..., Decimal('0'), Decimal('81'), Decimal('2.25')],
       [Decimal('1'), Decimal('2.25'), Decimal('4'), ..., Decimal('0.25'),
        Decimal('0.6944444444444444444444444444'), Decimal('1')]], dtype=object)
4789338L # 4.8 sec_<<<_Decimal_______________________________vs. 19 msec float96
2692088L # 2.7 sec_<<<_Fraction______________________________vs. 19 msec float96

三对角矩阵1000x1000的期望值可以低于50毫秒吗?

由于存在3,000个非零(稀疏表示)元素,而不是上面测试的完全填充的500x500矩阵中的250,000个单元,因此使用这些pythonic类进行任意精度计算的性能会有巨大的提高。一旦引擎可能在MUL / DIV操作上使用numerator / denominator构造优于Decimal的情况下,分数就有更大的空间,但是在实际情况下,您的计算方法正在使用时,应在体内对确切的性能范围进行测试。

SparseMatrix的Sympy语法和1000x1000三对角线上的测试

@tmyklebu提出的对1000x1000 SparseMatrix的真实测试由于安装麻烦而花了更长的时间来阐述,但是可能会为您提供对现实世界实施项目的一些进一步了解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
>>> F = sympy.SparseMatrix( 1000, 1000, { (0,0): 1} )        # .Fraction()
>>> D = sympy.SparseMatrix( 1000, 1000, { (0,0): 1} )        # .Decimal()

>>> for i in range( 1000 ):                                  # GEN to have F & D hold
...     for j in range( 1000 ):                              #     SAME values,
...         if i-j in [-1,0,1]:                              # but DIFF representations
...            num = int( 100 * numpy.random.random() )      #    
...            den = int( 100 * numpy.random.random() ) + 1  # + 1 to avoid DIV!0
...            F[i,j] = fractions.Fraction( numerator = num, denominator = den )
...            D[i,j] = decimal.Decimal( str( num ) ) / decimal.Decimal( str( den ) )

# called in Zig-Zag(F*F/D*D/F*F/D*D/...) order to avoid memory-access cache artifacts

>>> aClk.start();VOID=F*F;aClk.stop()
770353L                                      # notice the 1st eval took  TRIPLE LONGER
205585L                                      # notice the 2nd+
205364L # 0.205 sec_<<<_Fraction()____________________________vs. 0.331 sec Decimal()


>>> aClk.start();VOID=D*D;aClk.stop()
383137L # 0.383 sec_<<<_Decimal()____________________________vs. 0.770 sec 1st Fraction()
390164L # 0.390 sec_<<<_Decimal()____________________________vs. 0.205 sec 2nd Fraction()
331291L # 0.331 sec_<<<_Decimal()____________________________vs. 0.205 sec 3rd Fraction()

>>> F[0:4,0:4]
Matrix([
[ 1/52,  6/23,     0,     0],
[42/29, 29/12,     1,     0],
[    0, 57/88, 39/62, 13/57],
[    0,     0, 34/83, 26/95]])
>>> D[0:4,0:4]
Matrix([
[0.0192307692307692, 0.260869565217391,                 0,                 0],
[  1.44827586206897,  2.41666666666667,               1.0,                 0],
[                 0, 0.647727272727273, 0.629032258064516, 0.228070175438596],
[                 0,                 0, 0.409638554216867, 0.273684210526316]])