Acenet Siku集群中R并行处理adehabitatHR的内存管理问询
问题描述
背景
使用adehabitatHR包生成野生动物种群的利用分布估计(UDEs),需处理数千个UDEs,通过terra包的vect()函数转换为Shapefile,本地单线程处理代码为:
spatvec_UDEs <- lapply(UDEs, function(x){vect(x)})
核心问题
在Acenet Siku集群上并行执行家域估计算法时,内存占用极高且无法成功保存/导出结果;对SLURM内存映射逻辑、共享/分布式内存的区别及在SLURM中的运行逻辑存在困惑,尝试多种并行方式(parLapply()、mclapply()、slurm_map)及SLURM交互式命令均失败。
已尝试的SLURM交互式命令及问题
BRB1
salloc --time=3:00:0 --ntasks=1 --cpus-per-task=8 --mem-per-cpu=23875M
使用mclapply时出现无限重复的"Selection"界面。
BRB2
salloc --time=3:00:0 --ntasks=1 --cpus-per-task=16 --mem-per-cpu=11937M
耗时4619秒,parLapply无法识别有效集群。
BRB3
salloc --time=3:00:0 --nodes=1 --ntasks=1 --cpus-per-task=16 --mem-per-cpu=11937M
耗时4700秒(比BRB2慢),添加cl=cl参数后parLapply执行失败。
BRB4
salloc --time=3:00:0 --nodes=1 --cpus-per-task=20 --mem-per-cpu=11937M
耗时4262.420秒(最快),但parLapply执行失败。
BRB5
salloc --time=3:00:0 --nodes=1 --cpus-per-task=20 --mem-per-cpu=18500M
耗时4565.692秒,增加内存未提升速度;尝试slurm_map提交作业失败:
sbatch: error: Batch job submission failed: Requested partition configuration not available now.
最小可复现示例(保存为Rds)
SLURM命令
salloc --time=3:00:0 --nodes=2 --cpus-per-task=20 --mem-per-cpu=18500M
R代码
library('adehabitatHR') library('foreach') data(puechcirc) pc1 <- puechcirc[1] pc2 <- puechcirc[2] pc3 <- puechcirc[3] Traj_li <- vector("list", 3) Traj_li[[1]] <- pc1 Traj_li[[2]] <- pc2 Traj_li[[3]] <- pc3 DLik <- c(2.1, 2.2, 4) system.time({ BRBs_PC <- foreach(i = 1:length(Traj_li), .combine = c, .packages = c("adehabitatHR","adehabitatLT", "terra")) %dopar% { BRB(Traj_li[[i]][1], D = DLik[i], Tmax = 1500*60, Lmin = 2, hmin = 20, type = "UD", grid = 4000) } }) thenames <- c("pn1", "pn2", "pn3") WRds <- function (x) {saveRDS(x, paste0(here("BRB_UDs"), "/", thenames,".Rds")) } BRBs_BM_WIv <- slurm_map(BRBs_BM_WI, f=WRds, nodes=2, cpus_per_node = 1)
报错信息
sbatch: error: Batch job submission failed: Requested partition configuration not available now Error in strsplit(sys_out, " ")[[1]] : subscript out of bounds In addition: Warning message: In system("sbatch submit.sh", intern = TRUE) : running command 'sbatch submit.sh' had status 1
其他尝试的并行代码
# mclapply尝试 mclapply(X = BRBs_BM_WIv, FUN=WV, mc.cores = n.cores) # parLapply尝试1 my.cluster <- parallel::makeCluster( n.cores, type = "FORK" ) doParallel::registerDoParallel(cl = my.cluster) WVec <- function (x) {vect(x)} BRBs_BM_WIv <- parLapply(cl=my.cluster, BRBs_BM_WI, FUN = MVec) stopCluster(my.cluster) # parLapply尝试2 my.cluster <- parallel::makeCluster( n.cores, type = "FORK" ) doParallel::registerDoParallel(cl = my.cluster) WV <- function (x) {writeVector(x, paste0(here("BRB_UDs"), "/", thenames,".shp"), overwrite=TRUE)} parLapply(cl=my.cluster, X = BRBs_BM_WIv, FUN = MV) stopCluster(my.cluster)
诉求
- 阐明高性能集群并行方法的内存管理逻辑(尤其是SLURM内存映射)
- 提供
parLapply()、mclapply()或future::plan(multicore)的可行实现示例 - 明确共享内存与分布式内存的区别及在SLURM中的运行逻辑
解决方案
一、核心概念梳理
共享内存 vs 分布式内存
- 共享内存:所有并行进程在同一节点上,共享同一物理内存空间。适合单节点多核并行(如
mclapply、parallel::makeCluster(type="FORK")),进程可直接读取父进程的数据,无需序列化,但总内存受单节点限制。 - 分布式内存:进程分布在多个节点,每个节点有独立内存空间。适合跨节点并行(如
parallel::makeCluster(type="PSOCK")、slurm_map),进程间需通过网络传输数据,序列化开销大,但可利用集群多节点的总内存。
SLURM内存参数逻辑
--mem-per-cpu=<value>:为每个CPU核心分配指定内存,总内存=核心数×该值。--mem=<value>:为整个任务分配固定内存(推荐使用,避免因核心数计算错误导致内存不足)。- 单节点任务:用
--nodes=1+--cpus-per-task=<n>,指定单节点用n个核心,内存用--mem设置单节点总可用内存。 - 多节点任务:用
--nodes=<m>+--ntasks-per-node=<k>,每个节点跑k个任务,总任务数=m×k。
二、可行并行实现示例
1. 单节点共享内存:mclapply(适合内存需求高、单节点能容纳的场景)
SLURM交互式命令
# 申请单节点,20核心,总内存240G(根据实际需求调整) salloc --time=3:00:0 --nodes=1 --cpus-per-task=20 --mem=240G
R代码
library(adehabitatHR) library(terra) library(parallel) # 加载数据(替换为你的UDEs列表) data(puechcirc) Traj_li <- list(puechcirc[1], puechcirc[2], puechcirc[3]) DLik <- c(2.1, 2.2, 4) # 定义处理函数:计算BRB并转换为vect,直接保存结果(避免内存累积) process_ude <- function(traj, d, name) { # 计算UDE ude <- BRB(traj[1], D = d, Tmax = 1500*60, Lmin = 2, hmin = 20, type = "UD", grid = 4000) # 转换为vect spatvec <- vect(ude) # 保存为Shapefile(直接写入磁盘,不返回大对象) writeVector(spatvec, paste0("BRB_UDs/", name, ".shp"), overwrite = TRUE) # 返回标识,确认完成 return(paste(name, "processed")) } # 生成文件名列表 thenames <- paste0("pn", 1:length(Traj_li)) # 并行执行,mc.cores匹配SLURM申请的核心数 results <- mclapply(1:length(Traj_li), function(i) { process_ude(Traj_li[[i]], DLik[i], thenames[i]) }, mc.cores = 20) # 查看结果 print(unlist(results))
2. 单节点共享内存:parLapply(FORK模式)
SLURM命令同mclapply示例
R代码
library(adehabitatHR) library(terra) library(parallel) # 加载数据 data(puechcirc) Traj_li <- list(puechcirc[1], puechcirc[2], puechcirc[3]) DLik <- c(2.1, 2.2, 4) thenames <- paste0("pn", 1:length(Traj_li)) # 创建FORK集群(共享父进程内存,无需重新加载包/数据) n_cores <- 20 my_cluster <- makeCluster(n_cores, type = "FORK") # 定义处理函数 process_ude <- function(i) { ude <- BRB(Traj_li[[i]][1], D = DLik[i], Tmax = 1500*60, Lmin = 2, hmin = 20, type = "UD", grid = 4000) spatvec <- vect(ude) writeVector(spatvec, paste0("BRB_UDs/", thenames[i], ".shp"), overwrite = TRUE) return(paste(thenames[i], "processed")) } # 并行执行 results <- parLapply(my_cluster, 1:length(Traj_li), process_ude) # 关闭集群 stopCluster(my_cluster) print(unlist(results))
3. 多节点分布式内存:future + future.apply(适合超大规模任务)
SLURM交互式命令
# 申请2节点,每个节点10核心,总内存120G/节点 salloc --time=6:00:0 --nodes=2 --ntasks-per-node=10 --mem=120G
R代码
library(adehabitatHR) library(terra) library(future) library(future.apply) # 设置并行计划:multisession(跨节点PSOCK集群) plan(multisession, workers = 20) # 2节点×10核心=20 workers # 加载数据 data(puechcirc) Traj_li <- list(puechcirc[1], puechcirc[2], puechcirc[3]) DLik <- c(2.1, 2.2, 4) thenames <- paste0("pn", 1:length(Traj_li)) # 定义处理函数(需包含包加载,因为每个worker是独立进程) process_ude <- function(i) { library(adehabitatHR) library(terra) ude <- BRB(Traj_li[[i]][1], D = DLik[i], Tmax = 1500*60, Lmin = 2, hmin = 20, type = "UD", grid = 4000) spatvec <- vect(ude) writeVector(spatvec, paste0("BRB_UDs/", thenames[i], ".shp"), overwrite = TRUE) return(paste(thenames[i], "processed")) } # 并行执行 results <- future_lapply(1:length(Traj_li), process_ude) print(unlist(results))
三、内存优化关键技巧
- 避免内存累积:不要将所有UDEs/vect对象保存在内存中,处理一个就写入磁盘,只返回状态标识。
- 合理选择并行模式:单节点能容纳的任务优先用共享内存(FORK/mclapply),避免序列化开销;跨节点任务用分布式内存(PSOCK/future)。
- SLURM内存参数正确设置:优先用
--mem指定单节点总内存,而非--mem-per-cpu,避免因核心数计算导致内存分配不足或浪费。 - 避免嵌套并行:
adehabitatHR的部分函数可能自带OpenMP并行,需关闭或设置环境变量Sys.setenv(OMP_NUM_THREADS=1),避免与R的并行框架冲突。
内容的提问来源于stack exchange,提问作者Mark Thompson
相关产品推荐
相关产品推荐

