R並列計算(並列計算)-1- foreach関数を使ってみる

CPUのコア数とスレッド数の意味

 

プロセッサのコア数は、通常、コアとも呼ばれる物理コアの数を指します。デュアルコアは、さまざまなデータを処理するための中心となる演算器である、独立した CPU コアユニット群を 2 つ含むもの、クアッドコアは、独立した CPU コアユニット群を 4 つ含むものです。

通常、1コアは1スレッドに対応しますが、Intelはハイパースレッディング技術を開発し、1コアで2スレッド、7コアで12スレッドの計算を実行できます。スレッド数は論理的な概念であり、仮想 CPU コアの数です。

たとえば、CPU は銀行に相当し、CPU コアは窓口に相当し、スレッドの数はいくつかの窓口を開くのに相当します。窓口や窓口が増えるほど、同時に処理される業務の数が増えます。速度が速いほど。

通常、1 つの窓口に 1 人の窓口が対応しますが、ハイパースレッディング技術により、1 人の窓口が 2 つの窓口を管理するのと同等となり、左手と右手を使って 2 つの窓口の業務を同時に処理できるため、業務効率が大幅に向上します。コアの効率が向上し、ビジネスの処理速度が向上します。

なぜ R は並列コンピューティングを使用する必要があるのでしょうか?

メモリの観点から見ると、R はインメモリ コンピューティング モデル (In-Memory) を使用しており、処理されたデータはストレージ (RAM) にプリフェッチされる必要があります。利点は高い計算効率と高速性ですが、欠点は処理できる問題の規模が非常に限られていることです (RAM のサイズ未満)。

一方、R にはデフォルトで単一スレッドの作業しかなく、他のスレッドをアイドル状態にしておくのは明らかに無駄です。

たとえば、R が 260 コアの CPU で実行されている場合、シングルスレッドの R プログラムは最大でも 1/260 の計算能力しか使用できないため、他の 259/260 の計算コアが無駄になります。現在のコンピューターはすべて 4 ~ 16 コアを備えており、高構成のコンピューターでも低構成のコンピューターでも、並列コンピューティングを使用しなければ、各コンピューターの実行速度に大きな違いはありません。

並列コンピューティングとは、複数のスレッドまたはすべてのスレッドを同時に動作させることです。

R並列コンピューティング

コンピューターの物理コアスレッドの数を表示する

detectCores(logical = F)#查看电脑的物理核数
#18

install.packages("future")
library(future)
availableCores()#查看电脑可用的线程数
#36

例1

for ループを使用して繰り返しタスクを実行します。各ループで、標準正規分布に従う 100,000 個の乱数を計算し、その平均値を計算します。このプロセスは 30,000 回繰り返されます。コードは以下のように表示されます。

#example 1
timestart <- Sys.time()
x <- numeric()
for (i in 1:30000) {
  x[i] <- mean(rnorm(1e5))
}
timeend <- Sys.time()
runningtime <- timeend - timestart
print(runningtime)
#3.83mins

R で for ループの使用を避けるようにしてください。R で for ループを使用すると、非常に時間がかかります。コードを記述するときは、for ループの使用をできるだけ避ける必要があります。ベクトル化されたプログラミングを検討できます (全体的な考え方は、ベクトル/行列を操作することなので、その中の各要素は同じ操作を実行します)。

1-foreach (非並列計算) の最も単純な使用法

for ループの代わりに %do%+foreach() を使用すると、各ループで標準正規分布から 100,000 個の乱数が計算され、合計 30,000 回のループになります。結果から、計算速度は for ループと同等であることがわかります。関数 foreach はリスト (リスト) を返すことに注意してください。foreach を使用する利点の 1 つは、for ループのように %do% の後の中括弧 {} の間に複数のステートメントを記述できることです。

timestart <- Sys.time()
library(foreach)
x2 <- list()
foreach(i = 1:30000) %do% {
  x2[[i]] <- mean(rnorm(1e5))
}
timeend <- Sys.time()
runningtime <- timeend - timestart
print(runningtime)
#3.91 mins

2foreach並列コンピューティング

並列計算を実行するには foreach を使用します。このとき、並列計算を開始するには、上記の %do% を %dopar% に置き換える必要があります。並列コンピューティングを使用する前に、まず doParallel パッケージをロードし、クラスターを作成して登録する必要があります。18 個の物理コアと 36 個のスレッドを備えたコンピューターを例に挙げます。正規分布乱数の平均値を計算するための前述のコードを計算します。

同じタスクを完了するのにかかる時間はわずか約 19 秒で、単一コアを使用する場合よりもほぼ 10 倍速くなります。foreach のデフォルトの戻り値のデータ型はリストですが、ベクトル形式または行列形式を使用することを好みます。その場合は、「.combine」パラメータを使用して出力データの種類を指定できます。

library(foreach)
library(doParallel)
# 创建一个集群并注册
cl <- makeCluster(18)
registerDoParallel(cl)


# 启动并行计算
time1 <- Sys.time()
x2 <- foreach(i = 1:3e4, .combine = c) %dopar% {
  mean(rnorm(1e5))
}
time2 <- Sys.time()
print(time2-time1)
#19sec in cl <- makeCluster(36)

#21sec in cl <- makeCluster(18)

# 在计算结束后别忘记关闭集群
stopImplicitCluster()
stopCluster(cl)

foreach 関数は、rbind や cbind などの関数を使用して結果を行列形式で出力することもできます。

timestart <- Sys.time()
x4 <- matrix(0,nrow=3e4,ncol=6)
for (i in 1:30000) {
  x4[i,] <- summary(rnorm(1e5))
}
timeend <- Sys.time()
runningtime <- timeend - timestart
print(runningtime)
#6.036685 mins


# 创建一个集群并注册
cl <- makeCluster(36)
registerDoParallel(cl)
# 启动并行计算
timestart <- Sys.time()
x <- foreach(i = 1:3e4, .combine = rbind) %dopar% {
  summary(rnorm(1e5))
}
timeend <- Sys.time()
runningtime <- timeend - timestart
print(runningtime)
stopImplicitCluster()
stopCluster(cl)
# 26.86028 secs

foreach 関数のいくつかの重要なパラメータ

(1).パッケージ

後で作成されるコード%dopar%では、多くの場合、機械学習でよく使用されるランダム フォレスト アルゴリズムなど、サードパーティの R パッケージ.packageを で指定する必要があります。

x <- matrix(runif(500), 100)
y <- gl(2, 50)
rf <- foreach(
  ntree = rep(250, 4),.combine = combine,.packages = 'randomForest') %dopar% {
    randomForest(x, y, ntree = ntree)
  }

(2).エラーアクション

.errorhandlingループ内でエラーが発生した場合の応答メソッドを処理できます。デフォルトは ですstopたとえば、ここではエラーが意図的に報告されていますi=5。コードは次のとおりです。

x <- foreach(i = 1:10, .combine = c,.errorhandling = "stop") %dopar% {
  if(i == 5)
    stop('STOP!')
  i
}
Error in { : task 5 failed - "STOP!"

x
错误: 找不到对象'x'

1 つのエラー報告により、それまでの 4 つのタスクが無駄に実行され、数時間実行されていたタスクが一連のエラー報告によって破壊されたら、キーボードを叩きたくなるでしょう。.errorhandlingこの時点で、エラー報告サイクルをスキップするように変更できますremove。たとえば、

x <- foreach(i = 1:10, .combine = c,.errorhandling = "remove") %dopar% {
  if(i == 5)
    stop('STOP!')
  i
}
x
[1]  1  2  3  4  6  7  8  9 10

5 番目のエラー レポートのサイクルがスキップされたことがわかります。もちろん、エラーが報告されている場所を見つけて解決したいと考えていますが、現時点では次のように変更でき.errorhandlingますpass

x <- foreach(i = 1:7, .combine = rbind, .errorhandling = "pass") %dopar% {
  if(i == 5)
    stop('STOP!')
  c(i, i^2)
}
x
         message call      
result.1 1       1         
result.2 2       4         
result.3 3       9         
result.4 4       16        
result.5 "STOP!" Expression
result.6 6       36        
result.7 7       49 

# 第五次循环的报错信息被记录了下来

4. 環境と変数のスコープに関する注意事項

argumentsR 関数にはパラメーター ( ) と本体 ( )だけでなくbody、独自の環境 ( environment) もあります。R の環境の概念は、ほとんどのユーザーにとって馴染みのないものかもしれません。

x <- 1:3
# 创建一个名为f的函数
f <- function(x){
  k <- 3
  h <- function(){
    x <- sqrt(x)
  }
  print(environment(h))
  return(x + k)
}
# 查看函数f所处的环境
environment(f)
<environment: R_GlobalEnv> 

コードの実行結果からenvironment(f)、関数がf()トップレベルの環境でR_GlobalEnv作成されたことがわかります。関数内にf()新しい関数h()と変数を作成しましたが、 C 言語の概念kでは、変数は関数のローカル変数です。しかし、R では、と は両方ともローカル変数とみなされ、両方ともトップレベルの環境には見えません。関数を実行すると、次の結果が得られます。kf()h()kf()f()

f(x)
<environment: 0x0000021b918e0310>
[1] 4 5 6

printこの関数は、私たちが環境h()にいるのではなく、名前付きの環境 (実際にはメモリ アドレス) にいるということを示します。R_GlobalEnv0x0000021b918e0310

次のように実行するようにコードを変更すると、次のようになります。

x1 <- 1:3
f <- function(x){
  x2 <- 5
  return(h())
}

h <- function(){
  x1 + x2
}
f(x1)
Error in h() : 找不到对象'x2'

エラーが報告されます。このとき、関数はh()トップレベルの環境で定義されているためR_GlobalEnvf()独自の環境で作成されたローカル変数はユーザーには見えません。これがスコープの概念ですx2h()

foreachたとえば、スコープの特性について説明します。

x1 <- 1
x2 <- 2
f <- function(x1) {
  foreach(i = 1:100, .combine = c)  %dopar% {
    x1 + x2 + i
  }
}
f(x1) 
Error in { : task 1 failed - "找不到对象'x2'" 

上に示したように、関数を実行するとf()エラーが報告されます。

その理由は、foreachこれを関数内に記述すると、foreachランタイムはf()その環境 (つまり、関数の環境) 内の必要なすべての変数をエクスポート (エクスポート) しますが、上位レベルの環境 (ここでは、R_GlobalEnv) の変数。このコードでは、x2関数の必須変数ではないため、環境にf()配置されていないため、認識されません。この状況には 2 つの解決策があります。f()foreach

(1)x2記述されるf()パラメータ

x1 <- 1
x2 <- 2
f <- function(x1, x2) {
  foreach(i = 1:3, .combine = c)  %dopar% {
    x1 + x2 + i
  }
}
f(x1, x2) 
[1] 4 5 6

(2)foreach使用するパラメータ.export:

x1 <- 1
x2 <- 2
f <- function(x1) {
  foreach(i = 1:3, .combine = c, .export = 'x2')  %dopar% {
    x1 + x2 + i
  }
}
f(x1) 
[1] 4 5 6

5. 最後に一言

(1) 時間 (計算速度) とスペース (メモリ使用量) のトレードオフ: R の並列計算は大量のメモリを消費するため、適切な CPU コア数を慎重に選択してください (またはコードからメモリ使用量を最適化します)。処理するデータが大きく、メモリが十分でない場合、コンピュータはオフラインで「スタック」します。実行可能な解決策は次のとおりです。

①メモリースティックのマルチインサート

② 1Wデータなどのタスクを分解し、2Kの5つに分割する

(2)十分なメモリがある場合、コア数はそれほど良くありません。モデルを計算するとき、1 コアから 36 コアまでモデルの実行に必要な時間をカウントします (2 ~ 7 列の名前はデータです)ボリューム)。呼び出されるコアの数が物理コアの数の 1.5 倍 (18*1.5=27 コア) を超えると、ハイパースレッディングによってもたらされる可能性はほぼ搾り取られ、並列コンピューティングによってもたらされる速度の向上はそれほど大きくありません。

したがって、ハイパースレッディングをサポートする CPU の場合は、並列コンピューティングの上限として物理コア数の 1.5 倍を選択することをお勧めします。コマンドを使用してコンピューターの物理コア数を表示できますdetectCores(logical = F)

(3) Parallel はしばらく涼しく、特にノート PC の場合は CPU の放熱にも注意する必要があります。オールコア動作では、数分以内に熱放散が悪く、周波数が低下するか、場合によってはコンピューターがクラッシュする可能性があります。

MM.fun <- function(){
  b <- 1+2
  return(b)
}

cfun <- function(c){
test <- 1:10
m <- length(test)
aa <- MM.fun(m)#全局变量和局部变量的区别,这个时候,m是局部变量,没有办法传递给MM.fun
f <- c+aa
return(f)
}

cfun(1)

参考:

CPU コアとスレッドの数の役割は何ですか? CPU コアとスレッドの数は何を意味しますか? CPUコア数とスレッド数の関係と違い - Zhihu (zhihu.com)

R の速度向上: 効率的なコードの作成と並列プログラミング | Rブロガー

[マルチコア Spring] R 言語での並列コンピューティング - Zhihu (zhihu.com)

おすすめ

転載: blog.csdn.net/u011375991/article/details/131272023