42

parallel分析の実行に必要な時間を短縮するために、パッケージをいつ使用するかを決定しようとしています。私がする必要があることの1つは、行数が異なる2つのデータフレーム内の変数を比較する行列を作成することです。StackOverflowで効率的に行う方法について質問し、ブログにテストについて書きました。私は最善のアプローチに慣れているので、並行して実行することでプロセスをスピードアップしたいと思いました。以下の結果は、8GBのRAMを搭載した2GHzのi7Macに基づいています。parallelパッケージ、parSapply特に関数が、関数を使用するよりも悪いことに驚いていapplyます。これを複製するコードは以下のとおりです。現在、作成した2つの列のうち1つだけを使用していますが、最終的には両方を使用したいと考えています。

実行時間
(出典:bryer.org

require(parallel)
require(ggplot2)
require(reshape2)
set.seed(2112)
results <- list()
sizes <- seq(1000, 30000, by=5000)
pb <- txtProgressBar(min=0, max=length(sizes), style=3)
for(cnt in 1:length(sizes)) {
    i <- sizes[cnt]
    df1 <- data.frame(row.names=1:i, 
                      var1=sample(c(TRUE,FALSE), i, replace=TRUE), 
                      var2=sample(1:10, i, replace=TRUE) )
    df2 <- data.frame(row.names=(i + 1):(i + i), 
                      var1=sample(c(TRUE,FALSE), i, replace=TRUE),
                      var2=sample(1:10, i, replace=TRUE))
    tm1 <- system.time({
        df6 <- sapply(df2$var1, FUN=function(x) { x == df1$var1 })
        dimnames(df6) <- list(row.names(df1), row.names(df2))
    })
    rm(df6)
    tm2 <- system.time({
        cl <- makeCluster(getOption('cl.cores', detectCores()))
        tm3 <- system.time({
            df7 <- parSapply(cl, df1$var1, FUN=function(x, df2) { x == df2$var1 }, df2=df2)
            dimnames(df7) <- list(row.names(df1), row.names(df2))
        })
        stopCluster(cl)
    })
    rm(df7)
    results[[cnt]] <- c(apply=tm1, parallel.total=tm2, parallel.exec=tm3)
    setTxtProgressBar(pb, cnt)
}

toplot <- as.data.frame(results)[,c('apply.user.self','parallel.total.user.self',
                          'parallel.exec.user.self')]
toplot$size <- sizes
toplot <- melt(toplot, id='size')

ggplot(toplot, aes(x=size, y=value, colour=variable)) + geom_line() + 
    xlab('Vector Size') + ylab('Time (seconds)')
4

3 に答える 3

29

ジョブを並行して実行すると、オーバーヘッドが発生します。ワーカーノードで実行するジョブにかなりの時間がかかる場合にのみ、並列化によって全体的なパフォーマンスが向上します。個々のジョブに数ミリ秒しかかからない場合、ジョブを絶えず起動するオーバーヘッドにより、全体的なパフォーマンスが低下します。秘訣は、ジョブが十分に長くなるように、たとえば少なくとも数秒になるように、ノード間で作業を分割することです。これを使用して、6つのFortranモデルを同時に実行すると大きな効果がありましたが、これらの個々のモデルの実行には数時間かかり、オーバーヘッドの影響はほとんどなくなりました。

私はあなたの例を実行していないことに注意してください、しかし私が上で説明した状況は、並列化が連続して実行するよりも長くかかる場合にしばしば問題になります。

于 2013-01-30T22:03:17.513 に答える
23

これらの違いは、1)通信のオーバーヘッド(特にノード間で実行する場合)と2)パフォーマンスのオーバーヘッド(たとえば、並列化を開始する場合に比べてジョブがそれほど集中的でない場合)に起因する可能性があります。通常、並列化するタスクにそれほど時間がかからない場合は、ほとんどの場合、並列化はあまり効果がないことがわかります(これは、巨大なデータセットで非常に目立ちます。

これはあなたのベンチマークに直接答えないかもしれませんが、私はこれがかなり単純であり、関連していることができることを願っています。例として、ここでは、一意の列エントリと列にいくつかの値をdata.frame持つ1e6行を使用してを作成します。そして、並列化を使用する場合と使用しない場合を使用して実行します。1e4groupvalplyrparalleldoMC

df <- data.frame(group = as.factor(sample(1:1e4, 1e6, replace = T)), 
                 val = sample(1:10, 1e6, replace = T))
> head(df)
  group val
# 1  8498   8
# 2  5253   6
# 3  1495   1
# 4  7362   9
# 5  2344   6
# 6  5602   9

> dim(df)
# [1] 1000000       2

require(plyr)
require(doMC)
registerDoMC(20) # 20 processors

# parallelisation using doMC + plyr 
P.PLYR <- function() {
    o1 <- ddply(df, .(group), function(x) sum(x$val), .parallel = TRUE)
}

# no parallelisation
PLYR <- function() {
    o2 <- ddply(df, .(group), function(x) sum(x$val), .parallel = FALSE)
}

require(rbenchmark)
benchmark(P.PLYR(), PLYR(), replications = 2, order = "elapsed")

      test replications elapsed relative user.self sys.self user.child sys.child
2   PLYR()            2   8.925    1.000     8.865    0.068      0.000     0.000
1 P.PLYR()            2  30.637    3.433    15.841   13.945      8.944    38.858

ご覧のとおり、並列バージョンのplyr実行は3.5倍遅くなります

さて、同じものを使用しましょうdata.frame。ただし、計算する代わりにsum、もう少し要求の厳しい関数を作成しましょう。たとえば、median(.) * median(rnorm(1e4)((意味がない、はい):

潮流が変化し始めていることがわかります。

# parallelisation using doMC + plyr 
P.PLYR <- function() {
    o1 <- ddply(df, .(group), function(x) 
      median(x$val) * median(rnorm(1e4)), .parallel = TRUE)
}

# no parallelisation
PLYR <- function() {
    o2 <- ddply(df, .(group), function(x) 
         median(x$val) * median(rnorm(1e4)), .parallel = FALSE)
}

> benchmark(P.PLYR(), PLYR(), replications = 2, order = "elapsed")
      test replications elapsed relative user.self sys.self user.child sys.child
1 P.PLYR()            2  41.911    1.000    15.265   15.369    141.585    34.254
2   PLYR()            2  73.417    1.752    73.372    0.052      0.000     0.000

ここで、並列バージョンは非並列バージョンよりも1.752 times 高速です。

編集: @Paulのコメントに続いて、を使用してわずかな遅延を実装しましたSys.sleep()。もちろん、結果は明らかです。ただし、完全を期すために、20*2のdata.frameでの結果を次に示します。

df <- data.frame(group=sample(letters[1:5], 20, replace=T), val=sample(20))

# parallelisation using doMC + plyr 
P.PLYR <- function() {
    o1 <- ddply(df, .(group), function(x) {
    Sys.sleep(2)
    median(x$val)
    }, .parallel = TRUE)
}

# no parallelisation
PLYR <- function() {
    o2 <- ddply(df, .(group), function(x) {
        Sys.sleep(2)
        median(x$val)
    }, .parallel = FALSE)
}

> benchmark(P.PLYR(), PLYR(), replications = 2, order = "elapsed")

#       test replications elapsed relative user.self sys.self user.child sys.child
# 1 P.PLYR()            2   4.116    1.000     0.056    0.056      0.024      0.04
# 2   PLYR()            2  20.050    4.871     0.028    0.000      0.000      0.00

ここでの違いは驚くべきことではありません。

于 2013-01-30T22:06:29.793 に答える
9

なぜ...?に関する@Arunと@PaulHiemestraの議論に完全に同意します。あなたの質問の一部。

ただし、状況に応じてパッケージからいくつかのメリットを享受できるようですparallel(少なくともWindowsに固執していない場合)。考えられる解決策は、高速フォークと共有メモリに依存するmclapplyの代わりにを使用することです。parSapply

  tm2 <- system.time({
    tm3 <- system.time({
     df7 <- matrix(unlist(mclapply(df2$var1, FUN=function(x) {x==df1$var1}, mc.cores=8)), nrow=i)
     dimnames(df7) <- list(row.names(df1), row.names(df2))
    })
  })

もちろん、system.timeここではネストは必要ありません。私の2つのコアで私は得ました:

ここに画像の説明を入力してください

于 2013-01-31T01:36:19.037 に答える