2014-04-17 11 views
5

के कुशल संस्करण अक्सर मैं उन परिस्थितियों में भाग लेता हूं जहां मुझे यह जांचने की आवश्यकता होती है कि क्या कुछ शर्त किसी भी बड़े वेक्टर या सूची के किसी भी या सभी तत्वों के लिए है या नहीं। उदाहरण की जाँच करने के लिए यदि एक सूची किसी भी/केवल NULL तत्व शामिल हैं मैं का प्रयोग करेंगे:किसी भी/सभी

any(vapply(x, is.null, logical(1)) 
all(vapply(x, is.null, logical(1)) 

हालांकि इस अक्षम है क्योंकि यह हमेशा सूची में प्रत्येक तत्व की जांच करता है। एक बेहतर कार्यान्वयन जांचना बंद कर देगा जब पहले NULL या गैर NULL तत्व पाया गया था। अर्थात। के बराबर: एक for पाश के साथ ऐसा करने से

is.null(x[[1]]) || is.null(x[[2]]) || is.null(x[[3]]) || ... 
is.null(x[[1]]) && is.null(x[[2]]) && is.null(x[[3]]) && ... 

धीमी है। आर-बेस द्वारा प्रदान किए गए कुछ विशेष मामले हैं, उदाहरण के लिए anyNAany(is.na(.)) का एक कुशल संस्करण है जो वास्तव में ऐसा करता है।

all_fast(x, is.null) 
any_fast(x, is.null) 

लेकिन यह भी:

all_fast(x, function(z) {length(z) == 2}) 
all_fast(x, is, "POSIXt") 
+3

आप सी ++ या RCpp :) – bartektartanus

+0

में लिख सकते आप प्रत्येक व्यक्ति के लिए समस्या मतलब है, या सामान्य 'में all_fast' समारोह को लागू करने के' एक तरह से होगा Rcpp'? – Jeroen

+0

नहीं ... आप आरसीपीपी फ़ंक्शन के लिए तर्क के रूप में फ़ंक्शन पास कर सकते हैं। – bartektartanus

उत्तर

7

यहाँ अनुभवहीन तरीका है,

all0 <- function(x, FUN) 
    all(vapply(x, FUN, logical(1))) 

और एक अनुसंधान पाश ...

all1 <- function(x, FUN) { 
    for (xi in x) 
     if (!FUN(xi)) 
      return(FALSE) 
    TRUE 
} 

...जो

library(compiler) 
all1c <- cmpfun(all1) 

संकलित किया जा सकता है ... या सी में लिखे

library(inline) 
allc <- cfunction(signature(x="list", fun="function"), " 
    SEXP call = PROTECT(lang2(fun, R_NilValue)); 
    int len = Rf_length(x); 
    for (int i = 0; i < len; ++i) { 
     SETCADR(call, VECTOR_ELT(x, i)); 
     if (!LOGICAL(eval(call, R_GlobalEnv))[0]) { 
      UNPROTECT(1); 
      return Rf_ScalarLogical(FALSE); 
     } 
    } 
    UNPROTECT(1); 
    return Rf_ScalarLogical(TRUE);") 

हम प्रदर्शन को मापने की जरूरत है, तो

library(microbenchmark) 

सबसे ज्यादा मामले शर्त यह है कि होने लगते हैं पास

n <- 100000 
x0 <- x <- vector("list", n) 
microbenchmark(all0(x, is.null), all1(x, is.null), all1c(x, is.null), 
       allc(x, is.null)) 
## Unit: milliseconds 
##    expr  min  lq median  uq  max neval 
## all0(x, is.null) 47.48038 50.58960 52.34946 54.10116 61.94736 100 
## all1(x, is.null) 41.52370 44.40024 45.25135 46.68218 53.22317 100 
## all1c(x, is.null) 33.76666 35.03008 35.71738 36.41944 45.37174 100 
## allc(x, is.null) 13.95340 14.43153 14.78244 15.94688 19.41072 100 

इसलिए हम संकलित आर संस्करण की तुलना में सी में तेजी से केवल 2x हैं - प्रत्येक परीक्षण पर एक फ़ंक्शन कॉल है, इसलिए हम केवल लूपिंग प्रति से बचत कर रहे हैं। सी पाश - सबसे अच्छा मामला है जब हम बाहर निकलने के तुरंत और स्पष्ट रूप से पाश का लाभ दिखा, लेकिन फिर न संकलन है और न ही सी कोड हमें

x[[1]] <- FALSE 
microbenchmark(all0(x, is.null), all1(x, is.null), all1c(x, is.null), 
       allc(x, is.null)) 
## Unit: microseconds 
##    expr  min   lq  median  uq  max neval 
## all0(x, is.null) 45376.760 45772.5020 46108.5795 46655.005 54242.687 100 
## all1(x, is.null)  1.566  1.9550  2.6335 12.015 14.177 100 
## all1c(x, is.null)  1.367  1.7340  2.0345  9.359 17.438 100 
## allc(x, is.null)  1.229  1.6925  4.6955 11.628 23.378 100 

में मदद करता है यहाँ एक मध्यवर्ती मामला है, जो वास्तव में किसी भी आश्चर्य शामिल नहीं करता है संकलित आर लूप की तुलना में लगभग 2x तेज है, इसलिए लगभग 2x जल्दी हो जाता है।

x <- x0 
x[[length(x)/2]] <- FALSE 
microbenchmark(all0(x, is.null), all1(x, is.null), all1c(x, is.null), 
       allc(x, is.null)) 
## Unit: milliseconds 
##    expr  min  lq median  uq  max neval 
## all0(x, is.null) 46.85690 49.92969 51.045519 52.653137 59.445611 100 
## all1(x, is.null) 20.90066 21.92357 22.582636 23.077863 25.974395 100 
## all1c(x, is.null) 16.51897 17.44539 17.825551 18.119202 20.535709 100 
## allc(x, is.null) 6.98468 7.18392 7.312575 8.290859 9.460558 100 

स्पष्ट रूप से सी स्तर (VECTOR_ELT(x, i) == R_NilValue) पर शून्य के लिए परीक्षण, बहुत तेजी से है, इसलिए सी कोड है कि शून्य करने के लिए मूल्य की तुलना इसी आर कोड की तुलना में तेजी 100x के बारे में है। ऐसा लगता है कि सभी एनएलएल एक मूल्यवान हो सकता है, जबकि गति सामान्यता थी, लेकिन सामान्य उद्देश्य सी-लेवल के मामले में सभी इतने आकर्षक लगते नहीं हैं। और निश्चित रूप से सी कोड एनए या त्रुटि शर्तों से निपटता नहीं है।

+0

धन्यवाद! मुझे लगता है कि 'allc' मूर्ख' all0' पर एक बड़ा सुधार है। – Jeroen

1

'कोई भी' संस्करण:

res <- FALSE 
for (i in seq_along(x)) { if(is.null(x[i])) { res <-TRUE; break()} 
res 
लेकिन मैं अगर हम इस अधिक आम तौर पर लागू करने और एक शर्त है की जाँच के लिए एक अनुकूलित कार्यों प्रदान कर सकता है सोच रहा था

lapply और vapply आंतरिक रूप से केवल लूप के लिए हैं, इसलिए आप केवल सिंटैक्टिक संपीड़न को खो रहे हैं, लेकिन आप इसे तोड़ने की क्षमता प्राप्त कर रहे हैं परिभाषित स्थिति की पहली घटना पर लूप। आप res <- TRUE का उपयोग कर सकते हैं और 'सभी' संस्करण के लिए FALSE पर सेट कर सकते हैं।

2

जेरोन ठीक ही कहा गया है कि

हालांकि इस अक्षम है क्योंकि यह हमेशा सूची में प्रत्येक तत्व की जांच करता है। एक स्मार्ट कार्यान्वयन जांचना बंद कर देगा जब पहला न्यूल या गैर न्यूल तत्व पाया गया था।

और आरसीपीपी चीनी संस्करण कुछ सालों से ऐसा कर रहे हैं। मेरे पास कहीं बेंचमार्क तुलना है।

संपादित करें: यह पाया जाता है, यह एक बहुत पुराना उदाहरण है कि rbenchmark या microbenchmark संकुल के हमारे उपयोग के पहले का है, और यह अभी भी examples/SugarPerformance निर्देशिका में Rcpp पैकेज में है। जब मैंने इसे अब चलाने के लिए, प्रासंगिक लाइन है (और यहां लाइन फिट करने के लिए संपादित)

runs    expr hand.written  sugar  R hnd/sugar R/sugar 
1 5000 any(x * y < 0) 0.000128746 0.000232458 7.52280 0.553846 32361.9631 

हम "लाभ" इतना प्रभावशाली लगता है के रूप में जल्दी वार्ता का एक बहुत में यह प्रयोग किया जाता है। लेकिन यहां तक ​​कि एक भी आर रन सिर्फ 0.15 मिलीसेकंड है, जब तक कि आप वास्तव में बार-बार यह लाभ के लायक नहीं हैं।

और जैसा कि मार्टिन अपने जवाब में दिखाता है, बस बाइट-कंपाइलिंग (जो तब तक उपलब्ध नहीं था जब हम 2010 की शुरुआत में उदाहरण स्थापित करते थे) भी मददगार है।

1

एफडब्ल्यूआईडब्ल्यू, हालांकि यह कम लचीला है, यह तेज होने पर आर के मूल्यांकन तंत्र से बचने के लिए तेज़ी से है। मैं मार्टिन के उत्तर की तुलना में एक सरल Rcpp समाधान प्रदान करता हूं, लेकिन विशेष रूप से 'सभी NULL' मामले के लिए।

#include <Rcpp.h> 
using namespace Rcpp; 

// [[Rcpp::export]] 
SEXP all_fast(SEXP x, SEXP fun) { 
    SEXP call = PROTECT(Rf_lang2(fun, R_NilValue)); 
    int len = Rf_length(x); 
    for (int i = 0; i < len; ++i) { 
     SETCADR(call, VECTOR_ELT(x, i)); 
     if (!LOGICAL(Rf_eval(call, R_GlobalEnv))[0]) { 
      UNPROTECT(1); 
      return Rf_ScalarLogical(FALSE); 
     } 
    } 
    UNPROTECT(1); 
    return Rf_ScalarLogical(TRUE); 
} 

// [[Rcpp::export]] 
bool all_null(List x) { 
    int n = x.size(); 
    for (R_len_t i=0; i < n; ++i) { 
    if (x[i] != R_NilValue) return false; 
    } 
    return true; 
} 

/*** R 
n <- 100000 
x0 <- x <- vector("list", n) 
all_fast(x, is.null) 
all_null(x) 
library(microbenchmark) 
microbenchmark(
    all_fast(x, is.null), 
    all_null(x) 
) 
*/ 

मुझे

> Rcpp::sourceCpp('~/Desktop/all_fast.cpp') 

> n <- 100000 

> x0 <- x <- vector("list", n) 

> all_fast(x, is.null) 
[1] TRUE 

> all_null(x) 
[1] TRUE 

> library(microbenchmark) 

> microbenchmark(
+ all_fast(x, is.null), 
+ all_null(x) 
+) 
Unit: microseconds 
       expr  min  lq median  uq  max neval 
all_fast(x, is.null) 6703.948 6962.7355 7051.680 7231.1805 13100.41 100 
      all_null(x) 280.816 283.8025 292.531 303.3125 340.19 100 

अपनी खुद की सरल Rcpp रैपर लिखने के लिए प्रयास देता है आप कार्यों का एक सेट है कि बहुत सामान्य लागू कर रहे हैं, तो इसके लायक हो सकता है। आप लचीलापन खो देते हैं, लेकिन आपको पर्याप्त मात्रा में गति मिलती है।

क्या सहेजे गए माइक्रोसॉन्ड इसके लायक होने के लिए पर्याप्त जोड़ते हैं, हालांकि आपके उपयोग के मामले/डेटा आकार पर निर्भर करता है।

हालांकि मुझे लगता है कि मार्टिन का उत्तर सर्वोत्तम उत्तर है, मुझे लगता है कि यह जानना उचित है कि कुछ सामान्य मामलों के लिए विशिष्ट कार्यान्वयन इसके लायक हो सकते हैं।

इन अवधारणाओं को लागू करने वाला एक पैकेज अच्छा होगा: 'जेनेरिक' संस्करण मार्टिन प्रदान करता है, साथ ही आम मामलों के लिए 'ट्यूनेड' संस्करण प्रदान करता है। उदाहरण के लिए: all_null, all_na, all_inherits, all_odd, ...