R:在没有预分配的情况下动态更新矩阵时的效率问题

Posted

技术标签:

【中文标题】R:在没有预分配的情况下动态更新矩阵时的效率问题【英文标题】:R: Efficiency concerns when dynamically updating matrix without preallocation 【发布时间】:2015-10-21 11:31:46 【问题描述】:

我正在从一个 API 中提取数据,我通过 for 循环将这些数据堆叠在一个数组中。我在plyr 上使用for 循环,因为我的代码还不够健壮,无法正常运行,因此现在我可以手动或使用tryCatch() 重新启动for 循环。工作示例(为了说明,没有 API 调用,但有一些简单的计算):

x <- 1:10000
mydata <- NULL

for(i in 1:length(x))

  y = x[i]
  x1 = y*2
  x2 = y*3
  x3 = y*4

mydata <- rbind(mydata, cbind(x1,x2,x3))


经过一番研究,我怀疑瓶颈是rbind() 行。一个典型的解决方案是预先分配矩阵以合并最终数据的维度。但是,我无法为我的目的预先分配矩阵的原因是 API 的输出具有不同的维度。我不知道我的数据的维度是多少(一些 API 调用返回零行,而另一些返回 100)。如何使每次迭代的数据堆叠更(时间)有效,尤其是当x 的大小因此目标矩阵增加时?

【问题讨论】:

如果您使用的是for 循环,这是否意味着您知道循环运行了多少次?如果你知道循环运行了多少次,你不也知道最终数据框中有多少行吗? @Benjamin 如果您从 API 运行请求,请求的输出可能会从迭代变为其他。 对不起,我不清楚。我确实知道循环运行了多少次,但是(1)一些 API 调用无效,因此不返回任何数据;并且即使API调用有效,(2)返回的数据可以是数据中的0行,1、2……最多上千行。因此,我永远无法准确估计最终矩阵的维度。 啊,我明白了。感谢您的澄清。在这种情况下,是否可以分配临时数据帧(或NULLs,视情况而定)来保存每次迭代的数据,然后在一次调用中绑定所有临时数据帧? (即,使用assign(paste0("temp_df", [i]), cbind(x1, x2, x3)))....只是头脑风暴。 您能否使用length == length(x) 的预分配“列表”并根据每次迭代在每个元素中分配有效或无效或不存在的数据? 【参考方案1】:

这里是一些选项的比较

使用您当前的代码:

x <- 1:10000

system.time(  mydata <- NULL
                for(i in 1:length(x))
                  y = x[i]
                  x1 = y*2
                  x2 = y*3
                  x3 = y*4
                  mydata <- rbind(mydata, cbind(x1,x2,x3))
                
              )

   user  system elapsed 
   1.42    0.00    1.42 

使用列表会更快一些

system.time(  mydata_list <- NULL
               for(i in 1:length(x))
                 y = x[i]
                 x1 = y*2
                 x2 = y*3
                 x3 = y*4
                 mydata_list <- c(mydata_list, list(cbind(x1,x2,x3)))
               
               mydata_list <- do.call("rbind", mydata_list)
             )

user  system elapsed 
0.42    0.00    0.42

如果您将每次迭代的输出分配给不同环境中的对象并在之后检索它们,您将获得更多时间。

system.time(  temp_env <- new.env()
               for(i in 1:length(x))
                 y = x[i]
                 x1 = y*2
                 x2 = y*3
                 x3 = y*4
                 assign(paste0("obj", i), cbind(x1, x2, x3), envir = temp_env)
               
               mydata_env <- do.call("rbind", 
                                     mget(paste0("obj", 1:length(x)), envir = temp_env))
             )

user  system elapsed 
0.09    0.00    0.09

【讨论】:

该示例的性能提升令人信服(因此,我接受了这个答案)。但是,我将方法 3(移动到另一个环境)应用于我的设置并循环 2000 个 API 调用,并将其与方法 1(循环内的rbind)进行比较。结果数据的维度是 146436 行和 3 列。方法 1 耗时 718 秒,方法 3 耗时 703 秒。虽然它更快,但这是令人惊讶的边际。我不确定发生了什么事。最终,API 调用的延迟很可能是瓶颈。 另一种可能性是数据的维度太小,现在无法注意到实质性差异。 我并不完全感到惊讶,尽管我希望会更好。但是我们这里的例子是 10,000 个循环,数据量很小,它只增加了 1.39 秒。我知道 R 3.2 通过花费更少的时间来确定它是什么类型的对象以及调用什么方法,从而更快地打印对象。也许这些相同的优化也适用于这个过程?在任何情况下,在这一点上,您加快速度的最大希望似乎是将您的 for 循环转换为一个函数并通过 parallel::parLapply 运行它。 感谢您的建议。考虑到我们示例的结果,我也希望做得更好。我尝试运行相同的代码,但没有 rbind 行,所以它只是在每次迭代中提取数据,做一些事情,但不存储它。事实证明,这与将其存储在列表或临时环境中一样快。因此,我得出结论,对于我的应用程序的这种数据规模(2000 次调用),rbind 行不是瓶颈。考虑到这里的结果,这非常令人惊讶。当我扩大规模时,也许会有更明显的差异。 另外,尝试了不同大小的循环,它似乎增加接近线性。这没有明确的证据,当循环大小更大时,这可能会改变,但它提供了一些见解,即数据的存储不是瓶颈。

以上是关于R:在没有预分配的情况下动态更新矩阵时的效率问题的主要内容,如果未能解决你的问题,请参考以下文章

hbase Normalizer解决预分区错误,在不动数据的情况下完美解决热点问题

如何在没有额外分配的情况下从 OpenCV 矩阵中获取图像数据?

为动态数据结构预分配内存

预编码

在没有动态分配的情况下在构造时组合对象时避免数据的多个副本

如何在不缩小r闪亮中的矩阵大小的情况下向输入矩阵添加列?