2011-07-29 41 views
12

我經常會得到幾個嵌套的foreach循環,有時編寫通用函數(例如,對於程序包)時,沒有明顯要並行化的級別。有什麼方法可以完成下面介紹的模型?並行或按順序執行foreach循環給定條件

foreach(i = 1:I) %if(I < J) `do` else `dopar`% { 
    foreach(j = 1:J) %if(I >= J) `do` else `dopar`% { 
     # Do stuff 
    } 
} 

此外,有一些方法來檢測並行後端被註冊,所以我才能避免不必要的警告消息?這對於在CRAN提交之前檢查軟件包以及不打擾在單核計算機上運行R的用戶都是有用的。

foreach(i=1:I) %if(is.parallel.backend.registered()) `dopar` else `do`% { 
    # Do stuff 
} 

謝謝你的時間。

編輯:非常感謝您對核心和工作人員的所有反饋,因此處理上述示例的最佳方法就是重新考慮整個設置。我更喜歡下面的想法,但它基本上是相同的觀點。當然也可以像Joris建議的那樣使用並行tapply來完成。

ij <- expand.grid(i=1:I, j=1:J) 
foreach(i=ij$I, j=ij$J) %dopar% { 
    myFuction(i, j) 
} 

但是,在我試圖簡化導致這個線程的情況下,我忽略了一些關鍵細節。想象一下,我有兩個函數analysebatch.analyse,並行化的最佳級別可能會有所不同,具體取決於n.replicatesn.time.points的值。

analyse <- function(x, y, n.replicates=1000){ 
    foreach(r = 1:n.replicates) %do% { 
     # Do stuff with x and y 
    } 
} 
batch.analyse <- function(x, y, n.replicates=10, n.time.points=1000){ 
    foreach(tp = 1:time.points) %do% { 
     my.y <- my.func(y, tp) 
     analyse(x, my.y, n.replicates) 
    } 
} 

如果n.time.points > n.replicates是有意義的batch.analyse並行但除此之外,它更有意義的analyse並行。有關如何解決它的任何想法?在analyse中是否有可能檢測到並行化是否已經發生?

回答

8

,你提高了對foreach嵌套操作者的動機問題,「%:%」。如果內循環體需要的計算大量時間,你是非常安全的使用:

foreach(i = 1:I) %:% 
    foreach(j = 1:J) %dopar% { 
     # Do stuff 
    } 

這種「解開」嵌套循環,導致都可以執行(I * J),任務在平行下。

如果內循環的主體不需要很多時間,解決方案就更加困難。標準的解決方案是並行化外部循環,但這仍然可能導致許多小任務(當我很大,J很小時)或幾個大任務(當我很小,J很大時)。

我最喜歡的解決方案是使用具有任務塊的嵌套操作符。下面是一個使用doMPI後端的完整範例:

library(doMPI) 
cl <- startMPIcluster() 
registerDoMPI(cl) 
I <- 100; J <- 2 
opt <- list(chunkSize=10) 
foreach(i = 1:I, .combine='cbind', .options.mpi=opt) %:% 
    foreach(j = 1:J, .combine='c') %dopar% { 
     (i * j) 
    } 
closeCluster(cl) 

這將導致20「任務塊」,每一個組成的循環體的10分計算。如果你想爲每個工人單任務塊,你可以計算塊的大小爲:

cs <- ceiling((I * J)/getDoParWorkers()) 
opt <- list(chunkSize=cs) 

不幸的是,並非所有的並行後端支持任務分塊。另外,doMPI不支持Windows。

有關此主題的更多信息,請參閱我在foreach包小插曲「嵌套foreach循環」:

library(foreach) 
vignette('nesting') 
+0

哇,我站在敬畏,很高興你加入了!當我找到你的時候,如果你有一個非常耗時的任務執行,有沒有辦法保存部分結果,然後用'foreach'包來恢復?對我來說,這是完美並行化框架中唯一缺失的部分。如果你有需要運行一週的事情,那麼偶爾停下來看看你是否正常運行是很好的。 – Backlin

+0

@Backlin:在foreach中肯定沒有檢查點功能,但通過編寫自己的迭代器和組合函數,你可以做多少事情令人驚訝。如果提供foreach循環的迭代器能夠與組合函數協調,那麼您可能會拼湊一些類似的東西。我可能會嘗試寫一個演示這個想法的例子。 –

+0

好吧,只是想確保我沒有錯過另一個半明顯的事情。如果您編寫檢查點示例,我會很高興看到它,但手動執行相當容易。 – Backlin

6

如果你最終得到幾個嵌套的foreach循環,我會重新思考我的方法。使用並行版本tapply可以解決很多麻煩。一般來說,你不應該使用嵌套並行化,因爲這不會給你帶來任何東西。並行化外部循環,並忘記內部循環。

原因很簡單:如果在羣集中有3個連接,則外部的dopar循環將使用全部三個連接。內部的dopar循環將無法使用任何額外的連接,因爲沒有可用的連接。所以你沒有獲得一件事。因此,從編程的角度來看,你給出的模型根本沒有意義。

第二個問題很容易被getDoParRegistered()這個函數回答,當後端註冊時返回TRUE,否則返回FALSE。請注意:

  • 如果註冊了後續後端(即調用registerDoSEQ),它也返回TRUE。
  • 集羣停止後它也會返回TRUE,但是在這種情況下%dopar%會返回一個錯誤。

如:

require(foreach) 
require(doSNOW) 
cl <- makeCluster(rep("localhost",2),type="SOCK") 
getDoParRegistered() 
[1] FALSE 
registerDoSNOW(cl) 
getDoParRegistered() 
[1] TRUE 
stopCluster(cl) 
getDoParRegistered() 
[1] TRUE 

但現在運行此代碼:

a <- matrix(1:16, 4, 4) 
b <- t(a) 
foreach(b=iter(b, by='col'), .combine=cbind) %dopar% 
    (a %*% b) 

會返回一個錯誤:

Error in summary.connection(connection) : invalid connection 

你可以建立一個額外的檢查。 A(極其醜陋的)黑客可以用它來檢查由doSNOW註冊的連接是有效的,可以是:

isvalid <- function(){ 
    if (getDoParRegistered()){ 
     X <- foreach:::.foreachGlobals$objs[[1]]$data 
     x <- try(capture.output(print(X)),silent=TRUE) 
     if(is(x,"try-error")) FALSE else TRUE 
    } else { 
     FALSE 
    } 
} 

,您可以使用作爲

if(!isvalid()) registerDoSEQ() 

這將如果getDoParRegistered登記順序後端()返回TRUE,但不再有有效的集羣連接。但是,再次,這是一個黑客攻擊,我不知道它是否與其他後端甚至其他類型的集羣類型(我主要使用套接字)。

+1

好東西。 'getDoParWorkers()'也會返回#個註冊工作者。 – Iterator

+0

@Iterator:是的,但在這裏沒有必要。如果後端已經註冊,我會將用戶知道他/她正在做什麼。如果沒有,registerDoSEQ()採取安全的方式。如果有人使用不同的後端,例如doMC,doSMP,... –

2

在的問題,相反的順序,你問:

  1. @Joris是正確的關於檢查一個已註冊的並行後端。但是請注意,單核的機器與是否註冊了並行後端是不同的。檢查內核的數量是一個非常平臺(操作系統)的特定任務。在Linux上,這可能會爲你工作:

    CountUnixCPUs <- function(cpuinfo = "/proc/cpuinfo"){ 
    tmpCmd <- paste("grep processor ", cpuinfo, " | wc -l", sep = "") 
    numCPU <- as.numeric(system(tmpCmd, intern = TRUE)) 
    return(numCPU) 
    } 
    

    編輯:見@里斯的鏈接到其他頁面,下面,爲Windows和Linux提供建議。我可能會重寫我自己的代碼,至少包括更多的核心計數選項。

  2. 關於嵌套循環,我採取了不同的方法:我準備一個參數表,然後遍歷行。一個非常簡單的方法是,例如:

    library(Matrix) 
    ptable <- which(triu(matrix(1, ncol = 20, nrow = 20))==1, arr.ind = TRUE) 
    foreach(ix_row = 1:nrow(ptable)) %dopar% { myFunction(ptable[ix_row,])} 
    
+0

順便說一下,如果有任何方法可以從R內部確定可用內核的數量,那麼我會*非常*有興趣知道這一點。它似乎不受'.Platform','.Machine'或'Sys.info()'支持。 – Iterator

+0

不知道這是否會讓你走得更遠。在單核上,您仍然可以註冊多個工作者。我甚至不知道這些天是否銷售了許多單核電腦... –

+0

@Joris:你是對的 - 任何人都可以堅持下去,擁有比核心更多的工作者。在設置工人數量之前,我總是先檢查覈心數量。對於正在銷售的單核電腦,它們已經全部結束:iPhone,Android設備,甚至微軟都以這些「電話」功能支持這些單核電腦。 ;-)想象一下:R在你的口袋裏! – Iterator