有什么办法可以减少这两个相依矩阵之间的运算吗?
Is there any way to reduce the operations between these two dependent matrices?
我有两个矩阵,其中第一个矩阵是根据第二个矩阵的数据创建的,用于转换第二个矩阵。我多次重复这个操作。由于这两个矩阵的依赖关系,我一直没能在这里找到加速运算的方法。我会尝试用小矩阵向您展示我在说什么。
[ 1 0 0 0 ] [ .11 .22 .33 .44 ]
[ 0 1 0 0 ] [ .65 .42 .01 .92 ]
[ 0 0 .51^2 .85^2 ] * [ .31 .15 .51 .85 ]
[ 0 0 .44^2 .23^2 ] [ .25 .78 .44 .23 ]
A B
假设我做了数百万次这个操作,并且计算值在 A 中的位置取决于我希望在 B 中发生旋转的位置。因此,在每次迭代中矩阵 A 和 B 是不同的,并且用于计算要放入 A 的新值的值不同。
有谁知道加速此类代码的方法吗?考虑到矩阵乘法本质上是向量矩阵乘法,我希望创建一个组合 A(展开到 A 是一个完整矩阵的点,或者足够充分以利用 MMM 算法),但是新值的数据依赖性使得看来我可能被卡住了。我得到这样的东西:
A * B = B'
A' * B' = B''
A'' * B'' = B'''
其中 A' 派生自 B',A'' 派生自 B'',依此类推。
编辑:
澄清一下,第二轮可以是:
[ 1 0 0 0 ] [ .11 .22 .33 .44 ]
[ 0 .65^2 .42^2 0 ] [ .65 .42 .01 .92 ]
[ 0 .26^2 .60^2 0 ] * [ .26 .60 .45 .39 ]
[ 0 0 0 1 ] [ .06 .04 .10 .17 ]
A' B'
与其说这是一个编程问题,不如说这是一个数学问题,但是是的,有一种方法可以加快您的计算速度。此答案假设您乘以大型矩阵,但 A
是对单位矩阵的轻微修改,仅包含位置 (i,i
)、(i,i+1
)、(i+1,i
) 和 (i+1,i+1
) 正如你在评论中提到的那样。
请注意,将左侧的 B
乘以矩阵 A
,其中 A
的一行全为零,对角线上的 1 除外,等同于说 [= AB
的第 19=] 行等于 B 的第 i
行。这意味着您可以立即在迭代中节省大量计算。
鉴于更新 B' = A*B
其中 A
具有这些不错的属性,我们有以下算法:
- 初始化
B'
正好是 B
.
- 执行矩阵乘法
update = [A(i,:),A(i+1,:)]*B
,其中左侧是 (2xn)
矩阵。
- 将此更新的输出存储在
B'
的正确两行中:[B'(i,:),B'(i+1,:)]
.
每次更新失败 = O(2n^2)
如果您的大小 n
确实很大,您可能还想利用 A
两行的行稀疏性来节省这些小矩阵乘法的更多计算量。您可以通过 (nxn)
矩阵乘法执行 (2xn)
矩阵乘法,而不是通过 (nxn)
矩阵乘法执行 (2xn)
矩阵乘法。
这个新算法将再次更新 B'
的正确两行。但是,现在您的更新矩阵乘法只需:
update = [A(i ,i), A(i ,i+1)] * [B(i ,:)]
[A(i+1,i), A(i+1,i+1)] [B(i+1,:)]
每次更新失败 = O(4n)
请注意,两种算法都不会显式存储所有 A
,因此您将节省内存和计算量。此外,如果您以行优先格式存储矩阵,则上述两种算法的缓存效率都很高。
当矩阵A[rows][num]
乘以矩阵B[num][cols]
时,结果矩阵C[rows][cols]
中的每个元素是
C[r][c] = A[r][0]*B[0][c] + ... + A[r][num-1]*B[num-1][c]
= sum( A[r][k] * B[k][c], k = 0 .. num-1 )
这里,A
是除两行(r0
和r1
)之外的单位矩阵,每行在两列(c0
和c1
).因为行 r0
和 r1
否则为零,所以这些行上的结果矩阵 C
中的每个元素都是两个产品的总和:
C[r0][i] = A[r0][c0] * B[c0][i] + A[r0][c1] * B[c1][i]
C[r1][i] = A[r1][c0] * B[c0][i] + A[r1][c1] * B[c1][i]
并且由于 A
是单位矩阵,结果中的所有其他行与 B
矩阵中的对应行相同。
如果我们使用
a00 = A[r0][c0] a01 = A[r0][c1]
a10 = A[r1][c0] a11 = A[r1][c1]
那么我们可以将更新循环描述为
For i = 0 .. cols-1
C[r0][i] = a00 * B[c0][i] + a01 * B[c1][i]
C[r1][i] = a10 * B[c0][i] + a11 * B[c1][i]
End for
如果我们在更新循环中使用两个临时变量(在 c0 == r0
或 c1 == r0
的情况下,这样我们在计算位于 [r0][i]
的元素之前不会覆盖元素[r1][i]
),整个操作就可以原地完成,只使用B
矩阵。
如果左矩阵A
中的非恒等元元素是矩阵B
中对应元素的平方,我们就地更新,就变得很简单了。在 C99 中:
#include <stdlib.h>
void update(const size_t n, double B[n][n], const size_t r, const size_t c)
{
const double a00 = B[r ][c ] * B[r ][c ];
const double a01 = B[r ][c+1] * B[r ][c+1];
const double a10 = B[r+1][c ] * B[r+1][c ];
const double a11 = B[r+1][c+1] * B[r+1][c+1];
size_t i;
for (i = 0; i < n; i++) {
const double b0i = B[c ][i];
const double b1i = B[c+1][i];
B[r ][i] = a00 * b0i + a01 * b1i;
B[r+1][i] = a10 * b0i + a11 * b1i;
}
}
上述操作的计算成本是4*n+4
次乘法和2*n
次加法(矩阵元素),即O(N).
这种方法也适用于 Givens and Jacobi 旋转,除了不是使用给定矩阵计算四个元素,而是根据传递给函数的参数计算它们;而不是连续的行和列,需要传递两个单独的行和两个列参数。
我有两个矩阵,其中第一个矩阵是根据第二个矩阵的数据创建的,用于转换第二个矩阵。我多次重复这个操作。由于这两个矩阵的依赖关系,我一直没能在这里找到加速运算的方法。我会尝试用小矩阵向您展示我在说什么。
[ 1 0 0 0 ] [ .11 .22 .33 .44 ]
[ 0 1 0 0 ] [ .65 .42 .01 .92 ]
[ 0 0 .51^2 .85^2 ] * [ .31 .15 .51 .85 ]
[ 0 0 .44^2 .23^2 ] [ .25 .78 .44 .23 ]
A B
假设我做了数百万次这个操作,并且计算值在 A 中的位置取决于我希望在 B 中发生旋转的位置。因此,在每次迭代中矩阵 A 和 B 是不同的,并且用于计算要放入 A 的新值的值不同。
有谁知道加速此类代码的方法吗?考虑到矩阵乘法本质上是向量矩阵乘法,我希望创建一个组合 A(展开到 A 是一个完整矩阵的点,或者足够充分以利用 MMM 算法),但是新值的数据依赖性使得看来我可能被卡住了。我得到这样的东西:
A * B = B'
A' * B' = B''
A'' * B'' = B'''
其中 A' 派生自 B',A'' 派生自 B'',依此类推。
编辑:
澄清一下,第二轮可以是:
[ 1 0 0 0 ] [ .11 .22 .33 .44 ]
[ 0 .65^2 .42^2 0 ] [ .65 .42 .01 .92 ]
[ 0 .26^2 .60^2 0 ] * [ .26 .60 .45 .39 ]
[ 0 0 0 1 ] [ .06 .04 .10 .17 ]
A' B'
与其说这是一个编程问题,不如说这是一个数学问题,但是是的,有一种方法可以加快您的计算速度。此答案假设您乘以大型矩阵,但 A
是对单位矩阵的轻微修改,仅包含位置 (i,i
)、(i,i+1
)、(i+1,i
) 和 (i+1,i+1
) 正如你在评论中提到的那样。
请注意,将左侧的 B
乘以矩阵 A
,其中 A
的一行全为零,对角线上的 1 除外,等同于说 [= AB
的第 19=] 行等于 B 的第 i
行。这意味着您可以立即在迭代中节省大量计算。
鉴于更新 B' = A*B
其中 A
具有这些不错的属性,我们有以下算法:
- 初始化
B'
正好是B
. - 执行矩阵乘法
update = [A(i,:),A(i+1,:)]*B
,其中左侧是(2xn)
矩阵。 - 将此更新的输出存储在
B'
的正确两行中:[B'(i,:),B'(i+1,:)]
.
每次更新失败 = O(2n^2)
如果您的大小 n
确实很大,您可能还想利用 A
两行的行稀疏性来节省这些小矩阵乘法的更多计算量。您可以通过 (nxn)
矩阵乘法执行 (2xn)
矩阵乘法,而不是通过 (nxn)
矩阵乘法执行 (2xn)
矩阵乘法。
这个新算法将再次更新 B'
的正确两行。但是,现在您的更新矩阵乘法只需:
update = [A(i ,i), A(i ,i+1)] * [B(i ,:)]
[A(i+1,i), A(i+1,i+1)] [B(i+1,:)]
每次更新失败 = O(4n)
请注意,两种算法都不会显式存储所有 A
,因此您将节省内存和计算量。此外,如果您以行优先格式存储矩阵,则上述两种算法的缓存效率都很高。
当矩阵A[rows][num]
乘以矩阵B[num][cols]
时,结果矩阵C[rows][cols]
中的每个元素是
C[r][c] = A[r][0]*B[0][c] + ... + A[r][num-1]*B[num-1][c]
= sum( A[r][k] * B[k][c], k = 0 .. num-1 )
这里,A
是除两行(r0
和r1
)之外的单位矩阵,每行在两列(c0
和c1
).因为行 r0
和 r1
否则为零,所以这些行上的结果矩阵 C
中的每个元素都是两个产品的总和:
C[r0][i] = A[r0][c0] * B[c0][i] + A[r0][c1] * B[c1][i]
C[r1][i] = A[r1][c0] * B[c0][i] + A[r1][c1] * B[c1][i]
并且由于 A
是单位矩阵,结果中的所有其他行与 B
矩阵中的对应行相同。
如果我们使用
a00 = A[r0][c0] a01 = A[r0][c1]
a10 = A[r1][c0] a11 = A[r1][c1]
那么我们可以将更新循环描述为
For i = 0 .. cols-1
C[r0][i] = a00 * B[c0][i] + a01 * B[c1][i]
C[r1][i] = a10 * B[c0][i] + a11 * B[c1][i]
End for
如果我们在更新循环中使用两个临时变量(在 c0 == r0
或 c1 == r0
的情况下,这样我们在计算位于 [r0][i]
的元素之前不会覆盖元素[r1][i]
),整个操作就可以原地完成,只使用B
矩阵。
如果左矩阵A
中的非恒等元元素是矩阵B
中对应元素的平方,我们就地更新,就变得很简单了。在 C99 中:
#include <stdlib.h>
void update(const size_t n, double B[n][n], const size_t r, const size_t c)
{
const double a00 = B[r ][c ] * B[r ][c ];
const double a01 = B[r ][c+1] * B[r ][c+1];
const double a10 = B[r+1][c ] * B[r+1][c ];
const double a11 = B[r+1][c+1] * B[r+1][c+1];
size_t i;
for (i = 0; i < n; i++) {
const double b0i = B[c ][i];
const double b1i = B[c+1][i];
B[r ][i] = a00 * b0i + a01 * b1i;
B[r+1][i] = a10 * b0i + a11 * b1i;
}
}
上述操作的计算成本是4*n+4
次乘法和2*n
次加法(矩阵元素),即O(N).
这种方法也适用于 Givens and Jacobi 旋转,除了不是使用给定矩阵计算四个元素,而是根据传递给函数的参数计算它们;而不是连续的行和列,需要传递两个单独的行和两个列参数。