【问题标题】:reason for faster matrix allocation in RR中更快的矩阵分配的原因
【发布时间】:2012-08-31 17:38:26
【问题描述】:

发布Best way to allocate matrix in R, NULL vs NA? 表明,在 R 中编写自己的矩阵分配函数可以比使用 R 的内置 matrix() 函数预分配大矩阵快 8 到 10 倍。

有谁知道为什么手工制作的功能如此之快? R 在如此慢的 matrix() 内部做了什么?谢谢。

这是我系统上的代码:

create.matrix <- function( nrow, ncol ) {
x<-matrix()
length(x) <- nrow*ncol
dim(x) <- c(nrow,ncol)
x
}

system.time( x <- matrix(nrow=10000, ncol=9999) )
user  system elapsed 
1.989   0.136   2.127 

system.time( y <- create.matrix( 10000, 9999 ) )
user  system elapsed 
0.192   0.141   0.332 
identical(x,y)
[1] TRUE

我向那些评论认为用户定义的函数速度较慢的人表示歉意,因为上述链接的答案中发布的内容不一致。我正在查看用户时间,在上面的链接中大约快 8 倍,而在我的系统上,用户定义的与内置的大约快 10 倍。

响应 Joshua 的会话信息请求:

> sessionInfo()
R version 2.12.1 (2010-12-16)
Platform: x86_64-unknown-linux-gnu (64-bit)

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
 [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=C              LC_MESSAGES=en_US.UTF-8   
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

loaded via a namespace (and not attached):
[1] tools_2.12.1

另外,我尝试运行 Simon 的三个示例,Simon 给出的第三个示例速度最快,但对我来说却是最慢的:

> system.time(matrix(NA, nrow=10000, ncol=9999)) 
   user  system elapsed 
  2.011   0.159   2.171 
> system.time({x=NA; length(x)=99990000; dim(x)=c(10000,9999); x}) 
   user  system elapsed 
  0.194   0.137   0.330 
> system.time(matrix(logical(0), nrow=10000, ncol=9999)) 
   user  system elapsed 
  4.180   0.200   4.385 

不过,我仍然认为 Simon 的想法可能是正确的,即 matrix() 最初分配一个 1x1 矩阵然后复制它。有人知道关于 R 内部的任何好的文档吗?谢谢。

【问题讨论】:

  • 我不确定我是否遵循。那个问题中的自定义函数不是 slower 吗(即使那样,大约是 3 倍)?正如评论中指出的那样,使用矢量化总是会快得多。
  • 在接受的答案中,我看到用户定义的函数比仅使用 matrix 慢 3 倍。你能提供一个比matrix快8-10倍的自定义函数的例子吗?
  • 还要注意链接中的 2 个对象具有不同的存储模式(向量(可能是逻辑)与列表),因此虽然它们都是矩阵,但它们在可以存储的内容和随之而来的开销。
  • 你的sessionInfo 是什么? create.matrix 函数在 WinXP 32 位上使用 R-2.15.1 仅稍微快一点(~15%),但我在 Ubuntu 64 位上使用 R-2.15.1 得到类似的时间。
  • 我不知道你为什么仍然认为西蒙是在正确的轨道上。我已经在我的 cmets 中解释了他的回答在哪里以及为什么我认为它偏离了轨道。不过要等西蒙确认。

标签: r matrix


【解决方案1】:

问题是您的matrix 调用比您想象的要复杂一些。比较以下版本:

# copy NA matrix
> system.time(matrix(NA, nrow=10000, ncol=9999))
   user  system elapsed 
  1.272   0.224   1.496 

# replicate NA vector (faster version of what you used)
> system.time({x=NA; length(x)=99990000; dim(x)=c(10000,9999); x})
   user  system elapsed 
  0.292   0.260   0.552 

# fastest - just allocate a matrix filled with NAs 
> system.time(matrix(logical(0), nrow=10000, ncol=9999))
   user  system elapsed 
  0.184   0.308   0.495 

因此,在您的示例中,您实际上是在创建一个 1 x 1 NA 矩阵,该矩阵被复制到您指定的大小 - 最慢的方法。对向量执行相同操作会更快(因为它不需要对列使用模)-您以一种有点复杂的方式进行操作(通过创建矩阵,将其转换为向量,然后再转换回矩阵),但是这个想法是一样的。最后,如果你只使用一个空向量,那么矩阵将简单地用NAs 填充你想要的东西,因此不需要额外的工作(最快)。

编辑 不过,重要的一点是:Matthew 的建议是正确的,尽管没有涉及(因为他引用的代码是 logical(0) 案例,而不是 NA 案例)。无意中我在上述时间运行 R-devel,所以发布的 R 中的时间会有所不同。

【讨论】:

  • 感谢您如此快速地更改 do_matrix。但是我认为您在上面的第三种情况下使用的是固定版本,因为传递给matrix()data 的长度为0,使C do_matrix 中的lendat 0,因此然后运行新的更高效的代码。为了检查,对我来说,在 R 2.15.1 中运行你的时间会显示第 1 次和第 3 次同时运行。
  • 或者换句话说,我同意我的建议没有涉及,但现在你已经通过设置data=logical(0) 参与了它:-)
  • 好的。现在我们正在取得进展。我还是不太明白里面发生了什么。听起来matrix(nrow=10000,ncol=9999)matrix(data=NA,nrow=10000,ncol=9999) 相同,两者都很慢,因为它们首先创建一个单元素矩阵,然后将其复制 (10000*9999) 次。 matrix(logical(0),nrow=10000,ncol=9999) 与其他两个有何不同? logical(0) 表示 NA 不表示什么?谢谢。
  • 嗯。我今天刚回到我的系统,你说的最快(即使用逻辑(0))对我来说是最慢的。需要 4 秒!
  • @Daniel 我已经在上面的 cmets 中解释过了。等待西蒙确认。
【解决方案2】:

我将对 cme​​ts 提出异议,尽管我确实了解其中的大多数。问题是引用的帖子的答案存在内部矛盾,评论者一直依赖而没有检查。用户和系统的时间没有按应有的方式正确加起来。

 create.matrix <- function(size) {
  x <- matrix()
  length(x) <- size^2
  dim(x) <- c(size,size)
  x
  }
  system.time(x <- matrix(data=NA,nrow=10000,ncol=10000))
#   user  system elapsed 
#  0.464   0.226   0.688 
 system.time(y <- create.matrix(size=10000))
#   user  system elapsed 
#  0.177   0.239   0.414 

我怀疑效率实际上是通过以下事实实现的:用户定义的函数只能创建方阵,并且“矩阵”需要检查参数的有效性以适应更一般的情况。

编辑:我看到你已经反驳了我的一个假设(关于方阵限制),我还会注意到我的另一个假设,即这是由于惰性评估导致的,我的测试也失败了。这种差异确实没有意义,因为用户代码使用了matrix 函数。

【讨论】:

  • 我也对 cme​​ts 持异议,尤其是在他们说矩阵比用户定义的函数快的地方。但是,我很抱歉没有更仔细地查看帖子中的时间安排(我说的是摆姿势的答案,而不是帖子本身)。你是对的,他们不加起来。这是它在我的系统上的样子,并带有一个非方阵:(下一条评论)。
  • 'create.matrix system.time( x system.time( y 相同(x,y) [1] 真'
  • @user1639359:如果您将这些示例添加到您的问题中会更好(就像我在评论中要求的那样)。
  • 对不起,我想我可以在评论中格式化代码。我已将其添加到问题中。谢谢。 --d.
  • @user1639359 很好奇。我只会为我的评论辩护,因为它被表述为表达困惑,而不是确定性。 (我的速度也只有约 15% 的差异。)
【解决方案3】:

不确定这是不是这个原因(可能是不同的低效率),但在 src/array.c 中的do_matrix 中有一个类型开关,其中包含:

case LGLSXP :
    for (i = 0; i < nr; i++)
    for (j = 0; j < nc; j++)
        LOGICAL(ans)[i + j * NR] = NA_LOGICAL;

这看起来是页面效率低下。认为应该是:

case LGLSXP :
    for (j = 0; j < nc; j++)
    for (i = 0; i < nr; i++)
        LOGICAL(ans)[i + j * NR] = NA_LOGICAL;

或更简单:

case LGLSXP :
    for (i = 0; i < nc*nr; i++)
        LOGICAL(ans)[i] = NA_LOGICAL;

(需要进行一些微调,因为 NRR_xlen_t 类型,而 incnrint 类型)。


更新:

发布到 r-devel 后:

Possible page inefficiency in do_matrix in array.c

Simon Urbanek 现在已提交对 R 的更改。它现在使用上面的单索引方法:

Latest live version of array.c

但正如西蒙所说,我在上面介绍了自己,这似乎并不能解决问题提出的特定问题。第二个不同的低效率问题也需要找到并解决。


这可能是后续修复的内容。这结合了新代码(现在在 R 中)的页面效率,但是,当 matrix(data=NA)(R 的默认值)时切换到使用它。这通过避免 NA 案例中的 copyMatrix 避免了 Simon 在他的回答中提到的 copyMatrix 中的模数。

目前,array.c 中的 do_matrix 有:

if(lendat) {
    if (isVector(vals))
        copyMatrix(ans, vals, byrow);
    else
        copyListMatrix(ans, vals, byrow);
} else if (isVector(vals)) { 
    // fill with NAs in the new page efficient way that Simon already committed.
}

可能如下。我不知道 C 级别的 ISNA() 函数需要 SEXP 输入,所以已经编写了那个长手(Simon,有更好的方法吗?):

if(lendat && // but not a single NA, basically :
             !(lendat==1 &&
                  ((isLogical(vals) && LOGICAL(vals)[0] == NA_LOGICAL) ||
                   (isReal(vals) && ISNA(REAL(vals)[0])) ||
                   (isInteger(vals) && INTEGER(vals)[0] == NA_INTEGER)))) {
    if (isVector(vals))
        copyMatrix(ans, vals, byrow);
    else
        copyListMatrix(ans, vals, byrow);
} else if (isVector(vals)) { 
    // fill with NAs in the new page efficient way that Simon already committed.
    // this branch will now run when dat is a single NA, too
}

【讨论】:

  • 可能是一个失败的原因,但是您是否考虑过将其发送给 r-devel? (或拉德福德尼尔?)
【解决方案4】:

嗯。是的,这很奇怪。 ...而且这仍然稍微快一些 - 尽管它更像 matrix() ,因为它允许单个数据参数(但它必须是标量):

create.matrix2 <- function(data=NA, nrow, ncol) {
  x <- rep.int(data[[1]], nrow*ncol)
  dim(x) <- c(nrow, ncol)
  x
}
system.time( x <- matrix(nrow=10000, ncol=9999) ) # 0.387 secs
system.time( y <- create.matrix(nrow=10000, ncol=9999) )  # 0.199 secs
system.time( z <- create.matrix2(nrow=10000, ncol=9999) ) # 0.173 secs
identical(x,z) # TRUE

...我猜用于创建矩阵的内部代码做了一些浪费(或者可能有用,但我想不出那会是什么)...

哦,因为它处理任何长度的data,它最终可能会得到类似于rep(data, length.out=nrow*ncol) 的东西,但速度相当慢:

system.time( rep(NA, length.out=10000*9999) ) # 1.5 secs!

无论如何,肯定有改进的余地!

【讨论】:

    猜你喜欢
    • 2018-05-14
    • 1970-01-01
    • 2013-08-14
    • 2017-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-10
    • 1970-01-01
    相关资源
    最近更新 更多