“ * apply”家庭真的没有向量化吗?


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执行不劣于colMeans和更好的比for一个大的数据集循环

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 

换句话说,这样说lapplyvapply 实际上是矢量化的(与apply哪个for也调用的循环相比lapply)是正确的吗,Patrick Burns的真正意思是说什么?


8
这全是语义上的,但我不会认为它们是矢量化的。我认为,如果R函数仅被调用一次并且可以传递值的向量,则该方法是向量化的。*apply函数反复调用R函数,这使它们循环。关于:的良好性能sapply(m, mean)lapply方法的C代码是否可能仅调度一次,然后重复调用该方法?mean.default非常优化。
罗兰

4
很好的问题,感谢您检查基础代码。我正在查看它是否最近已更改,但是从2.13.0版开始的R发行说明中对此没有任何了解。
ilir 2015年

1
性能在多大程度上取决于平台以及所使用的C编译器和链接器标志?
smci 2015年

3
@DavidArenburg实际上,我认为定义不明确。至少我不知道规范的参考。语言定义提到“矢量化”操作,但没有定义矢量化。
罗兰2015年

3
密切相关:R的适用范围是否比句法糖更重要?(而且,像这些答案一样,也是不错的读物。)
Gregor Thomas

Answers:


73

首先,在你的榜样,你做一个“data.frame”,这是不公平的测试colMeansapply并且"[.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中完成所有功能的函数,所以,我想,这应该是“向量化”的含义吗?

我们在每个“列表”元素中找到均值的示例:

编辑,2016年5月11日:我相信找到“均值”的示例对于迭代评估R函数和编译后的代码之间的差异不是一个很好的设置,(1)由于R的均值算法在“数值”上的特殊性通过一个简单的sum(x) / length(x)(2)来用来测试“列表”应该更有意义length(x) >> lengths(x)。因此,“均值”示例移至最后并替换为另一个。)

作为一个简单的示例,我们可以考虑找到length == 1“列表” 中每个元素的相反元素:

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'3

这是一个很好的答案,尽管我无法编译您的all_CC_and_R函数。我的单证也发现compiler::cmpfun一个lapply的旧版本[R包含一个实际的[R for循环,我开始怀疑,伯恩斯指的自那以后这是矢量化旧版本,这是实际的回答我的问题.. ..
David Arenburg

@DavidArenburg:标杆la1?compiler::cmpfun看来,仍然,产生相同的效率,但所有all_C功能。我想,这确实是一个定义问题。“矢量化”是指任何不仅接受标量的函数,任何具有C代码的函数,仅使用C语言进行计算的任何函数?
alexis_laz 2015年

1
我猜R中的所有函数都包含C代码,这仅仅是因为R中的所有都是函数(必须用某种语言编写)。因此,基本上,如果我理解正确,您是说lapply不是对向量进行简单化,因为它在每次迭代中都使用C代码评估R函数吗?
David Arenburg

5
@DavidArenburg:如果我必须以某种方式定义“向量化”,我想我会选择一种语言方法;也就是说,一个接受并知道如何处理“向量”的函数,无论它是快速,慢速,以C语言编写,以R语言编写还是其他形式。在R中,向量化的重要性在于许多函数都用C编写并处理向量,而在其他语言中,用户通常会遍历输入以找到均值。这使得矢量化与速度,效率,安全性和鲁棒性间接相关。
alexis_laz 2015年

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循环通常用C而不是R编写。这具有显着的性能优势,但我认为这不是向量化的关键特性。向量化从根本上讲是要节省大脑,而不是节省计算机工作量。


5
我认为C和R for循环之间没有有意义的性能差异。可以,编译器可能会优化C循环,但是性能的重点是循环的内容是否有效。显然,编译后的代码通常比解释后的代码要快。但这可能就是您想说的。
罗兰

3
@Roland是的,它本身并不是for循环本身,而是它周围的所有东西(函数调用的成本,就地修改的能力,等等​​)。
哈德利2015年

10
@DavidArenburg“不需要保持一致就是小头脑的
妖精

6
不,我认为性能不是矢量化代码的重点。将循环重写为一个快活是有益的,即使循环速度不快。dplyr的要点是,它使表达数据操作更容易(而且非常高兴,它也很快)。
哈德利2015年

12
@DavidArenburg是因为您是经验丰富的R用户。大多数新用户发现循环更为自然,因此需要鼓励对其进行矢量化。对我来说,使用像colMeans这样的函数不一定与矢量化有关,而是与重用某人已经编写的快速代码有关
hadley 2015年

49

我同意Patrick Burns的观点,即循环隐藏而不是代码矢量化。原因如下:

考虑以下C代码片段:

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

什么我们希望做的是相当清楚的。但是,如何执行任务或如何执行任务并不是真的。一个for循环在默认情况下是串行结构。它不会告知是否或如何并行完成操作。

最明显的方式是代码以顺序方式运行。加载a[i]并加载b[i]到寄存器,添加它们,将结果存储在中c[i],然后对每个寄存器执行此操作i

然而,现代处理器具有向量SIMD指令集,当执行相同操作时,该向量SIMD指令集能够在相同指令期间对数据向量进行操作(例如,如上所示将两个向量相加)。视处理器/体系结构而定,可能有可能在同一条指令中并在同一条指令下添加四个数字,而不是一次添加一个。ab

我们想利用单指令多数据并执行数据级并行性,例如,一次加载4个东西,一次添加4个东西,一次存储4个东西。这就是代码向量化

请注意,这不同于代码并行化-代码并行化是同时执行多个计算的。

如果编译器识别出此类代码块并自动将其矢量化,那就太好了,这是一项艰巨的任务。自动代码矢量化是计算机科学中一个具有挑战性的研究主题。但是随着时间的推移,编译器在此方面做得更好。您可以在此处检查自动矢量化功能。同样在这里。您还可以在最后一个链接中找到与和进行比较的基准(英特尔C ++编译器)。GNU-gcc LLVM-clang gccICC

gccv4.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(均通过自制软件MacOS 10.12.3进行安装)进行了测试,两者均支持OpenMP 4.0。


从这个意义上讲,尽管Array Programming上的Wikipedia页面提到在整个阵列上运行的语言通常将其称为矢量化操作,但这实际上是对IMO的循环隐藏(除非它实际上是矢量化的)。

在R的情况下,C中的偶数rowSums()colSums()代码不使用代码矢量化 IIUC;它只是C中的一个循环lapply()。在这种情况下apply(),它在R中。因此所有这些都被循环隐藏

简而言之,通过以下方式包装R函数:

只是在!=向量化代码中编写for循环C
只是在!=向量化代码中编写for循环R

例如,英特尔数学内核库(MKL)实现了矢量形式的函数。

高温超导


参考文献:

  1. 英特尔James Reinders的演讲(此答案主要是试图总结这个出色的演讲)

35

因此,将重要的答案/评论总结为一些通用的答案并提供一些背景知识:R具有4种类型的循环(从非向量化到向量化顺序

  1. R for循环在每次迭代中反复调用R函数(未向量化
  2. 在每个迭代中反复调用R函数的C循环(未向量化
  3. 仅调用R函数一次的C循环(有点矢量化
  4. 一个普通的C循环,它根本不会调用任何 R函数,而是使用它自己的编译函数(Vectorized

所以*apply家庭是第二种。除非apply是第一种

您可以从其源代码中的注释中了解这一点

/ *。内部(lapply(X,FUN))* /

/ *这是一个特殊的.Internal,因此未评估的参数也是如此。
从闭包中调用它,因此X和FUN是应许。FUN必须未经评估才能用于bquote。* /

这意味着lapplys C代码从R接受一个未求值的函数,然后在C代码本身中对其求值。这基本上是lapplys .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);
   }

总结起来,lapply不是向量化的,尽管它比普通的R 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所述,它运行一个已编译的C循环,而不是一个解释的R循环


尽管在对代码进行矢量化处理时,仍需要考虑一些事项。

  1. 如果您的数据集(姑且称之为df)是一流的data.frame,一些矢量化功能(如colMeanscolSumsrowSums等),必须把它转换到一个矩阵,只是因为这是他们是如何设计的。这意味着对于一个大型企业,df这可能会产生巨大的开销。虽然lapply不必这样做,因为它会从中提取实际的向量df(就像data.frame向量列表一样),因此,如果您没有那么多列但有很多行,lapply(df, mean)有时可能会比更好colMeans(df)
  2. 另一件要记住的是,R有很多种不同的功能类型,如.Primitive,和通用(S3S4)看到这里的一些额外的信息。泛型函数必须执行方法分派,有时这是一项昂贵的操作。例如,meanis是泛型S3函数,而sumis是Primitive。因此,根据上述原因,某些时间lapply(df, sum)可能会非常有效colSums

1
非常有凝聚力的总结。仅需注意以下几点:(1)C知道如何处理“ data.frame”,因为它们是具有属性的“列表”;那就是colMeans等,它们只建了处理矩阵。(2)我对您的第三类感到有些困惑;我不知道您指的是什么。(3)由于您是专门指lapply,我相信"[<-"R和C 之间没有区别;他们都会预先分配一个“列表”(一个SEXP),并在每次迭代中填充它(SET_VECTOR_ELT用C语言编写),除非我遗漏了您的意思。
alexis_laz 2015年

2
我的意思do.call是,它在C环境中构建了一个函数调用并对其求值;尽管我很难将其与循环或矢量化进行比较,因为它做的是不同的事情。实际上,访问和分配C和R之间的差异是正确的,尽管两者都停留在微秒级,并且不会对结果造成很大的影响,因为代价是R函数的迭代调用(比较R_loopR_lapply在我的回答中)。(我将使用基准测试来编辑您的帖子;希望您仍然不介意)
alexis_laz 2015年

2
我并不是想不同意-老实说,我对您不同意感到困惑。我先前的评论本来可以用更好的措词表达。我正在尝试完善所使用的术语,因为术语“向量化”具有两个经常被混淆的定义。我不认为这是有争议的。Burns,您似乎只想在实现的意义上使用它,但是Hadley和许多R-Core成员(以Vectorize()示例为例)也在UI意义上使用它。我认为,此主题中的许多分歧是由于对两个单独但相关的概念使用一个术语引起的。
格雷戈尔·托马斯

3
@DavidArenburg,这是否不是UI意义上的矢量化,而不管其下的R或C中是否存在for循环?
格雷戈尔·托马斯

2
@ DavidArenburg,Gregor,我认为混淆是在“代码向量化”和“向量化函数”之间。在R中,用法似乎倾向于后者。“代码向量化”描述了在同一指令中对长度为“ k”的向量进行操作。包装一个fn。围绕循环代码会产生“向量化函数”(是的,我认为这没有意义,而且令人困惑,最好是循环隐藏向量i / p函数),并且与代码向量化无关。在R中,apply将是一个向量化函数,但是它不会向量化您的代码,而是对向量进行运算。
阿伦(Arun)2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.