由于内存(和速度)问题,我希望在data.table内进行一些计算,而不是在data.table外进行。
以下代码有100.000行,但我正在处理4000万行。
library(tictoc)
library(data.table) # version 1.11.8
library(purrr)
library(furrr)
plan(multiprocess)
veryfing_function <- function(vec1, vec2){
vector <- as.vector(outer(vec1, vec2, paste0))
split(vector, ceiling(seq_along(vector)/length(vec1)))
}
dt <- data.table(letters = replicate(1e6, sample(letters[1:5], 3, TRUE), simplify = FALSE),
numbers = replicate(1e6, sample(letters[6:10], 3, TRUE), simplify = FALSE))
tic()
result1 <- future_map2(dt$letters, dt$numbers, veryfing_function)
toc()
tic()
result2 <- mapply(veryfing_function, dt$letters, dt$numbers, SIMPLIFY = FALSE)
toc()
tic()
dt[, result := future_map2(letters, numbers, veryfing_function)]
toc()
tic()
dt[, result2 := mapply(veryfing_function, letters, numbers, SIMPLIFY = FALSE)]
toc()
所有变体的输出都是相同的,并且符合预期。基准是:
26秒72秒38秒105秒,所以我看不到使用data.table内的函数或使用mapply的优势。
我主要关心的是内存,future_map2解决方案无法解决该问题。
我现在正在使用Windows,所以我希望找到除mclapply以外的其他速度解决方案,可能是一些data.table技巧,我没有看到(列表不支持键)
这确实是有关内存和数据存储类型的问题。我所有的讨论将针对100,000个数据元素,以便不会陷入困境。
让我们检查一个长度为100,000的向量与包含100,000个独立元素的列表的比较。
object.size(rep(1L, 1E5)) #400048 bytes object.size(replicate(1E5, 1, simplify = F)) #6400048 bytes
通过将数据存储在不同的位置,我们从0.4 MB增长到6.4 MB!将其应用到函数Map(veryfing_function, ...)
和仅1E5元素时:
dt <- data.table(letters = replicate(1e5, sample(letters[1:5], 3, TRUE), simplify = FALSE), numbers = replicate(1e5, sample(letters[6:10], 3, TRUE), simplify = FALSE)) tic() result2 <- Map(veryfing_function, dt[['letters']], dt[['numbers']]) toc() # 11.93 sec elapsed object.size(result2) # 109,769,872 bytes #example return: [[1000]] [[1000]]$`1` [1] "cg" "bg" "cg" [[1000]]$`2` [1] "ch" "bh" "ch" [[1000]]$`3` [1] "ch" "bh" "ch"
我们可以对您的函数做一个简单的修改,以返回未命名列表而不是拆分,并且由于split()
显示命名列表而节省了一点内存,我认为我们不需要这个名称:
verifying_function2 <- function(vec1, vec2) { vector <- outer(vec1, vec2, paste0) #not as.vector lapply(seq_len(ncol(vector)), function(i) vector[, i]) #no need to split, just return a list } tic() result2_mod <- Map(verifying_function2, dt[['letters']], dt[['numbers']]) toc() # 2.86 sec elapsed object.size(result2_mod) # 73,769,872 bytes #example_output [[1000]] [[1000]][[1]] [1] "cg" "bg" "cg" [[1000]][[2]] [1] "ch" "bh" "ch" [[1000]][[3]] [1] "ch" "bh" "ch"
下一步是为什么要完全返回列表列表。我lapply()
在修改后的函数中使用的只是获得您的输出。松开该lapply()
列表会代替矩阵列表,我认为这样会有所帮助:
tic() result2_mod2 <- Map(function(x,y) outer(x, y, paste0), dt[['letters']], dt[['numbers']]) toc() # 1.66 sec elapsed object.size(result2_mod2) # 68,570,336 bytes #example output: [[1000]] [,1] [,2] [,3] [1,] "cg" "ch" "ch" [2,] "bg" "bh" "bh" [3,] "cg" "ch" "ch"
逻辑上的最后一步是只返回一个矩阵。请注意,在整个过程中,我们一直在与mapply(..., simplify = F)
等同于的简化进行斗争Map()
。
tic() result2_mod3 <- mapply(function(x,y) outer(x, y, paste0), dt[['letters']], dt[['numbers']]) toc() # 1.3 sec elapsed object.size(result2_mod3) # 7,201,616 bytes
如果需要某种尺寸,可以将大矩阵转换为3D数组:
tic() result2_mod3_arr <- array(as.vector(result2_mod3), dim = c(3,3,1E5)) toc() # 0.02 sec elapsed result2_mod3_arr[,,1000] [,1] [,2] [,3] [1,] "cg" "ch" "ch" [2,] "bg" "bh" "bh" [3,] "cg" "ch" "ch" object.size(result2_mod3_arr) # 7,201,624 bytes
我还查看了@marbel的答案-速度更快,并且仅占用略多的内存。通过将初始dt
列表尽快转换为其他内容,我的方法可能会受益。
tic() dt1 = as.data.table(do.call(rbind, dt[['letters']])) dt2 = as.data.table(do.call(rbind, dt[['numbers']])) res = data.table() combs = expand.grid(names(dt1), names(dt2), stringsAsFactors=FALSE) set(res, j=paste0(combs[,1], combs[,2]), value=paste0( dt1[, get(combs[,1])], dt2[, get(combs[,2])] ) ) toc() # 0.14 sec elapsed object.size(res) # 7,215,384 bytes
tl; dr-将您的对象转换为矩阵或data.frame,以使其更易于存储。同样有意义的是data.table
,您的函数版本需要更长的时间-可能比直接应用需要更多的开销mapply()
。