什么时候交叉推进'喜欢'%*%',什么时候不是?

时间:2017-03-13 20:52:56

标签: r matrix matrix-multiplication blas

当X和Y都是矩阵时,crossprod(X,Y)是否优先于t(X) %*% Y?文档说

  

将矩阵xy作为参数,返回矩阵交叉积。   这正式等同于(但通常略快)   致电t(x) %*% ycrossprod)或x %*% t(y)tcrossprod)。

那么什么时候更快?在线搜索时,我发现有几个消息来源声明crossprod通常是首选,应该用作默认值(例如here),或者它取决于比较结果并不确定。例如,Douglas Bates (2007) says that

  

请注意,在较新版本的R和BLAS库中(截至夏季)   2007),R %*%能够检测到mm中的多个零和快捷方式   许多操作,因此对于这样的稀疏矩阵来说要快得多   比目前没有使用此类的crossprod   优化[...]

那么我应该何时使用%*%以及何时使用crossprod

1 个答案:

答案 0 :(得分:4)

我在几个关于统计计算问题的答案中简要介绍了这个问题。 my this answer跟进部分相对全面。请注意,我在那里的讨论以及以下内容确实假设您知道 BLAS 是什么,尤其是3级BLAS例程dgemmdsyrk

我在这里的答案,将提供一些证据和基准,以确保我在那里的论点。此外,道格拉斯贝茨的解释'评论将会给出。

crossprod"%*%"真正做了什么?

让我们检查两个例程的源代码。 R base 包的C级源代码主要位于

R-release/src/main

特别是,矩阵运算在

中定义
R-release/src/main/array.c

现在,

  • "%*%"与C例程matprod匹配,后者使用dgemmtransa = "N"调用transb = "N";
  • crossprod很容易被视为复合函数,与symcrossprodcrossprodsymtcrossprodtcrossprod匹配(复杂矩阵的对应物,如ccrossprod,未列在此处。)

您现在应该了解crossprod避免所有显式矩阵转置。 crossprod(A, B)t(A) %*% B便宜,因为后者需要A的显式矩阵转置。在下文中,我将其称为转置开销

R级别分析可以暴露这种开销。请考虑以下示例:

A <- matrix(ruinf(1000 * 1000), 1000)
B <- matrix(ruinf(1000 * 1000), 1000)

Rprof("brutal.out")
result <- t.default(A) %*% B
Rprof(NULL)
summaryRprof("brutal.out")

Rprof("smart.out")
result <- crossprod(A, B)
Rprof(NULL)
summaryRprof("smart.out")

注意t.default如何进入第一种情况的分析结果。

分析还为执行提供了CPU时间。你会发现两者似乎花了相同的时间,因为开销是微不足道的。现在,我会告诉你什么时候头痛是痛苦的。

转置开销何时显着?

Ak * m矩阵,Bk * n矩阵,然后矩阵乘法A'B'表示转置​​) FLOP计数(浮点加法和乘法的数量)2mnk。如果您执行t(A) %*% B,转置开销为mkA中的元素数量),因此比例为

useful computation : overhead = 2n : 1

除非n很大,否则转置开销不能在实际矩阵乘法中摊销。

最极端的情况是n = 1,即B有一个单列矩阵(或向量)。考虑一个基准测试:

library(microbenchmark)
A <- matrix(runif(2000 * 2000), 2000)
x <- runif(2000)
microbenchmark(t.default(A) %*% x, crossprod(A, x))

您会看到crossprod快几倍!

"%*%"何时结构上较差?

正如我的链接答案(以及Bates&#39;基准测试结果的注释)中所述,如果您执行A'Acrossprod肯定会快2倍。

如果你有稀疏矩阵怎么办?

具有稀疏矩阵并不会改变上面的基本结论。用于设置稀疏矩阵的R包Matrix也具有"%*%"crossprod的稀疏计算方法。所以你仍然希望crossprod稍快一些。

那么贝茨&#39;评论BLAS&#34;截至2007年夏季&#34;意思?

这与稀疏矩阵无关。 BLAS严格用于密集数值线性代数。

它与 Netlib的F77参考 dgemm中使用的循环变体的区别有关。有两种循环变量用于调度矩阵 - 矩阵乘法op(A) * op(B)(此处*表示矩阵乘法而非元素乘积!),变量完全由op(A)的转置设置决定:

  • 代表op(A) = A',&#34;内部产品&#34; version用在最里面的循环中,在这种情况下不可能进行零检测;
  • 代表op(A) = A,&#34; AXPY&#34;使用版本,op(B)中的任何零都可以从计算中排除。

现在想想R如何调用dgemm。第一种情况对应crossprod,而第二种情况对应"%*%"(以及tcrossprod)。

在这方面,如果您的B矩阵有很多零,虽然它仍然是密集矩阵格式,那么t(A) %*% B将比{{1}更快只是因为前者的循环变量更有效。

最有启发性的例子是crossprod(A, B)是对角矩阵或带状矩阵。让我们考虑带状矩阵(这里实际上是对称的三对角矩阵):

B

现在让B_diag <- diag(runif(1000)) B_subdiag <- rbind(0, cbind(diag(runif(999)), 0)) B <- B_diag + B_subdiag + t(B_subdiag) 仍然是一个完整的密集矩阵

A

然后比较

A <- matrix(runif(1000 * 1000), 1000)

你会发现library(microbenchmark) microbenchmark(t.default(A) %*% B, crossprod(A, B)) 要快得多!

我想一个更好的例子是矩阵"%*%"是一个三角矩阵。这在实践中相当普遍。三角矩阵不被视为稀疏矩阵(也不应存储为稀疏矩阵)。

警告:如果您使用R和优化的BLAS库(如OpenBLAS或英特尔MKL),您仍会看到B更快。但是,这真的是因为阻塞&amp;任何优化的BLAS库中的缓存策略都会破坏循环变体调度模式,如Netlib的F77参考BLAS。结果,任何&#34;零检测&#34;是不可能的。所以您将观察到的是对于这个特定的例子,F77参考BLAS甚至比优化的BLAS更快。