आर

2014-09-01 6 views
7

में वर्ण वैक्टरों की तेजी से भागना/छेड़छाड़ करना जेसन में तारों को एन्कोड करने के लिए, कई आरक्षित पात्रों को बैकस्लैश से बचने की आवश्यकता होती है, और प्रत्येक स्ट्रिंग को डबल कोट्स में लपेटने की आवश्यकता होती है। वर्तमान में jsonlite पैकेज इस लागू करता आधार आर में deparse फ़ंक्शन का उपयोग:आर

deparse_vector <- function(x) { 
    stopifnot(is.character(x)) 
    vapply(x, deparse, character(1), USE.NAMES=FALSE) 
} 

यह काम कर देता है:

test <- c("line\nline", "foo\\bar", "I said: \"hi!\"") 
cat(deparse_vector(test)) 

हालांकि deparse पता चला है बड़े वैक्टर के लिए धीमी गति से किया जाना है। एक वैकल्पिक कार्यान्वयन gsub को व्यक्तिगत रूप से हर किरदार है:

deparse_vector2 <- function(x) { 
    stopifnot(is.character(x)) 
    if(!length(x)) return(x) 
    x <- gsub("\\", "\\\\", x, fixed=TRUE) 
    x <- gsub("\"", "\\\"", x, fixed=TRUE) 
    x <- gsub("\n", "\\n", x, fixed=TRUE) 
    x <- gsub("\r", "\\r", x, fixed=TRUE) 
    x <- gsub("\t", "\\t", x, fixed=TRUE) 
    x <- gsub("\b", "\\b", x, fixed=TRUE) 
    x <- gsub("\f", "\\f", x, fixed=TRUE) 
    paste0("\"", x, "\"") 
} 

इसमें कुछ समय तेजी से होता है, लेकिन बहुत ज्यादा नहीं है और एक सा बदसूरत भी। ऐसा करने का एक बेहतर तरीका क्या होगा? (अधिमानतः अतिरिक्त निर्भरता के बिना)

यह script कार्यान्वयन तुलना करने के लिए इस्तेमाल किया जा सकता:

> system.time(out1 <- deparse_vector1(strings)) 
    user system elapsed 
    6.517 0.000 6.523 
> system.time(out2 <- deparse_vector2(strings)) 
    user system elapsed 
    1.194 0.000 1.194 
+1

आप कम से कम पोस्ट कुछ समय आपके द्वारा किए गए ताकि हमें पता चल सकता है कि क्या "काफी तेजी से" हो सकता है? – MrFlick

+0

मैंने कार्यान्वयन की तुलना करने के लिए एक स्क्रिप्ट का एक लिंक जोड़ा। – Jeroen

उत्तर

4

मैं एक तेज़ तरीका सिर्फ आर कोड के साथ ऐसा करने के बारे में पता नहीं है, लेकिन मैं कोशिश करने का फ़ैसला किया मेरा हाथ सी में कार्यान्वित करने पर, एक आर समारोह में लपेटा गया जिसे deparse_vector3 कहा जाता है। यह किसी न किसी तरह है (और मैं एक विशेषज्ञ सी प्रोग्रामर से दूर कर रहा हूँ), लेकिन यह आपके उदाहरण के लिए काम करने के लिए लगता है: https://gist.github.com/wch/e3ec5b20eb712f1b22b2

अपने सिस्टम (मैक, आर 3.1.1) पर, deparse_vector2deparse_vector से 20x से अधिक तेजी से होता है, जो आपके परीक्षण में 5x की तुलना में बहुत बड़ा अंतर।

मेरा deparse_vector3 फ़ंक्शन deparse_vector2 से 3x तेज है। शायद सुधार के लिए जगह है।

> system.time(out1 <- deparse_vector1(strings)) 
    user system elapsed 
    8.459 0.009 8.470 
> system.time(out2 <- deparse_vector2(strings)) 
    user system elapsed 
    0.368 0.007 0.374 
> system.time(out3 <- deparse_vector3(strings)) 
    user system elapsed 
    0.120 0.001 0.120 

मुझे नहीं लगता कि यह गैर-ASCII चरित्र एन्कोडिंग को सही तरीके से संभाल लेगा। आर स्रोत में एन्कोडिंग को कैसे प्रबंधित किया जाता है इसका एक उदाहरण यहां दिया गया है: https://github.com/wch/r-source/blob/trunk/src/main/grep.c#L704-L739

संपादित करें: ऐसा लगता है कि यह यूटीएफ -8 ठीक है, हालांकि यह संभव है कि मैं अपने परीक्षण में कुछ खो रहा हूं।

+0

मैंने इसे 'jsonlite' में प्लग करने का प्रयास किया, और ऐसा लगता है लेकिन यह थोड़ी देर के लिए इसका उपयोग करने के बाद आर में segfaults दे रहा है ... – Jeroen

+0

मुझे लगता है कि 'val = mkChar (newstr)' को वैल = प्रोटेक्ट (mkChar (newstr)); '(प्लस 'मुक्त()' के बाद असुरक्षित)। – hadley

+0

@ जेरोएन, क्या यह 'सुरक्षा' के साथ काम करता है? मैंने 'प्रोटेक्ट' के बिना आर स्रोत में 'एमकेखर' के बहुत सारे देखा, इसलिए मुझे लगा कि उन्हें संरक्षित करने की आवश्यकता नहीं है, लेकिन मैं इसके बारे में गलत हो सकता था। – wch

6

यहां विंस्टन के कोड का सी ++ संस्करण है। यह काफी आसान है क्योंकि आप std::string एस कुशलता से बढ़ सकते हैं। यह भी क्रैश होने की संभावना कम है क्योंकि आरसीपीपी आपके लिए मेमोरी प्रबंधन का ख्याल रखता है।

#include <Rcpp.h> 
using namespace Rcpp; 

// [[Rcpp::export]] 
std::string escape_one(std::string x) { 
    std::string out = "\""; 

    int n = x.size(); 
    for (int i = 0; i < n; ++i) { 
    char cur = x[i]; 

    switch(cur) { 
     case '\\': out += "\\\\"; break; 
     case '"': out += "\\\""; break; 
     case '\n': out += "\\n"; break; 
     case '\r': out += "\\r"; break; 
     case '\t': out += "\\t"; break; 
     case '\b': out += "\\b"; break; 
     case '\f': out += "\\f"; break; 
     default:  out += cur; 
    } 
    } 

    out += '"'; 

    return out; 
} 

// [[Rcpp::export]] 
CharacterVector escape_chars(CharacterVector x) { 
    int n = x.size(); 
    CharacterVector out(n); 

    for (int i = 0; i < n; ++i) { 
    String cur = x[i]; 
    out[i] = escape_one(cur); 
    } 

    return out; 
} 

अपने बेंचमार्क पर, deparse_vector2(strings) 0.8s लेता है, और escape_chars(strings) 0.165s लेता है।

+0

छोटे सुझाव: 'escape_oneerve 'में आउटपुट के लिए कुछ स्मृति को पूर्व-आवंटित करने के लिए' out.reserve (n) 'का उपयोग करें, हम पुन: आवंटन से बचने के लिए (हम जानते हैं कि' आउट' कम से कम 'x' के रूप में बड़ा है)। –

+0

@ केविन यूशी मैंने वास्तव में कहीं और कोशिश की - यह लगभग 10% – hadley

3

तुम भी deparse_vector

से stringi पैकेज से stri_escape_unicode कोशिश कर सकते हैं (हालांकि आप अतिरिक्त निर्भरता के बिना एक समाधान को प्राथमिकता दी लेकिन मुझे लगता है कि यह बहुत भविष्य पाठकों के लिए उपयोगी हो सकता है), जो की तुलना में लगभग 3 गुना तेजी से deparse_vector2 और के बारे में 7 गुना तेजी
require(stringi) 

समारोह

deparse_vector3 <- function(x){ 
    paste0("\"",stri_escape_unicode(x), "\"") 
} 

जांच की जा रही है कि सभी कार्यों smae परिणाम दे परिभाषित

all.equal(deparse_vector2(test), deparse_vector3(test)) 
## [1] TRUE 
all.equal(deparse_vector(test), deparse_vector3(test)) 
## [1] TRUE 

कुछ मानक

इस समस्या यह है कि एक जोड़े को तथ्यों का लाभ लेता है पर
library(microbenchmark) 
microbenchmark(deparse_vector(test), 
       deparse_vector2(test), 
       deparse_vector3(test), times = 1000L) 

# Unit: microseconds 
#     expr min  lq median  uq  max neval 
# deparse_vector(test) 98.548 102.654 104.707 111.380 2500.653 1000 
# deparse_vector2(test) 43.114 46.707 48.761 51.327 401.377 1000 
# deparse_vector3(test) 14.885 16.938 18.991 20.018 240.211 1000 <-- Clear winner 
2

एक और वार।

लंबाई n साथ एक स्ट्रिंग x को देखते हुए, हम जानते हैं कि उत्पादन स्ट्रिंग कम से कम लंबाई x हो जाएगा, और सबसे 2 * x पर। यह सुनिश्चित करने के लिए हम इसका लाभ उठा सकते हैं कि हम केवल एक बार मेमोरी आवंटित करें, बजाय बढ़ते कंटेनरों पर भरोसा करने के बजाय (कुशलतापूर्वक यद्यपि)।

ध्यान दें कि मैं सी ++ 11 के shared_ptr का उपयोग करता हूं, क्योंकि मैं कच्ची मेमोरी के साथ बदसूरत चीजें कर रहा हूं (और यह सुनिश्चित करना चाहता हूं कि यह स्वचालित रूप से साफ हो जाए)। यह मुझे प्रारंभिक पास से बचने की इजाजत देता है जिसमें मैं मैचों की गिनती करने का प्रयास करता हूं, लेकिन मुझे थोड़ा अधिक आवंटित करने के लिए भी मजबूर करता है (जिस मामले में हर एक चरित्र से बच जाना चाहिए दुर्लभ होगा)।

मुझे लगता है कि शुद्ध सी समाधान में इसे अनुकूलित करना अपेक्षाकृत आसान होगा, लेकिन यह सुनिश्चित करने के लिए कि यह मेमोरी ठीक से साफ हो जाए, यह मुश्किल होगा।

#include <memory> 
#include <Rcpp.h> 
using namespace Rcpp; 

// [[Rcpp::export]] 
void escape_one_fill(CharacterVector const& x, int i, CharacterVector& output) { 

    auto xi = CHAR(STRING_ELT(x, i)); 
    int n = strlen(xi); 

    // Over-allocate memory -- we know that in the worst case the output 
    // string is 2x the length of x (plus 1 for \0) 
    auto out = std::make_shared<char*>(new char[n * 2 + 1]); 

    int counter = 0; 
    (*out)[counter++] = '"'; 

    #define HANDLE_CASE(X, Y) \ 
    case X: \ 
     (*out)[counter++] = '\\'; \ 
     (*out)[counter++] = Y; \ 
     break; 

    for (int j = 0; j < n; ++j) { 
    switch (xi[j]) { 
     HANDLE_CASE('\\', '\\'); 
     HANDLE_CASE('"', '"'); 
     HANDLE_CASE('\n', 'n'); 
     HANDLE_CASE('\r', 'r'); 
     HANDLE_CASE('\t', 't'); 
     HANDLE_CASE('\b', 'b'); 
     HANDLE_CASE('\f', 'f'); 
     default: (*out)[counter++] = xi[j]; 
    } 
    } 

    (*out)[counter++] = '"'; 

    // Set a NUL so that Rf_mkChar does what it should 
    (*out)[counter++] = '\0'; 
    SET_STRING_ELT(output, i, Rf_mkChar(*out)); 

} 

// [[Rcpp::export]] 
CharacterVector escape_chars_with_fill(CharacterVector x) { 
    int n = x.size(); 
    CharacterVector out(n); 

    for (int i = 0; i < n; ++i) { 
    escape_one_fill(x, i, out); 
    } 

    return out; 
} 

बेंचमार्किंग इस, मैं हो (सिर्फ हेडली के impl की तुलना में):

> mychars <- c(letters, " ", '"', "\\", "\t", "\n", "\r", "'", "/", "#", "$"); 

> createstring <- function(length){ 
+ paste(mychars[ceiling(runif(length, 0, length(mychars)))], collapse="") 
+ } 

> strings <- vapply(rep(1000, 10000), createstring, character(1), USE.NAMES=FALSE) 

> system.time(escape_chars(strings)) 
    user system elapsed 
    0.14 0.00 0.14 

> system.time(escape_chars_with_fill(strings)) 
    user system elapsed 
    0.080 0.001 0.081 

> identical(escape_chars(strings), escape_chars_with_fill(strings)) 
[1] TRUE 
+0

(+1) द्वारा प्रदर्शन में सुधार करता है, मुझे नहीं लगता कि 'system.time' विधियों की तुलना करने का सही तरीका है, खासकर जब ऐसा छोटा अंतर होता है –