如何防止嵌套的 foreach 循环使用 R 中所有内核的 100% CPU?
How to prevent nested foreach loop using 100% CPU of all cores in R?
我是运行一个嵌套的3层foreach循环,但无法阻止代码100%占用远程服务器(Linux,centOS,物理核心= 14,逻辑核心= 56 ).我使用的框架是:
Library(doParallel)
doParallel::registerDoParallel(20)
outRes <- foreach1(I = seq1, …) %:%
foreach2(j = seq2, …) %dopar% {
innerRes <- foreach3(k = seq3, …)
}
我遇到了三个问题。
- 对于嵌套的foreach 循环,是否将已注册的后端传递给每个foreach 循环并实际产生20*3 = 60 个工人?
- 工人数量与CPU效用百分比之间的数学关系是什么?
- 在我的真实案例中,foreach1 和foreach2 是小进程,而foreach3 是大进程。这就造成了一个问题,就是worker大部分时间都在闲着等待,导致worker的浪费。有解决办法吗?
PS: 附上可重现的代码示例。
library(mlbench)
data("Sonar")
str(Sonar)
table(Sonar$Class)
seed <- 1234
# for cross validation
number_outCV <- 10
repeats_outCV <- 10
number_innerCV <- 10
repeats_innerCV <- 10
# list of numbers of features to model
featureSeq <- c(10, 30, 50)
# for LASSO training
lambda <- exp(seq(-7, 0, 1))
alpha <- 1
dataList <- list(data1 = Sonar, data2 = Sonar, data3 = Sonar, data4 = Sonar, data5 = Sonar, data6 = Sonar)
# library(doMC)
# doMC::registerDoMC(cores = 20)
library(doParallel)
doParallel::registerDoParallel(20)
nestedCV <- foreach::foreach(clust = 1:length(dataList), .combine = "c", .verbose = TRUE) %:%
foreach::foreach(outCV = 1:(number_outCV*repeats_outCV), .combine = "c", .verbose = TRUE) %dopar% {
# prepare data
dataset <- dataList[[clust]]
table(dataset$Class)
# split data into model developing and testing data in the outCV: repeated 10-fold CV
set.seed(seed)
ResampIndex <- caret::createMultiFolds(y = dataset$Class, k = number_outCV, times = repeats_outCV)
developIndex <- ResampIndex[[outCV]]
developX <- dataset[developIndex, !colnames(dataset) %in% c("Class")]
developY <- dataset$Class[developIndex]
testX <- dataset[-developIndex, !colnames(dataset) %in% c("Class")]
testY <- dataset$Class[-developIndex]
# get a pool of all the features
features_all <- colnames(developX)
# training model with inner repeated 10-fold CV
# foreach for nfeature search
nfeatureRes <- foreach::foreach(featNumIndex = seq(along = featureSeq), .combine = "c", .verbose = TRUE) %dopar% {
nfeature <- featureSeq[featNumIndex]
selectedFeatures <- features_all[1:nfeature]
# train LASSO
lassoCtrl <- trainControl(method = "repeatedCV",
number = number_innerCV,
repeats = repeats_innerCV,
verboseIter = TRUE, returnResamp = "all", savePredictions = "all",
classProbs = TRUE, summaryFunction = twoClassSummary)
lassofit.cv <- train(x = developX[, selectedFeatures],
y = developY,
method = "glmnet",
metric = "ROC",
trControl = lassoCtrl,
tuneGrid = expand.grid(lambda = lambda, alpha = alpha),
preProcess = c("center", "scale"))
AUC.test <- pROC::auc(response = testY, predictor = predict(lassofit.cv, newdata = testX[, selectedFeatures], type = "prob")[[2]])
performance <- data.frame(Class = clust, outCV = outCV, nfeature = nfeature, AUC.cv = max(lassofit.cv$results$ROC), AUC.test = as.numeric(AUC.test))
}
# end of nfeature search foreach loop
nfeatureRes
}
# end of outCV foreach loop as well as the dataList foreach loop
foreach::registerDoSEQ()
我不知道这是否可行,但也许您可以尝试使用 "nice" 命令通过 运行 降低服务器优先级(这样,即使它使用 100% CPU, 只会在闲暇时取)?
如果你想确保你的代码只使用一定数量的核心,你可以将你的进程固定到特定的核心。这个叫"CPU affinity" and in R you can use parallel::mcaffinity
来设置,例如:
parallel::mcaffinity(1:20)
允许您的 R 进程仅使用前 20 个内核。无论此进程中使用的其他库如何,这都有效,因为它调用 OS 级资源控制(一些罕见的库产生或与其他进程通信,但您的代码似乎没有使用类似的东西)。
%:%
是嵌套 foreach
循环的正确方法 — foreach
包在其调度中将同时考虑内循环和外循环,并且只执行 registerDoParallel
内循环一次处理多个主体——无论它们是否来自同一个外循环迭代。错误的方法是例如foreach(…) %dopar% { foreach(…) %dopar% { … } }
— 这将一次产生 registerDoParallel
平方的计算(因此,在您的情况下为 400)。 foreach(…) %do% { foreach(…) %dopar% { … } }
(或相反)会更好,但不是最理想的。有关详细信息,请参阅 foreach
的 nesting
vignette。
在你的情况下,最好保持两个外循环(%:%
和 %doPar%
),并将内循环更改为 %do%
.在两个外部循环中,您仍然有相当多的迭代总数来填充 20 个核心,并且常见的规则是,如果可能的话,并行化外部循环比内部循环更好。
通过许多实验,我猜这就是 foreach()
可能分叉工人的方式:
如果使用嵌套的 foreach(例如 foreach() %:% foreach() %dopar% {}
):分叉的工作人员(共享存储的逻辑 CPU 内核)将是 foreach()
乘法之前注册的内核foreach()
次。例如:
registerDoMc(cores = 10)
foreach() %:% foreach() %:% foreach() %dopar% {} # 10x3 = 30 workers will be finally forked in the following example.
如果一个 foreach()
嵌套在另一个 foreach()
中而不使用 %:%
,则派生的工人(逻辑 CUP 核心)将是从 %:%
部分乘以独立的嵌套部分。例如:
registerDoMc(cores = 10)
foreach() %:% foreach() %dopar% { foreach()} # (10+10)x10 = 200 workers will the finally forked.
如有不妥欢迎指正
我是运行一个嵌套的3层foreach循环,但无法阻止代码100%占用远程服务器(Linux,centOS,物理核心= 14,逻辑核心= 56 ).我使用的框架是:
Library(doParallel)
doParallel::registerDoParallel(20)
outRes <- foreach1(I = seq1, …) %:%
foreach2(j = seq2, …) %dopar% {
innerRes <- foreach3(k = seq3, …)
}
我遇到了三个问题。
- 对于嵌套的foreach 循环,是否将已注册的后端传递给每个foreach 循环并实际产生20*3 = 60 个工人?
- 工人数量与CPU效用百分比之间的数学关系是什么?
- 在我的真实案例中,foreach1 和foreach2 是小进程,而foreach3 是大进程。这就造成了一个问题,就是worker大部分时间都在闲着等待,导致worker的浪费。有解决办法吗?
PS: 附上可重现的代码示例。
library(mlbench)
data("Sonar")
str(Sonar)
table(Sonar$Class)
seed <- 1234
# for cross validation
number_outCV <- 10
repeats_outCV <- 10
number_innerCV <- 10
repeats_innerCV <- 10
# list of numbers of features to model
featureSeq <- c(10, 30, 50)
# for LASSO training
lambda <- exp(seq(-7, 0, 1))
alpha <- 1
dataList <- list(data1 = Sonar, data2 = Sonar, data3 = Sonar, data4 = Sonar, data5 = Sonar, data6 = Sonar)
# library(doMC)
# doMC::registerDoMC(cores = 20)
library(doParallel)
doParallel::registerDoParallel(20)
nestedCV <- foreach::foreach(clust = 1:length(dataList), .combine = "c", .verbose = TRUE) %:%
foreach::foreach(outCV = 1:(number_outCV*repeats_outCV), .combine = "c", .verbose = TRUE) %dopar% {
# prepare data
dataset <- dataList[[clust]]
table(dataset$Class)
# split data into model developing and testing data in the outCV: repeated 10-fold CV
set.seed(seed)
ResampIndex <- caret::createMultiFolds(y = dataset$Class, k = number_outCV, times = repeats_outCV)
developIndex <- ResampIndex[[outCV]]
developX <- dataset[developIndex, !colnames(dataset) %in% c("Class")]
developY <- dataset$Class[developIndex]
testX <- dataset[-developIndex, !colnames(dataset) %in% c("Class")]
testY <- dataset$Class[-developIndex]
# get a pool of all the features
features_all <- colnames(developX)
# training model with inner repeated 10-fold CV
# foreach for nfeature search
nfeatureRes <- foreach::foreach(featNumIndex = seq(along = featureSeq), .combine = "c", .verbose = TRUE) %dopar% {
nfeature <- featureSeq[featNumIndex]
selectedFeatures <- features_all[1:nfeature]
# train LASSO
lassoCtrl <- trainControl(method = "repeatedCV",
number = number_innerCV,
repeats = repeats_innerCV,
verboseIter = TRUE, returnResamp = "all", savePredictions = "all",
classProbs = TRUE, summaryFunction = twoClassSummary)
lassofit.cv <- train(x = developX[, selectedFeatures],
y = developY,
method = "glmnet",
metric = "ROC",
trControl = lassoCtrl,
tuneGrid = expand.grid(lambda = lambda, alpha = alpha),
preProcess = c("center", "scale"))
AUC.test <- pROC::auc(response = testY, predictor = predict(lassofit.cv, newdata = testX[, selectedFeatures], type = "prob")[[2]])
performance <- data.frame(Class = clust, outCV = outCV, nfeature = nfeature, AUC.cv = max(lassofit.cv$results$ROC), AUC.test = as.numeric(AUC.test))
}
# end of nfeature search foreach loop
nfeatureRes
}
# end of outCV foreach loop as well as the dataList foreach loop
foreach::registerDoSEQ()
我不知道这是否可行,但也许您可以尝试使用 "nice" 命令通过 运行 降低服务器优先级(这样,即使它使用 100% CPU, 只会在闲暇时取)?
如果你想确保你的代码只使用一定数量的核心,你可以将你的进程固定到特定的核心。这个叫"CPU affinity" and in R you can use parallel::mcaffinity
来设置,例如:
parallel::mcaffinity(1:20)
允许您的 R 进程仅使用前 20 个内核。无论此进程中使用的其他库如何,这都有效,因为它调用 OS 级资源控制(一些罕见的库产生或与其他进程通信,但您的代码似乎没有使用类似的东西)。
%:%
是嵌套 foreach
循环的正确方法 — foreach
包在其调度中将同时考虑内循环和外循环,并且只执行 registerDoParallel
内循环一次处理多个主体——无论它们是否来自同一个外循环迭代。错误的方法是例如foreach(…) %dopar% { foreach(…) %dopar% { … } }
— 这将一次产生 registerDoParallel
平方的计算(因此,在您的情况下为 400)。 foreach(…) %do% { foreach(…) %dopar% { … } }
(或相反)会更好,但不是最理想的。有关详细信息,请参阅 foreach
的 nesting
vignette。
在你的情况下,最好保持两个外循环(%:%
和 %doPar%
),并将内循环更改为 %do%
.在两个外部循环中,您仍然有相当多的迭代总数来填充 20 个核心,并且常见的规则是,如果可能的话,并行化外部循环比内部循环更好。
通过许多实验,我猜这就是 foreach()
可能分叉工人的方式:
如果使用嵌套的 foreach(例如
foreach() %:% foreach() %dopar% {}
):分叉的工作人员(共享存储的逻辑 CPU 内核)将是foreach()
乘法之前注册的内核foreach()
次。例如:registerDoMc(cores = 10) foreach() %:% foreach() %:% foreach() %dopar% {} # 10x3 = 30 workers will be finally forked in the following example.
如果一个
foreach()
嵌套在另一个foreach()
中而不使用%:%
,则派生的工人(逻辑 CUP 核心)将是从%:%
部分乘以独立的嵌套部分。例如:registerDoMc(cores = 10) foreach() %:% foreach() %dopar% { foreach()} # (10+10)x10 = 200 workers will the finally forked.
如有不妥欢迎指正