“* 적용”패밀리가 실제로 벡터화되지 않습니까?


138

그래서 우리는 모든 R 신규 사용자에게 " apply벡터화되지 않았으며 Patrick Burns R Inferno Circle 4 "를 확인하는 데 익숙합니다 .

일반적인 반사는 apply 제품군에서 기능을 사용하는 것입니다. 이것은 벡터화 가 아니며 루프 숨김 입니다. apply 함수의 정의에는 for 루프가 있습니다. lapply 함수는 루프를 묻지 만 실행 시간은 명시적인 for 루프와 거의 같은 경향이 있습니다.

실제로 apply소스 코드를 간단히 살펴보면 루프가 드러납니다.

grep("for", capture.output(getAnywhere("apply")), value = TRUE)
## [1] "        for (i in 1L:d2) {"  "    else for (i in 1L:d2) {"

좋아 지금까지,하지만 봐 lapply또는 vapply실제로는 완전히 다른 그림을 보여준다 :

lapply
## function (X, FUN, ...) 
## {
##     FUN <- match.fun(FUN)
##     if (!is.vector(X) || is.object(X)) 
##        X <- as.list(X)
##     .Internal(lapply(X, FUN))
## }
## <bytecode: 0x000000000284b618>
## <environment: namespace:base>

따라서 분명히 거기에 for숨어있는 R 루프 가 없으며 내부 C 작성 함수를 호출합니다.

토끼 구멍을 훑어 보면 거의 같은 그림이 나타납니다.

또한 colMeans벡터화되지 않은 것으로 비난받지 않은 함수를 예로 들어 봅시다.

colMeans
# function (x, na.rm = FALSE, dims = 1L) 
# {
#   if (is.data.frame(x)) 
#     x <- as.matrix(x)
#   if (!is.array(x) || length(dn <- dim(x)) < 2L) 
#     stop("'x' must be an array of at least two dimensions")
#   if (dims < 1L || dims > length(dn) - 1L) 
#     stop("invalid 'dims'")
#   n <- prod(dn[1L:dims])
#   dn <- dn[-(1L:dims)]
#   z <- if (is.complex(x)) 
#     .Internal(colMeans(Re(x), n, prod(dn), na.rm)) + (0+1i) * 
#     .Internal(colMeans(Im(x), n, prod(dn), na.rm))
#   else .Internal(colMeans(x, n, prod(dn), na.rm))
#   if (length(dn) > 1L) {
#     dim(z) <- dn
#     dimnames(z) <- dimnames(x)[-(1L:dims)]
#   }
#   else names(z) <- dimnames(x)[[dims + 1]]
#   z
# }
# <bytecode: 0x0000000008f89d20>
#   <environment: namespace:base>

응? 그것은 또한 .Internal(colMeans(...우리가 토끼 구멍 에서 찾을 수 있는 전화 입니다 . 그렇다면 어떻게 다른 .Internal(lapply(..가요?

실제로 빠른 벤치 마크 결과 는 빅 데이터 세트 의 루프 보다 sapply나쁘지 않은 성능을 보여줍니다.colMeansfor

m <- as.data.frame(matrix(1:1e7, ncol = 1e5))
system.time(colMeans(m))
# user  system elapsed 
# 1.69    0.03    1.73 
system.time(sapply(m, mean))
# user  system elapsed 
# 1.50    0.03    1.60 
system.time(apply(m, 2, mean))
# user  system elapsed 
# 3.84    0.03    3.90 
system.time(for(i in 1:ncol(m)) mean(m[, i]))
# user  system elapsed 
# 13.78    0.01   13.93 

즉, 그 말을 정확 lapply하고 vapply 실제로 벡터화된다 (비교 apply이다 for또한 호출하는 루프 lapply)와 패트릭 번스 정말 의미 무슨 말을 했습니까?


8
이것은 모두 시맨틱에 있지만 벡터화 된 것으로 간주하지는 않습니다. R 함수가 한 번만 호출되고 값의 벡터를 전달할 수 있다면 벡터화 된 접근법을 고려합니다. *apply함수는 반복적으로 R 함수를 호출하여 루프를 만듭니다. 좋은 성능과 관련하여 sapply(m, mean): 아마도 C 코드의 lapply메소드는 한 번만 디스패치 한 다음 메소드를 반복적으로 호출합니까? mean.default꽤 최적화되어 있습니다.
Roland

4
훌륭한 질문이며 기본 코드를 확인해 주셔서 감사합니다. 최근에 변경되었는지 확인했지만 버전 2.13.0 이후의 R 릴리스 정보에서는 이에 대한 내용이 없습니다.
ilir

1
성능은 사용 된 플랫폼과 C- 컴파일러 및 링커 플래그에 어느 정도까지 의존합니까?
smci

3
@DavidArenburg 사실, 나는 그것이 잘 정의되어 있다고 생각하지 않습니다. 최소한 나는 표준 참조를 모른다. 언어 정의는 "벡터화"연산을 언급하지만 벡터화를 정의하지는 않습니다.
Roland

3
매우 관련성이 있음 : R은 가족을 구문 설탕보다 더 많이 적용합니까? (이러한 답변과 마찬가지로 잘 읽어보십시오.)
Gregor Thomas

답변:


73

첫째, 귀하의 예제에서 당신은 공평하지 않은 "data.frame"에 시험을 colMeans, apply그리고 "[.data.frame"그들은 오버 헤드를 가지고 있기 때문에 :

system.time(as.matrix(m))  #called by `colMeans` and `apply`
#   user  system elapsed 
#   1.03    0.00    1.05
system.time(for(i in 1:ncol(m)) m[, i])  #in the `for` loop
#   user  system elapsed 
#  12.93    0.01   13.07

행렬에서 그림은 약간 다릅니다.

mm = as.matrix(m)
system.time(colMeans(mm))
#   user  system elapsed 
#   0.01    0.00    0.01 
system.time(apply(mm, 2, mean))
#   user  system elapsed 
#   1.48    0.03    1.53 
system.time(for(i in 1:ncol(mm)) mean(mm[, i]))
#   user  system elapsed 
#   1.22    0.00    1.21

질문의 주요 부분을 다시 정의하면 lapply/ mapply/ etc와 간단한 R- 루프 의 주요 차이점 은 루핑이 수행되는 위치입니다. Roland가 지적한 것처럼, C와 R 루프 모두 가장 비용이 많이 드는 각 반복에서 R 함수를 평가해야합니다. 정말 빠른 C 함수는 C의 모든 것을 수행하는 함수이므로 "벡터화"에 대한 것이어야한다고 생각합니다.

각 "목록"요소에서 평균을 찾는 예 :

( EDIT May 11 '16 : "평균"을 찾는 예제는 R 함수를 반복적으로 평가하는 코드와 컴파일 된 코드의 차이점에 대한 좋은 설정이 아니라고 생각합니다 (1) "숫자"에 대한 R의 평균 알고리즘의 특수성 때문에 간단한 통해 s의 sum(x) / length(x)그것과의 "리스트"또는 시험 더 이해하게한다 (2) length(x) >> lengths(x). 따라서, "평균"예 끝으로 이동하고 서로 대체된다.)

간단한 예로 우리 length == 1는 "목록" 의 각 요소의 반대 결과를 고려할 수 있습니다 .

A의 tmp.c파일 :

#include <R.h>
#define USE_RINTERNALS 
#include <Rinternals.h>
#include <Rdefines.h>

/* call a C function inside another */
double oppC(double x) { return(ISNAN(x) ? NA_REAL : -x); }
SEXP sapply_oppC(SEXP x)
{
    SEXP ans = PROTECT(allocVector(REALSXP, LENGTH(x)));
    for(int i = 0; i < LENGTH(x); i++) 
        REAL(ans)[i] = oppC(REAL(VECTOR_ELT(x, i))[0]);

    UNPROTECT(1);
    return(ans);
}

/* call an R function inside a C function;
 * will be used with 'f' as a closure and as a builtin */    
SEXP sapply_oppR(SEXP x, SEXP f)
{
    SEXP call = PROTECT(allocVector(LANGSXP, 2));
    SETCAR(call, install(CHAR(STRING_ELT(f, 0))));

    SEXP ans = PROTECT(allocVector(REALSXP, LENGTH(x)));     
    for(int i = 0; i < LENGTH(x); i++) { 
        SETCADR(call, VECTOR_ELT(x, i));
        REAL(ans)[i] = REAL(eval(call, R_GlobalEnv))[0];
    }

    UNPROTECT(2);
    return(ans);
}

그리고 R면에서 :

system("R CMD SHLIB /home/~/tmp.c")
dyn.load("/home/~/tmp.so")

데이터 포함 :

set.seed(007)
myls = rep_len(as.list(c(NA, runif(3))), 1e7)

#a closure wrapper of `-`
oppR = function(x) -x

for_oppR = compiler::cmpfun(function(x, f)
{
    f = match.fun(f)  
    ans = numeric(length(x))
    for(i in seq_along(x)) ans[[i]] = f(x[[i]])
    return(ans)
})

벤치마킹 :

#call a C function iteratively
system.time({ sapplyC =  .Call("sapply_oppC", myls) }) 
#   user  system elapsed 
#  0.048   0.000   0.047 

#evaluate an R closure iteratively
system.time({ sapplyRC =  .Call("sapply_oppR", myls, "oppR") }) 
#   user  system elapsed 
#  3.348   0.000   3.358 

#evaluate an R builtin iteratively
system.time({ sapplyRCprim =  .Call("sapply_oppR", myls, "-") }) 
#   user  system elapsed 
#  0.652   0.000   0.653 

#loop with a R closure
system.time({ forR = for_oppR(myls, "oppR") })
#   user  system elapsed 
#  4.396   0.000   4.409 

#loop with an R builtin
system.time({ forRprim = for_oppR(myls, "-") })
#   user  system elapsed 
#  1.908   0.000   1.913 

#for reference and testing 
system.time({ sapplyR = unlist(lapply(myls, oppR)) })
#   user  system elapsed 
#  7.080   0.068   7.170 
system.time({ sapplyRprim = unlist(lapply(myls, `-`)) }) 
#   user  system elapsed 
#  3.524   0.064   3.598 

all.equal(sapplyR, sapplyRprim)
#[1] TRUE 
all.equal(sapplyR, sapplyC)
#[1] TRUE
all.equal(sapplyR, sapplyRC)
#[1] TRUE
all.equal(sapplyR, sapplyRCprim)
#[1] TRUE
all.equal(sapplyR, forR)
#[1] TRUE
all.equal(sapplyR, forRprim)
#[1] TRUE

(원래 찾기의 원래 예를 따릅니다) :

#all computations in C
all_C = inline::cfunction(sig = c(R_ls = "list"), body = '
    SEXP tmp, ans;
    PROTECT(ans = allocVector(REALSXP, LENGTH(R_ls)));

    double *ptmp, *pans = REAL(ans);

    for(int i = 0; i < LENGTH(R_ls); i++) {
        pans[i] = 0.0;

        PROTECT(tmp = coerceVector(VECTOR_ELT(R_ls, i), REALSXP));
        ptmp = REAL(tmp);

        for(int j = 0; j < LENGTH(tmp); j++) pans[i] += ptmp[j];

        pans[i] /= LENGTH(tmp);

        UNPROTECT(1);
    }

    UNPROTECT(1);
    return(ans);
')

#a very simple `lapply(x, mean)`
C_and_R = inline::cfunction(sig = c(R_ls = "list"), body = '
    SEXP call, ans, ret;

    PROTECT(call = allocList(2));
    SET_TYPEOF(call, LANGSXP);
    SETCAR(call, install("mean"));

    PROTECT(ans = allocVector(VECSXP, LENGTH(R_ls)));
    PROTECT(ret = allocVector(REALSXP, LENGTH(ans)));

    for(int i = 0; i < LENGTH(R_ls); i++) {
        SETCADR(call, VECTOR_ELT(R_ls, i));
        SET_VECTOR_ELT(ans, i, eval(call, R_GlobalEnv));
    }

    double *pret = REAL(ret);
    for(int i = 0; i < LENGTH(ans); i++) pret[i] = REAL(VECTOR_ELT(ans, i))[0];

    UNPROTECT(3);
    return(ret);
')                    

R_lapply = function(x) unlist(lapply(x, mean))                       

R_loop = function(x) 
{
    ans = numeric(length(x))
    for(i in seq_along(x)) ans[i] = mean(x[[i]])
    return(ans)
} 

R_loopcmp = compiler::cmpfun(R_loop)


set.seed(007); myls = replicate(1e4, runif(1e3), simplify = FALSE)
all.equal(all_C(myls), C_and_R(myls))
#[1] TRUE
all.equal(all_C(myls), R_lapply(myls))
#[1] TRUE
all.equal(all_C(myls), R_loop(myls))
#[1] TRUE
all.equal(all_C(myls), R_loopcmp(myls))
#[1] TRUE

microbenchmark::microbenchmark(all_C(myls), 
                               C_and_R(myls), 
                               R_lapply(myls), 
                               R_loop(myls), 
                               R_loopcmp(myls), 
                               times = 15)
#Unit: milliseconds
#            expr       min        lq    median        uq      max neval
#     all_C(myls)  37.29183  38.19107  38.69359  39.58083  41.3861    15
#   C_and_R(myls) 117.21457 123.22044 124.58148 130.85513 169.6822    15
#  R_lapply(myls)  98.48009 103.80717 106.55519 109.54890 116.3150    15
#    R_loop(myls) 122.40367 130.85061 132.61378 138.53664 178.5128    15
# R_loopcmp(myls) 105.63228 111.38340 112.16781 115.68909 128.1976    15

10
data.frame을 행렬로 변환하는 비용과 벤치 마크를 제공해 주셔서 감사합니다.
Joshua Ulrich

비록 당신 all_CC_and_R함수 를 컴파일 할 수는 없었지만 매우 좋은 대답 입니다. 또한 실제 R 루프 를 포함 compiler::cmpfun하는 이전 R 버전의 lapply 문서에서 forBurns가 이후 벡터화 된 이전 버전을 참조하고 있다고 의심하기 시작 했으며 이것이 내 질문에 대한 실제 답변입니다. ..
David Arenburg

@DavidArenburg : 여전히 벤치마킹 la1은 기능 ?compiler::cmpfun을 제외한 모든 all_C기능에서 동일한 효율성을 제공 합니다. 나는 그것이 정의의 문제라고 생각한다. "벡터화"는 스칼라뿐만 아니라 C 코드가있는 함수, C에서만 계산을 사용하는 함수를 허용하는 함수를 의미합니까?
alexis_laz

1
R의 모든 함수에는 C 코드가 있다고 생각 합니다 .R의 모든 것이 함수 (일부 언어로 작성되어야 함) 이기 때문 입니다. 기본적으로, 내가 올바르게 이해하면 C 코드를 사용하는 반복 lapply에서 R 함수를 평가하기 때문에 단순히 벡터화되지 않는다고 말하는 것 입니까?
David Arenburg

5
@DavidArenburg : "벡터화"를 어떤 식 으로든 정의해야한다면 언어 적 접근법을 선택할 것입니다. 즉, 빠르고, 느리거나, C로 작성되었거나, R로 또는 기타로 "벡터"를 처리하는 방법을 받아들이고 알고있는 함수입니다. R에서 벡터화의 중요성은 많은 함수가 C로 작성되고 벡터를 처리하는 반면 다른 언어에서는 사용자가 일반적으로 입력을 반복하여 평균을 찾는 것입니다. 따라서 벡터화는 속도, 효율성, 안전성 및 견고성과 간접적으로 관련됩니다.
alexis_laz

65

나에게 벡터화는 주로 코드를 작성하고 이해하기 쉽게 만드는 것입니다.

벡터화 된 함수의 목표는 for 루프와 관련된 장부 관리를 제거하는 것입니다. 예를 들어,

means <- numeric(length(mtcars))
for (i in seq_along(mtcars)) {
  means[i] <- mean(mtcars[[i]])
}
sds <- numeric(length(mtcars))
for (i in seq_along(mtcars)) {
  sds[i] <- sd(mtcars[[i]])
}

당신은 쓸 수 있습니다:

means <- vapply(mtcars, mean, numeric(1))
sds   <- vapply(mtcars, sd, numeric(1))

따라서 동일한 내용 (입력 데이터)과 다른 내용 (적용중인 기능)을보다 쉽게 ​​확인할 수 있습니다.

벡터화의 두 번째 장점은 for 루프가 종종 R이 아닌 C로 작성된다는 것입니다. 이는 상당한 성능 이점이 있지만 이것이 벡터화의 주요 특성이라고 생각하지 않습니다. 벡터화는 기본적으로 컴퓨터 작업을 저장하는 것이 아니라 뇌를 절약하는 것입니다.


5
나는 C와 R for루프 사이에 의미있는 성능 차이가 있다고 생각하지 않습니다 . C 루프는 컴파일러에 의해 최적화 될 수 있지만 성능의 주요 요점은 루프의 내용이 효율적인지 여부입니다. 그리고 분명히 컴파일 된 코드는 일반적으로 해석 된 코드보다 빠릅니다. 그러나 그것은 아마도 당신이 말하려는 의도 일 것입니다.
Roland

3
@Roland 네, for-loop 자체가 아닙니다. 그것은 함수 호출 비용, 수정 능력 등의 모든 것입니다.
hadley

10
@DavidArenburg "불필요한 일관성은 작은 마음의
혼잡이다

6
아니요, 성능이 코드를 벡터화하는 주요 지점이라고 생각하지 않습니다. 루프를 랩으로 재 작성하는 것은 더 빠르지 않더라도 유리합니다. dplyr의 주요 요점은 데이터 조작을보다 쉽게 ​​표현할 수 있다는 것입니다. 또한 속도가 빠르다는 것도 아주 좋습니다.
hadley

12
@DavidArenburg는 숙련 된 R 사용자이기 때문입니다. 대부분의 신규 사용자는 루프가 훨씬 더 자연스럽고 벡터화를 장려해야합니다. 나에게 colMeans와 같은 함수를 사용하는 것이 반드시 벡터화에 관한 것은 아니며 누군가가 이미 작성한 빠른 코드를 재사용하는 것입니다.
hadley

49

나는 오히려 것을 패트릭 번즈보기에 동의 루프 숨어 아니라 코드 vectorisation . 이유는 다음과 같습니다.

C코드 스 니펫을 고려하십시오 .

for (int i=0; i<n; i++)
  c[i] = a[i] + b[i]

무엇을 우리가하고자하는 것은 매우 분명하다. 그러나 어떻게 작업이 수행 또는 어떻게 수행 할 수있는 것은 정말 아니다. 에 대한 루프 기본적으로는 직렬 구조입니다. 작업을 병렬로 수행 할 수 있는지 여부와 방법을 알려주지 않습니다.

가장 확실한 방법은 코드가 순차적으로 실행되는 것 입니다. 레지스터에 로드 a[i]하고 b[i]on하여 레지스터를 추가하고에 결과를 저장 한 다음 c[i]각에 대해이 작업을 수행하십시오 i.

그러나, 현대 프로세서는 동일한 동작을 수행 할 때 (예를 들어, 상기 도시 된 바와 같이 2 개의 벡터를 추가하는) 동일한 명령 동안 데이터벡터 에 대해 동작 할 수있는 벡터 또는 SIMD 명령 세트를 갖는다 . 프로세서 / 아키텍처에 따라서는, 추가 말, 네 개의 숫자에서 할 수있을 하고 한 번에 동일한 명령에 따라, 대신 중 하나.ab

예를 들어, 단일 명령어 다중 데이터 를 활용하고 데이터 레벨 병렬 처리를 수행 하려고합니다. 즉, 한 번에 4 개의 항목을로드하고 한 번에 4 개의 항목을 추가하고 한 번에 4 개의 항목을 저장합니다. 그리고 이것은 코드 벡터화 입니다.

이는 여러 계산을 동시에 수행하는 코드 병렬화와 다릅니다.

컴파일러가 그러한 코드 블록을 식별하고 자동으로 벡터화하는 것이 좋을 것 입니다. 이는 어려운 작업입니다. 자동 코드 벡터화 는 컴퓨터 과학의 까다로운 연구 주제입니다. 그러나 시간이 지남에 따라 컴파일러가 더 좋아졌습니다. 여기서 자동 벡터화 기능을 확인할 수 있습니다 . 마찬가지로 여기에 . 또한 마지막 링크에서 (Intel C ++ 컴파일러) 와 비교 한 일부 벤치 마크를 찾을 수 있습니다 .GNU-gcc LLVM-clang gccICC

gcc(I가있어 v4.9예를 들어 것은)에서 자동으로 코드 벡터화하지 않는 -O2수준의 최적화. 따라서 위에 표시된 코드를 실행하면 순차적으로 실행됩니다. 다음은 길이 5 억의 정수 벡터 두 개를 추가하는 타이밍입니다.

플래그를 추가 -ftree-vectorize하거나 최적화를 level로 변경해야합니다 -O3. ( 다른 추가 최적화-O3수행합니다 .) 이 플래그 는 루프가 성공적으로 벡터화되었을 때 알려주므로 유용합니다.-fopt-info-vec

# compiling with -O2, -ftree-vectorize and  -fopt-info-vec
# test.c:32:5: note: loop vectorized
# test.c:32:5: note: loop versioned for vectorization because of possible aliasing
# test.c:32:5: note: loop peeled for vectorization to enhance alignment    

함수가 벡터화되었음을 알려줍니다. 다음은 길이가 5 억인 정수 벡터에서 벡터화되지 않은 버전과 벡터화 된 버전을 비교하는 타이밍입니다.

x = sample(100L, 500e6L, TRUE)
y = sample(100L, 500e6L, TRUE)
z = vector("integer", 500e6L) # result vector

# non-vectorised, -O2
system.time(.Call("Csum", x, y, z))
#    user  system elapsed 
#   1.830   0.009   1.852

# vectorised using flags shown above at -O2
system.time(.Call("Csum", x, y, z))
#    user  system elapsed 
#   0.361   0.001   0.362

# both results are checked for identicalness, returns TRUE

연속성을 잃지 않고이 부분을 안전하게 건너 뛸 수 있습니다.

컴파일러에는 항상 벡터화하기에 충분한 정보가있는 것은 아닙니다. 병렬 프로그래밍을 위해 OpenMP 사양을 사용할 수 있으며 , 컴파일러에게 코드를 벡터화하도록 지시 하는 simd 컴파일러 지시문 도 제공합니다 . 코드를 수동으로 벡터화 할 때 메모리 오버랩, 경쟁 조건 등이 없는지 확인해야합니다. 그렇지 않으면 잘못된 결과가 발생합니다.

#pragma omp simd
for (i=0; i<n; i++) 
  c[i] = a[i] + b[i]

이를 통해 컴파일러는 무엇이든 상관없이 벡터화하도록 요청합니다. 컴파일 타임 플래그를 사용하여 OpenMP 확장을 활성화해야합니다 -fopenmp. 그렇게함으로써 :

# timing with -O2 + OpenMP with simd
x = sample(100L, 500e6L, TRUE)
y = sample(100L, 500e6L, TRUE)
z = vector("integer", 500e6L) # result vector
system.time(.Call("Cvecsum", x, y, z))
#    user  system elapsed 
#   0.360   0.001   0.360

대단해! 이것은 gcc v6.2.0 및 llvm clang v3.9.0 (둘 다 Homebrew, MacOS 10.12.3을 통해 설치됨)으로 테스트되었으며 둘 다 OpenMP 4.0을 지원합니다.


이러한 의미에서, 비록 배열 프로그래밍에 대한 위키 백과 페이지가 전체 어레이에서 작동하는 언어를 언급 일반적으로한다는 전화를 벡터화 작업 , 그것은 정말 루프 숨어 (가 실제로 벡터화되지 않는 한) IMO.

R의 경우, 짝수 rowSums()또는 colSums()C의 코드는 코드 벡터화 IIUC를 이용하지 않습니다 . 그것은 단지 C의 루프입니다 lapply(). 의 경우 apply()R에 있습니다. 따라서 모두 루프 숨기기 입니다.

간단히 말해서 R 함수를 다음과 같이 래핑합니다.

코드를 벡터화하기 위해 for 루프 를 작성 C하십시오! 코드를 벡터화하기 위해 for 루프
작성 하십시오!R

예를 들어 인텔 수학 커널 라이브러리 (MKL) 는 벡터화 된 형태의 함수를 구현합니다.

HTH


참고 문헌 :

  1. Intel, James Reinders의 대화 (이 답변은 주로이 뛰어난 대화를 요약하려는 시도입니다)

35

따라서 큰 답변 / 의견을 일반적인 답변으로 요약하고 배경을 제공하십시오 .R에는 4 가지 유형의 루프가 있습니다 ( 벡터화되지 않은 순서에서 벡터화 된 순서로 )

  1. for각 반복에서 R 함수를 반복적으로 호출하는 R 루프 ( 벡터화되지 않음 )
  2. 각 반복에서 R 함수를 반복적으로 호출하는 C 루프 ( 벡터화되지 않음 )
  3. R 함수를 한 번만 호출하는 C 루프 ( 약간 벡터화 됨 )
  4. 호출하지 않는 일반 C 루프 어떤 모두에서 R 기능을하고 컴파일 된 함수를 소유하고 사용 ( 벡터화 )

그래서 *apply가족은 두 번째 유형입니다. apply첫 번째 유형 중 더 많은 것을 제외하고

소스 코드 의 주석에서 이것을 이해할 수 있습니다.

/ *. 내부 (lapply (X, FUN)) * /

/ * 이것은 특별한 .Internal이므로 평가되지 않은 인수가 있습니다. 되는
X과 재미가 약속 그래서, 폐쇄 래퍼에서 호출. bquote 등에서 사용하려면 FUN을 평가하지 않아야합니다. * /

즉, lapplys C 코드는 R의 평가되지 않은 함수를 수락하고 나중에 C 코드 자체에서이를 평가합니다. 이것은 기본적으로 차이입니다 lapply님의 .Internal전화

.Internal(lapply(X, FUN))

가 어떤 FUN에 R 기능을 보유하고 인수를

그리고 colMeans .Internal호출하는이 되지 않습니다FUN인수를

.Internal(colMeans(Re(x), n, prod(dn), na.rm))

colMeans사용하는 함수를 정확히lapply 아는 것과 달리 C 코드 내에서 평균을 내부적으로 계산합니다.

C 코드 내의 각 반복에서 R 함수의 평가 프로세스를 명확하게 볼 수 있습니다lapply

 for(R_xlen_t i = 0; i < n; i++) {
      if (realIndx) REAL(ind)[0] = (double)(i + 1);
      else INTEGER(ind)[0] = (int)(i + 1);
      tmp = eval(R_fcall, rho);   // <----------------------------- here it is
      if (MAYBE_REFERENCED(tmp)) tmp = lazy_duplicate(tmp);
      SET_VECTOR_ELT(ans, i, tmp);
   }

요약 하면 일반화 R 루프에 비해 두 가지 가능한 장점이 있지만 lapply벡터화되지는 않습니다.for

  1. 루프에서 액세스하고 할당하는 것은 C에서 더 빠릅니다 (즉 lapply, 함수를 수행하는 것). 비록 차이는 크지 만 여전히 마이크로 초 수준을 유지하고 비용이 많이 드는 것은 각 반복에서 R 함수의 평가입니다. 간단한 예 :

    ffR = function(x)  {
        ans = vector("list", length(x))
        for(i in seq_along(x)) ans[[i]] = x[[i]]
        ans 
    }
    
    ffC = inline::cfunction(sig = c(R_x = "data.frame"), body = '
        SEXP ans;
        PROTECT(ans = allocVector(VECSXP, LENGTH(R_x)));
        for(int i = 0; i < LENGTH(R_x); i++) 
               SET_VECTOR_ELT(ans, i, VECTOR_ELT(R_x, i));
        UNPROTECT(1);
        return(ans); 
    ')
    
    set.seed(007) 
    myls = replicate(1e3, runif(1e3), simplify = FALSE)     
    mydf = as.data.frame(myls)
    
    all.equal(ffR(myls), ffC(myls))
    #[1] TRUE 
    all.equal(ffR(mydf), ffC(mydf))
    #[1] TRUE
    
    microbenchmark::microbenchmark(ffR(myls), ffC(myls), 
                                   ffR(mydf), ffC(mydf),
                                   times = 30)
    #Unit: microseconds
    #      expr       min        lq    median        uq       max neval
    # ffR(myls)  3933.764  3975.076  4073.540  5121.045 32956.580    30
    # ffC(myls)    12.553    12.934    16.695    18.210    19.481    30
    # ffR(mydf) 14799.340 15095.677 15661.889 16129.689 18439.908    30
    # ffC(mydf)    12.599    13.068    15.835    18.402    20.509    30
  2. @Roland에서 언급했듯이 해석 된 R 루프 대신 컴파일 된 C 루프를 실행합니다.


코드를 벡터화 할 때 고려해야 할 사항이 있습니다.

  1. 데이터 세트 (하자 전화가있는 경우 df) 클래스이며 data.frame, 일부 벡터화 기능 (예 : colMeans, colSums, rowSums이 그들이 디자인 된 방법이기 때문에, 등), 첫 번째 행렬로 변환해야합니다. 이는 큰 df경우 큰 오버 헤드를 생성 할 수 있음을 의미합니다 . 하지만 lapply이 중 실제 벡터를 추출로이 작업을 수행 할 필요가 없습니다 df(같은 data.frame당신이 많은 열하지만 많은 행하지 그래서이있는 경우, 따라서 벡터의 단지 목록입니다)하고, lapply(df, mean)때로는보다 더 나은 옵션이 될 수 있습니다 colMeans(df).
  2. 기억해야 할 또 다른 사항은 R에는 .Primitive, 및 generic ( S3, S4) 과 같은 다양한 기능 유형이 있으며 여기 에서 추가 정보를 참조 하십시오 . 일반 함수는 때로는 값 비싼 작업 인 메소드 디스패치를 ​​수행해야합니다. 예를 들어 meanis은 일반 S3함수 sum입니다 Primitive. 따라서 위에 나열된 이유 lapply(df, sum)와 비교 colSums하여 시간 이 매우 효율적일 수 있습니다.

1
매우 응집력있는 요약. (1) C는 "data.frame"이 속성이있는 "목록"이므로 "data.frame"을 처리하는 방법을 알고 있습니다. colMeans행렬 만 처리하도록 만들어진 것이 그 등입니다. (2) 나는 당신의 세 번째 범주에 약간 혼란스러워합니다. 나는 당신이 무엇을 말하는지 알 수 없습니다. (3) 당신이 구체적으로 언급하고 있기 때문에 lapply, 나는 그것이 "[<-"R과 C에서 차이를 만들지 않는다고 생각합니다 . 그들은 당신의 요점이 빠지지 않는 한 "목록"(SEXP)을 미리 할당하고 각 반복 ( SET_VECTOR_ELTC 에서)으로 채 웁니다 .
alexis_laz

2
do.callC 환경에서 함수 호출을 작성하고 평가한다는 점 에서 요점 을 알 수 있습니다. 다른 작업을 수행하기 때문에 루프 또는 벡터화와 비교하기가 어렵습니다. 실제로 C와 R 사이의 차이에 액세스하고 할당하는 것에 대해서는 옳습니다.하지만 둘 다 마이크로 초 수준으로 유지하고 반복적 인 R 함수 호출 비용이 많이 들기 때문에 결과에 큰 영향을 미치지 않습니다 (비교 R_loopR_lapply내 대답) ). (나는 벤치 마크로 게시물을 편집 할 것입니다, 당신은 여전히 ​​걱정하지 않기를 바랍니다)
alexis_laz

2
나는 동의하지 않으려 고 노력하지 않으며, 당신이 동의하지 않는 것에 대해 정직하게 혼란스러워합니다. 내 이전 의견은 더 잘 표현 될 수있었습니다. "벡터화"라는 용어에는 종종 얽히는 두 가지 정의가 있기 때문에 사용되는 용어를 수정하려고합니다. 나는 이것이 논쟁의 여지가 없다고 생각합니다. 번즈와 구현의 의미에서만 사용하고 싶지만 Hadley와 많은 R-Core 멤버 ( Vectorize()예를 들어)는 UI 의미에서도이를 사용합니다. 이 스레드에서 많은 의견 차이는 두 가지 분리 된 관련 개념에 대해 하나의 용어를 사용함으로써 발생한다고 생각합니다.
Gregor Thomas

3
@DavidArenburg 그리고 R 또는 C 아래에 for 루프가 있는지 여부에 관계없이 UI 의미에서 벡터화되지 않습니까?
Gregor Thomas

2
@DavidArenburg, Gregor 저는 혼란이 "코드 벡터화"와 "벡터화 함수"사이에 있다고 생각합니다. R에서는 사용법이 후자에 대한 경향이 있습니다. "코드 벡터화"는 동일한 명령어에서 길이 'k'의 벡터에서 작동하는 것을 설명합니다. fn 포장. 루프 코드 주위에서 "벡터화 된 함수"가 발생합니다 (예, 말이 안되고 혼란 스럽습니다. 루프 숨기기 또는 벡터 i / p 함수 가 더 좋을 것입니다 ) . 코드 벡터화 와 관련이 없습니다 . R에서 apply는 벡터화 된 함수 이지만 코드를 벡터화하지 않고 벡터에서 작동합니다.
Arun
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.