20

私は大きなコードを持っており、集約ステップが現在速度のボトルネックになっています。

私のコードでは、データのグループ化ステップを高速化して高速化したいと考えています。私のデータの SNOTE (単純で重要な例) は次のようになります。

library(data.table)
a = sample(1:10000000, 50000000, replace = TRUE)
b = sample(c("3m","2m2d2m","3m2d1i3s2d","5m","4m","9m","1m"), 50000000, replace = TRUE)
d = sample(c("3m","2m2d2m","3m2d1i3s2d","5m","4m","9m","1m"), 50000000, replace = TRUE)
e = a
dt = data.table(a = a, b = b, d = d, e = e)
system.time(c.dt <- dt[,list(b = paste(b, collapse=""), d = paste(d, collapse=""), e = e[1], by=a)])
   user  system elapsed 
 60.107   3.143  63.534

これは、このような大規模なデータの例では非常に高速ですが、私の場合は、さらに高速化を求めています。私の場合、複数のコアを持っているので、そのような計算能力を使用する方法があるに違いないとほぼ確信しています。

データ型を data.frame または idata.frame オブジェクトに変更することにオープンです (理論的には、idata.frame は data.frames よりも高速であると考えられます)。

私はいくつかの調査を行い、plyr パッケージには役立つ可能性のある並列機能がいくつかあるようですが、私が行おうとしているグループ化のためにそれを行う方法についてはまだ苦労しています。別の SO 投稿では、これらのアイデアのいくつかについて説明しています。foreach関数を使用しているため、この並列化でどれだけ多くのことを達成できるかはまだわかりません. 私の経験では、foreach 関数は何百万もの高速操作には適していません。コア間の通信作業によって並列化作業が遅くなるためです。

4

2 に答える 2

13

で集計を並列化できますdata.tableか? はい。

その価値はありますか?いいえ。これは、前の回答で強調できなかった重要なポイントです。

Matt Dowle がdata.table と並列コンピューティングで説明しているように、操作を並列で実行する場合、配布する前にコピー (「チャンク」) を作成する必要があります。これは物事を遅くします。使用できない場合 (多くの線形回帰を実行している場合などdata.table) は、コア間でタスクを分割する価値があります。しかし、集約ではありません — 少なくともdata.tableが関係している場合は。

要するに (別の方法で証明されるまでは)、を使用して集約し、 を使用しdata.tableて速度が向上する可能性について心配するのをやめてdoMCください。data.tableマルチコアでなくても、アグリゲーションに関しては、他の利用可能なものと比較してすでに非常に高速です!


とを使用してdata.table内部集計を比較して、自分で実行できるいくつかのベンチマークを次に示します。結果が最初にリストされます。byforeachmclapply

#-----------------------------------------------

# TL;DR FINAL RESULTS (Best to Worst)
# 3 replications, N = 10000:
# (1)  0.007 -- data.table using `by`
# (2)  3.548 -- mclapply with rbindlist
# (3)  5.557 -- foreach with rbindlist
# (4)  5.959 -- foreach with .combine = "rbind"
# (5) 14.029 -- lapply

# ----------------------------------------------

library(data.table)

## And used the following to create the dt
N <- 1e4
set.seed(1)
a = sample(1:N, N*2, replace = TRUE)
b = sample(c("3m","2m2d2m","3m2d1i3s2d","5m","4m","9m","1m"), N*2, replace = TRUE)
d = sample(c("3m","2m2d2m","3m2d1i3s2d","5m","4m","9m","1m"), N*2, replace = TRUE)
e = a
dt = data.table(a = a, b = b, d = d, e = e, key="a")
setkey(dt, "a")

# TEST AGGREGATION WITHOUT PARALLELIZATION ---------------------------
## using data.tables `by` to aggregate
round(rowMeans(replicate(3, system.time({
    dt[,list(b = paste(b, collapse=""), d = paste(d, collapse=""), e = e[1], by=a)]
}))), 3)
# [1] 0.007 elapsed for N == 10,000, length(unique(dt[["a"]])) == 8617

## using `lapply`
round(rowMeans(replicate(3, system.time({
    results <- lapply(unique(dt[["a"]]), function(x) {
        dt[.(x), list(b = paste(b, collapse=""), d = paste(d, collapse=""), e = e[1])]
    })
    rbindlist(results)
}))), 3)
# [1] 14.029 elapsed for N == 10,000

# USING `mclapply` FORKING ---------------------------------
## use mclapply
round(rowMeans(replicate(3, system.time({
    results <- mclapply(unique(dt[["a"]]),
    function(x) {
        dt[.(x), list(b = paste(b, collapse=""), d = paste(d, collapse=""), e = e[[1]])]
    }, mc.cores=4)
    rbindlist(results)
}))), 3)
# [1] 3.548 elapsed for N == 10,000


# PARALLELIZATION USING `doMC` PACKAGE ---------------------------------
library(doMC)
mc = 4
registerDoMC(cores=mc)
getDoParWorkers()
# [1] 4

## (option a) by Ricardo Saporta
round(rowMeans(replicate(3, system.time({
    foreach(x=unique(dt[["a"]]), .combine="rbind", .inorder=FALSE) %dopar%
    dt[.(x) ,list(b = paste(b, collapse=""), d = paste(d, collapse=""), e = e[[1]])]
}))), 3)
# [1] 5.959 elapsed for N == 10,000

## (option b) by Ricardo Saporta
round(rowMeans(replicate(3, system.time({
    results <-
      foreach(x=unique(dt[["a"]])) %dopar%
        dt[.(x) ,list(b = paste(b, collapse=""), d = paste(d, collapse=""), e = e[[1]])]
    rbindlist(results)
}))), 3)
# [1] 5.557 elapsed for N == 10,000

registerDoSEQ()
getDoParWorkers()
# [1] 1
于 2015-06-23T17:18:12.147 に答える
8

複数のコアを利用できる場合は、キーを使用して data.table 内の行をすばやくフィルタリングおよびグループ化できるという事実を活用してみませんか。

library(doMC)
registerDoMC(cores=4)


setkey(dt, "a")

finalRowOrderMatters = FALSE # FALSE can be faster
foreach(x=unique(dt[["a"]]), .combine="rbind", .inorder=finalRowOrderMatters) %dopar% 
     dt[.(x) ,list(b = paste(b, collapse=""), d = paste(d, collapse=""), e = e[[1]])]

一意のグループ (つまりlength(unique(a))) の数が比較的少ない場合は、.combine引数を削除し、結果をリストに戻してからrbindlist、結果を呼び出す方が高速であることに注意してください。2 つのコアと 8 GB の RAM でのテストでは、しきい値は約 9,000 の一意の値でした。ベンチマークに使用したものは次のとおりです。

# (otion a)
round(rowMeans(replicate(3, system.time({
# ------- #
  foreach(x=unique(dt[["a"]]), .combine="rbind", .inorder=FALSE) %dopar% 
     dt[.(x) ,list(b = paste(b, collapse=""), d = paste(d, collapse=""), e = e[[1]])]
# ------- #
}))), 3) 
# [1]  1.243 elapsed for N ==  1,000
# [1] 11.540 elapsed for N == 10,000, length(unique(dt[["a"]])) == 8617
# [1] 57.404 elapsed for N == 50,000



# (otion b)
round(rowMeans(replicate(3, system.time({
# ------- #
    results <- 
      foreach(x=unique(dt[["a"]])) %dopar% 
         dt[.(x) ,list(b = paste(b, collapse=""), d = paste(d, collapse=""), e = e[[1]])]
    rbindlist(results)
# ------- #
}))), 3)
# [1]  1.117 elapsed for N ==  1,000
# [1] 10.567 elapsed for N == 10,000, length(unique(dt[["a"]])) == 8617
# [1] 76.613 elapsed for N == 50,000


## And used the following to create the dt
N <- 5e4
set.seed(1)
a = sample(1:N, N*2, replace = TRUE)
b = sample(c("3m","2m2d2m","3m2d1i3s2d","5m","4m","9m","1m"), N*2, replace = TRUE)
d = sample(c("3m","2m2d2m","3m2d1i3s2d","5m","4m","9m","1m"), N*2, replace = TRUE)
e = a
dt = data.table(a = a, b = b, d = d, e = e, key="a")
于 2013-10-06T04:00:55.277 に答える