क्या आर का लागू परिवार सिंटैक्टिक चीनी से अधिक है?


152

... निष्पादन समय और / या स्मृति के बारे में।

यदि यह सच नहीं है, तो इसे एक कोड स्निपेट के साथ साबित करें। ध्यान दें कि वैश्वीकरण द्वारा स्पीडअप की गणना नहीं की जाती है। Speedup से आना चाहिए apply( tapply, sapply, ...) ही।

जवाबों:


152

applyआर में कार्य अन्य पाशन कार्यों (उदाहरण के लिए अधिक बेहतर प्रदर्शन प्रदान नहीं करते हैं for)। इसका एक अपवाद है lapplyजो थोड़ा तेज़ हो सकता है क्योंकि यह R की तुलना में C कोड में अधिक काम करता है ( इस उदाहरण के लिए इस प्रश्न को देखें )।

लेकिन सामान्य तौर पर, नियम यह है कि आपको स्पष्टता के लिए एक लागू फ़ंक्शन का उपयोग करना चाहिए, प्रदर्शन के लिए नहीं

मैं यह है कि बढ़ेगी लागू कार्यों कोई साइड इफेक्ट है, जो एक महत्वपूर्ण अंतर है जब यह आर इस के साथ कार्यात्मक प्रोग्रामिंग के लिए आता है का उपयोग करके अधिरोहित जा सकता है assignया <<-है, लेकिन यह बहुत ही खतरनाक हो सकता है। दुष्प्रभाव भी समझने के लिए एक कार्यक्रम को कठिन बनाते हैं क्योंकि एक चर की स्थिति इतिहास पर निर्भर करती है।

संपादित करें:

सिर्फ एक तुच्छ उदाहरण के साथ इस पर जोर देने के लिए जो पुनरावर्तक रूप से फाइबोनैचि अनुक्रम की गणना करता है; यह एक सटीक उपाय प्राप्त करने के लिए कई बार चलाया जा सकता है, लेकिन मुद्दा यह है कि तरीकों में से कोई भी काफी अलग प्रदर्शन नहीं करता है:

> fibo <- function(n) {
+   if ( n < 2 ) n
+   else fibo(n-1) + fibo(n-2)
+ }
> system.time(for(i in 0:26) fibo(i))
   user  system elapsed 
   7.48    0.00    7.52 
> system.time(sapply(0:26, fibo))
   user  system elapsed 
   7.50    0.00    7.54 
> system.time(lapply(0:26, fibo))
   user  system elapsed 
   7.48    0.04    7.54 
> library(plyr)
> system.time(ldply(0:26, fibo))
   user  system elapsed 
   7.52    0.00    7.58 

2 संपादित करें:

R (उदाहरण के लिए rpvm, rmpi, स्नो) के लिए समानांतर पैकेज के उपयोग के बारे में, ये आम तौर पर applyपारिवारिक कार्य प्रदान करते हैं (यहां तक ​​कि foreachपैकेज अनिवार्य रूप से समकक्ष है, नाम के बावजूद)। यहाँ sapplyसमारोह का एक सरल उदाहरण है snow:

library(snow)
cl <- makeSOCKcluster(c("localhost","localhost"))
parSapply(cl, 1:20, get("+"), 3)

यह उदाहरण सॉकेट क्लस्टर का उपयोग करता है, जिसके लिए कोई अतिरिक्त सॉफ़्टवेयर स्थापित करने की आवश्यकता नहीं है; अन्यथा आपको पीवीएम या एमपीआई ( टियरनी के क्लस्टरिंग पृष्ठ देखें ) जैसे कुछ की आवश्यकता होगी । snowनिम्नलिखित लागू कार्य हैं:

parLapply(cl, x, fun, ...)
parSapply(cl, X, FUN, ..., simplify = TRUE, USE.NAMES = TRUE)
parApply(cl, X, MARGIN, FUN, ...)
parRapply(cl, x, fun, ...)
parCapply(cl, x, fun, ...)

यह समझ में आता है कि applyकार्यों का उपयोग समानांतर निष्पादन के लिए किया जाना चाहिए क्योंकि उनके कोई दुष्प्रभाव नहीं हैं । जब आप forलूप के भीतर एक वैरिएबल वैल्यू बदलते हैं , तो यह ग्लोबली सेट होता है। दूसरी ओर, सभी applyफ़ंक्शन सुरक्षित रूप से समानांतर में उपयोग किए जा सकते हैं क्योंकि फ़ंक्शन कॉल में स्थानीय परिवर्तन होते हैं (जब तक आप उपयोग करने की कोशिश नहीं करते हैं assignया <<-, जिस स्थिति में आप साइड इफेक्ट्स पेश कर सकते हैं)। कहने की जरूरत नहीं है, स्थानीय बनाम वैश्विक चर के बारे में सावधान रहना महत्वपूर्ण है, खासकर जब समानांतर निष्पादन से निपटना।

संपादित करें:

यहाँ एक तुच्छ उदाहरण के बीच अंतर दिखाने के लिए forऔर *applyअब तक साइड इफेक्ट्स का संबंध है:

> df <- 1:10
> # *apply example
> lapply(2:3, function(i) df <- df * i)
> df
 [1]  1  2  3  4  5  6  7  8  9 10
> # for loop example
> for(i in 2:3) df <- df * i
> df
 [1]  6 12 18 24 30 36 42 48 54 60

ध्यान दें कि dfमूल वातावरण में कैसे बदल जाता है forलेकिन नहीं *apply


30
आर के लिए अधिकांश मल्टी कोर पैकेज applyफ़ंक्शंस के परिवार के माध्यम से समानांतरकरण को भी लागू करते हैं। इसलिए स्ट्रक्चरिंग प्रोग्राम इसलिए वे उपयोग करते हैं, उन्हें बहुत मामूली सी लागत पर समानांतर करने की अनुमति देता है।
शार्प

शार्पी - इसके लिए धन्यवाद! एक उदाहरण के लिए किसी भी विचार दिखा कि (विंडोज़ XP पर)?
ताल गलिउल

5
मैं सुझाव देना चाहूंगा कि snowfallपैकेज को देखें और उनके विगनेट में उदाहरणों को आज़माएँ। पैकेज के snowfallऊपर बनाता है snowऔर समानांतर के विवरण को अमूर्त करता है और आगे भी समानांतर applyकार्यों को निष्पादित करने के लिए इसे मृत सरल बनाता है।
पैराग्राफ

1
@ शेर्पी लेकिन ध्यान दें कि foreachजब से उपलब्ध हुआ है और लगता है कि एसओ के बारे में बहुत पूछताछ की जा रही है।
अरी बी। फ्राइडमैन

1
@ शेन, आपके उत्तर के शीर्ष पर, आप एक उदाहरण के रूप में एक अन्य प्रश्न से लिंक करते हैं जहां लूप की lapplyतुलना में "थोड़ा तेज" है for। हालांकि, वहाँ, मैं ऐसा कुछ भी सुझाव नहीं देख रहा हूँ। आप केवल उसी lapplyका उल्लेख करते हैं sapply, जो अन्य कारणों से अच्छी तरह से ज्ञात तथ्य है ( sapplyउत्पादन को सरल बनाने की कोशिश करता है और इसलिए बहुत सारे डेटा साइज की जाँच और संभावित रूपांतरण करना पड़ता है)। से संबंधित कुछ भी नहीं for। क्या मैं कुछ भूल रहा हूँ?
फ्लोडेल

70

कभी-कभी स्पीडअप पर्याप्त हो सकता है, जैसे कि जब आपको एक से अधिक कारकों के समूहन के आधार पर औसत प्राप्त करने के लिए लूप के लिए घोंसला बनाना पड़ता है। यहां आपके पास दो दृष्टिकोण हैं जो आपको सटीक परिणाम देते हैं:

set.seed(1)  #for reproducability of the results

# The data
X <- rnorm(100000)
Y <- as.factor(sample(letters[1:5],100000,replace=T))
Z <- as.factor(sample(letters[1:10],100000,replace=T))

# the function forloop that averages X over every combination of Y and Z
forloop <- function(x,y,z){
# These ones are for optimization, so the functions 
#levels() and length() don't have to be called more than once.
  ylev <- levels(y)
  zlev <- levels(z)
  n <- length(ylev)
  p <- length(zlev)

  out <- matrix(NA,ncol=p,nrow=n)
  for(i in 1:n){
      for(j in 1:p){
          out[i,j] <- (mean(x[y==ylev[i] & z==zlev[j]]))
      }
  }
  rownames(out) <- ylev
  colnames(out) <- zlev
  return(out)
}

# Used on the generated data
forloop(X,Y,Z)

# The same using tapply
tapply(X,list(Y,Z),mean)

दोनों बिल्कुल समान परिणाम देते हैं, औसत और नामित पंक्तियों और स्तंभों के साथ 5 x 10 मैट्रिक्स है। परंतु :

> system.time(forloop(X,Y,Z))
   user  system elapsed 
   0.94    0.02    0.95 

> system.time(tapply(X,list(Y,Z),mean))
   user  system elapsed 
   0.06    0.00    0.06 

तुम वहाँ जाओ। मैं क्या जीत गया? ;-)


आह, इतनी प्यारी :-) मैं वास्तव में सोच रहा था कि क्या कोई कभी मेरे देर से जवाब में आएगा।
जोरिस मे ने

1
मैं हमेशा "सक्रिय" द्वारा सॉर्ट करता हूं। :) यह सुनिश्चित न करें कि अपने उत्तर को सामान्य कैसे करें; कभी-कभी *applyतेज होता है। लेकिन मुझे लगता है कि अधिक महत्वपूर्ण बिंदु दुष्प्रभाव है (एक उदाहरण के साथ मेरे जवाब को अपडेट किया गया)।
शेन

1
मुझे लगता है कि विशेष रूप से तेजी से लागू होता है जब आप अलग-अलग सबसेट पर एक फ़ंक्शन लागू करना चाहते हैं। यदि नेस्टेड लूप के लिए एक स्मार्ट लागू समाधान है, तो मुझे लगता है कि लागू समाधान भी तेज होगा। ज्यादातर मामलों में लागू होने से मुझे अनुमान लगाने में अधिक गति नहीं मिलती है, लेकिन मैं निश्चित रूप से दुष्प्रभावों पर सहमत हूं।
जोरिस मेय्स

2
यह थोड़ा बंद विषय है, लेकिन इस विशिष्ट उदाहरण के लिए, data.tableऔर भी तेज है और मुझे लगता है कि "आसान" है। library(data.table) dt<-data.table(X,Y,Z,key=c("Y,Z")) system.time(dt[,list(X_mean=mean(X)),by=c("Y,Z")])
dnlbrky

12
यह तुलना बेतुकी है। tapplyएक विशेष कार्य के लिए एक विशेष समारोह, है कि क्यों यह तेजी से पाश के लिए एक से है। यह वह नहीं कर सकता जो लूप के लिए कर सकता है (जबकि नियमित applyकर सकता है)। आप संतरे के साथ सेब की तुलना कर रहे हैं।
Eddi

47

... और जैसा कि मैंने अभी और कहीं लिखा है, vapply आपका दोस्त है! ... यह नीलम जैसा है, लेकिन आप वापसी मूल्य प्रकार भी निर्दिष्ट करते हैं जो इसे बहुत तेज बनाता है।

foo <- function(x) x+1
y <- numeric(1e6)

system.time({z <- numeric(1e6); for(i in y) z[i] <- foo(i)})
#   user  system elapsed 
#   3.54    0.00    3.53 
system.time(z <- lapply(y, foo))
#   user  system elapsed 
#   2.89    0.00    2.91 
system.time(z <- vapply(y, foo, numeric(1)))
#   user  system elapsed 
#   1.35    0.00    1.36 

1 जनवरी, 2020 अपडेट:

system.time({z1 <- numeric(1e6); for(i in seq_along(y)) z1[i] <- foo(y[i])})
#   user  system elapsed 
#   0.52    0.00    0.53 
system.time(z <- lapply(y, foo))
#   user  system elapsed 
#   0.72    0.00    0.72 
system.time(z3 <- vapply(y, foo, numeric(1)))
#   user  system elapsed 
#    0.7     0.0     0.7 
identical(z1, z3)
# [1] TRUE

मूल निष्कर्ष अब सत्य प्रतीत नहीं होते। forमेरे विंडोज 10, 2-कोर कंप्यूटर पर लूप तेज हैं। मैंने 5e6तत्वों के साथ ऐसा किया था - एक लूप 2.9 सेकंड बनाम 3.1 सेकंड के लिए था vapply
कोल

27

मैंने कहीं और लिखा है कि शेन जैसा उदाहरण वास्तव में विभिन्न प्रकार के लूपिंग सिंटैक्स के बीच प्रदर्शन में अंतर पर जोर नहीं देता है क्योंकि फ़ंक्शन के भीतर समय व्यतीत होने के बजाय वास्तव में लूप पर जोर दिया जाता है। इसके अलावा, कोड गलत तरीके से लूप के लिए तुलना करता है जिसमें कोई भी मेमोरी नहीं है जो कि पारिवारिक फ़ंक्शन को लागू करता है जो मान लौटाता है। यहाँ थोड़ा अलग उदाहरण है जो इस बिंदु पर जोर देता है।

foo <- function(x) {
   x <- x+1
 }
y <- numeric(1e6)
system.time({z <- numeric(1e6); for(i in y) z[i] <- foo(i)})
#   user  system elapsed 
#  4.967   0.049   7.293 
system.time(z <- sapply(y, foo))
#   user  system elapsed 
#  5.256   0.134   7.965 
system.time(z <- lapply(y, foo))
#   user  system elapsed 
#  2.179   0.126   3.301 

यदि आप परिणाम को बचाने की योजना बनाते हैं तो परिवार के कार्यों को लागू करें सिंटैक्टिक चीनी की तुलना में बहुत अधिक हो सकता है

(z का सिंपल अनलिस्ट केवल 0.2 s है, इसलिए lapply बहुत तेज है। लूप के लिए z को इनिशियलाइज़ करना काफी तेज़ है क्योंकि मैं अंतिम 5 में से 6 रन का औसत दे रहा हूँ ताकि सिस्टम के बाहर चला जाए। शायद ही चीजों को प्रभावित करें)

एक और बात ध्यान देने योग्य है कि उनके प्रदर्शन, स्पष्टता या साइड इफेक्ट्स की कमी से स्वतंत्र पारिवारिक कार्यों को लागू करने का एक और कारण है। एक forलूप आमतौर पर लूप के भीतर जितना संभव हो सके डालने को बढ़ावा देता है। ऐसा इसलिए है क्योंकि प्रत्येक लूप को जानकारी संग्रहीत करने के लिए चर की स्थापना की आवश्यकता होती है (अन्य संभावित कार्यों के बीच)। लागू करें बयानों को दूसरे तरीके से पक्षपाती किया जाता है। अक्सर कई बार आप अपने डेटा पर कई ऑपरेशन करना चाहते हैं, जिनमें से कई वेक्टर हो सकते हैं लेकिन कुछ नहीं हो सकते हैं। आर में, अन्य भाषाओं के विपरीत, उन ऑपरेशनों को अलग करना और उन लोगों को चलाना सबसे अच्छा है जो एक आवेदन विवरण (या फ़ंक्शन के वेक्टरकृत संस्करण) में सदिश नहीं किए गए हैं और जो सच्चे वेक्टर ऑपरेशन के रूप में वेक्टरकृत हैं। इससे अक्सर प्रदर्शन में तेजी आती है।

जोरिस मेय्स उदाहरण लेते हुए, जहां वह एक पारंपरिक आर लूप के लिए एक आसान आर फ़ंक्शन के साथ बदलता है हम इसका उपयोग विशेष फ़ंक्शन के बिना एक समान स्पीडअप के लिए अधिक आर अनुकूल तरीके से कोड लिखने की दक्षता दिखाने के लिए कर सकते हैं।

set.seed(1)  #for reproducability of the results

# The data - copied from Joris Meys answer
X <- rnorm(100000)
Y <- as.factor(sample(letters[1:5],100000,replace=T))
Z <- as.factor(sample(letters[1:10],100000,replace=T))

# an R way to generate tapply functionality that is fast and 
# shows more general principles about fast R coding
YZ <- interaction(Y, Z)
XS <- split(X, YZ)
m <- vapply(XS, mean, numeric(1))
m <- matrix(m, nrow = length(levels(Y)))
rownames(m) <- levels(Y)
colnames(m) <- levels(Z)
m

यह हवाओं के forलूप की तुलना में बहुत तेज है और अनुकूलित tapplyकार्य में निर्मित की तुलना में थोड़ा धीमा है । ऐसा नहीं है क्योंकि vapplyयह बहुत तेजी से है, forलेकिन क्योंकि यह लूप के प्रत्येक पुनरावृत्ति में केवल एक ऑपरेशन कर रहा है। इस कोड में और सब कुछ सदिश है। जोरिस मेय पारंपरिक forलूप में कई (7?) ऑपरेशन प्रत्येक पुनरावृत्ति में हो रहे हैं और इसे निष्पादित करने के लिए सेटअप का थोड़ा सा हिस्सा है। यह भी ध्यान दें कि यह forसंस्करण की तुलना में कितना अधिक कॉम्पैक्ट है ।


4
लेकिन शेन की उदाहरण यथार्थवादी में समय की है कि सबसे अधिक है है आमतौर पर समारोह में खर्च किया, पाश में नहीं।
हैडले

9
खुद के लिए बोलें ...:) ... हो सकता है कि शेन एक निश्चित अर्थ में यथार्थवादी है लेकिन उसी अर्थ में विश्लेषण पूरी तरह से बेकार है। लोगों को पुनरावृति तंत्र की गति के बारे में परवाह होगी जब उन्हें बहुत सारे पुनरावृत्तियों को करना होगा, अन्यथा उनकी समस्याएं वैसे भी कहीं और हैं। यह किसी भी फ़ंक्शन का सच है। अगर मैं एक ऐसा पाप लिखता हूँ जो 0.001 लेता है और कोई दूसरा लिखता है जो 0.002 लेता है जो परवाह करता है ?? खैर, जैसे ही आपको उनकी देखभाल करनी होती है।
जॉन

2
12 कोर 3Ghz इंटेल Xeon, 64 बिट पर, मुझे आपके लिए बहुत भिन्न संख्याएं मिलती हैं - लूप के लिए काफी सुधार होता है: आपके तीन परीक्षणों के लिए, मुझे मिलता है 2.798 0.003 2.803; 4.908 0.020 4.934; 1.498 0.025 1.528, और अस्पष्ट भी बेहतर है:1.19 0.00 1.19
n

2
यह ओएस और आर संस्करण के साथ भिन्न होता है ... और एक पूर्ण अर्थ सीपीयू में। मैं सिर्फ मैक पर 2.15.2 के साथ भाग गया और sapply50% धीमी forऔर lapplyदो बार के रूप में तेजी से मिला।
जॉन

1
आपके उदाहरण में, आपको सेट yकरने का मतलब है 1:1e6, न कि numeric(1e6)(शून्य का वेक्टर)। बार-बार आवंटित foo(0)करने की कोशिश करना z[0]अच्छी तरह से एक विशिष्ट forलूप उपयोग की व्याख्या नहीं करता है । संदेश अन्यथा हाजिर है।
फ्लोडेल

3

वेक्टर के सबसेट पर कार्य लागू करते समय, tapplyलूप की तुलना में बहुत तेज हो सकता है। उदाहरण:

df <- data.frame(id = rep(letters[1:10], 100000),
                 value = rnorm(1000000))

f1 <- function(x)
  tapply(x$value, x$id, sum)

f2 <- function(x){
  res <- 0
  for(i in seq_along(l <- unique(x$id)))
    res[i] <- sum(x$value[x$id == l[i]])
  names(res) <- l
  res
}            

library(microbenchmark)

> microbenchmark(f1(df), f2(df), times=100)
Unit: milliseconds
   expr      min       lq   median       uq      max neval
 f1(df) 28.02612 28.28589 28.46822 29.20458 32.54656   100
 f2(df) 38.02241 41.42277 41.80008 42.05954 45.94273   100

applyहालाँकि, ज्यादातर स्थिति में कोई गति वृद्धि प्रदान नहीं करता है, और कुछ मामलों में बहुत धीमी हो सकती है:

mat <- matrix(rnorm(1000000), nrow=1000)

f3 <- function(x)
  apply(x, 2, sum)

f4 <- function(x){
  res <- 0
  for(i in 1:ncol(x))
    res[i] <- sum(x[,i])
  res
}

> microbenchmark(f3(mat), f4(mat), times=100)
Unit: milliseconds
    expr      min       lq   median       uq      max neval
 f3(mat) 14.87594 15.44183 15.87897 17.93040 19.14975   100
 f4(mat) 12.01614 12.19718 12.40003 15.00919 40.59100   100

लेकिन इन स्थितियों के लिए हमें मिल गया है colSumsऔर rowSums:

f5 <- function(x)
  colSums(x) 

> microbenchmark(f5(mat), times=100)
Unit: milliseconds
    expr      min       lq   median       uq      max neval
 f5(mat) 1.362388 1.405203 1.413702 1.434388 1.992909   100

7
यह ध्यान रखना महत्वपूर्ण है कि (कोड के छोटे टुकड़ों के लिए) microbenchmarkयह बहुत अधिक सटीक है system.time। यदि आप तुलना करने की कोशिश करते हैं system.time(f3(mat))और system.time(f4(mat))आपको लगभग हर बार अलग परिणाम मिलेगा। कभी-कभी केवल एक उचित बेंचमार्क परीक्षण सबसे तेज कार्य दिखाने में सक्षम होता है।
मिशेल
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.