“* Uygula” ailesi gerçekten vektörlenmemiş mi?


138

Bu yüzden her R yeni kullanıcıya " applyvectorized değil, Patrick Burns R Inferno Circle 4'e göz atın " diyerek alışkınız :

Yaygın bir refleks, uygulama ailesinde bir işlev kullanmaktır. Bu vektörleşme değil, döngü gizleme . Apply işlevinin tanımında bir for döngüsü vardır. Laponly işlevi döngüyü gömer, ancak yürütme süreleri kabaca açık bir döngüye eşit olma eğilimindedir.

Gerçekten de, applykaynak koduna hızlı bir bakış döngüyü ortaya çıkarır:

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

Şimdiye kadar tamam, ancak tamamen farklı bir resme bakın lapplyveya vapplygerçekten ortaya koyun:

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>

Görünüşe göre orada forsaklanan R döngüsü yok, bunun yerine dahili C yazılı işlevi çağırıyorlar.

Tavşan deliğine hızlı bir bakış , aynı resmi hemen hemen ortaya koyuyor

Dahası, colMeansörneğin hiçbir zaman vektörleştirilmemekle suçlanan işlevi ele alalım

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>

Ha? Aynı zamanda tavşan deliğinde.Internal(colMeans(... de bulabileceğimiz çağrılar . Peki bunun farkı nedir?.Internal(lapply(..

Aslında hızlı bir karşılaştırma, büyük bir veri kümesi için bir döngüden daha sapplykötü colMeansve daha iyi performans göstermediğini ortaya koyuyorfor

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 

Başka bir deyişle, bunu söylemek doğru mudurlapply ve vapply aslında vektörize edilmiştir ( applyhangisi forçağıran bir döngü ile karşılaştırıldığında lapply) ve Patrick Burns gerçekten ne demek istiyordu?


8
Bunların hepsi anlambilimdedir, ancak bunların vektörleştirilmiş olduğunu düşünmem. Bir R fonksiyonu sadece bir kez çağrıldığında ve bir değerler vektörü geçirilebildiğinde vektörleştirilmiş bir yaklaşımı düşünürüm. *applyfonksiyonları art arda R fonksiyonlarını çağırır, bu da onları döngüler haline getirir. Aşağıdakilerin iyi performansıyla ilgili olarak sapply(m, mean): Muhtemelen C-kodu lapplyyöntemi sadece bir kez gönderilir ve daha sonra yöntemi tekrar tekrar çağırır mı? mean.defaultoldukça optimize edilmiştir.
Roland

4
Mükemmel bir soru ve altta yatan kodu kontrol ettiğiniz için teşekkürler. Son zamanlarda değiştirilip değiştirilmediğine bakıyordum, ancak sürüm 2.13.0'dan itibaren R sürüm notlarında bununla ilgili hiçbir şey yoktu.
ilir

1
Performans ne ölçüde platforma ve kullanılan C-derleyici ve bağlayıcı bayraklarına bağlıdır?
15'te smci

3
@DavidArenburg Aslında iyi tanımlandığını sanmıyorum. En azından kanonik bir referans bilmiyorum. Dil tanımı "vectorized" işlemlerinden bahseder, ancak vectorization'u tanımlamaz.
Roland

3
Çok ilgili: R'ler uygulama ailesini sözdizimsel şekerden daha mı fazla? (Ve bu cevaplar gibi, iyi bir okuma.)
Gregor Thomas

Yanıtlar:


73

Her şeyden önce, sizin örnekte sizin için adil olmayan bir "data.frame" üzerinde testler yapmak colMeans, applyve "[.data.frame"onlar bir yükü beri:

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

Bir matriste, resim biraz farklıdır:

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

Sorunun ana kısmına baktığımızda, lapply/ mapply/ etc ve basit R-ilmekleri arasındaki temel fark, döngülerin yapıldığı yerdir. Roland'ın belirttiği gibi, hem C hem de R döngülerinin her yinelemede en pahalı olan bir R işlevini değerlendirmesi gerekir. Gerçekten hızlı C fonksiyonları C her şeyi yapan, yani, sanırım, bu "vectorized" ne hakkında olmalıdır?

"Liste" öğelerinin her birinde ortalamayı bulduğumuz bir örnek:

( EDIT 11 Mayıs 1616 : "Ortalama" bulma örneğinin, R'nin "sayısal" üzerindeki ortalama algoritmasının özgüllüğü nedeniyle yinelenen bir R işlevini ve derlenmiş kodu değerlendirme arasındaki farklar için iyi bir kurulum olmadığına inanıyorum. basit sum(x) / length(x)ve (2) ile "liste" ler üzerinde test etmek daha mantıklı olmalıdır length(x) >> lengths(x). Böylece, "ortalama" örnek sonuna taşınır ve bir başkası ile değiştirilir.)

Basit bir örnek olarak length == 1, bir "liste" nin her bir öğesinin zıttı bulmayı düşünebiliriz :

Bir tmp.cdosyada:

#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);
}

Ve R tarafında:

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

verilerle:

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)
})

Kıyaslama:

#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

(Ortalama bulgu bulmanın orijinal örneğini takip eder):

#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'i bir matrise dönüştürme maliyetleri hakkında harika bir nokta ve karşılaştırmalı değerlendirme sağladığınız için teşekkürler.
Joshua Ulrich

Bu çok güzel bir cevap, ama senin all_Cve C_and_Rfonksiyonlarını derleyemedim . Ben de belgele bulunan compiler::cmpfunbir lapply eski R versiyonunda gerçek Ar içeren fordöngü, ben Burns atıfta bulundu şüphelenmeye başladım o zamandan beri vektörleştirilmiş edildi eski sürümü ve bu Sorumun gerçek cevabı .. ..
David Arenburg

@DavidArenburg: Kıyaslama la1dan ?compiler::cmpfungörünüyor, hala tüm ama aynı verimi elde etmek için all_Cfonksiyonlar. Sanırım, bu - bir tanım meselesi haline geliyor; "vectorized" sadece skaleri kabul eden herhangi bir fonksiyon, C kodu olan herhangi bir fonksiyon, sadece C'de hesaplamaları kullanan herhangi bir fonksiyon anlamına mı geliyor?
alexis_laz

1
Sanırım R'deki tüm fonksiyonların içinde C kodu var, çünkü R'deki her şey bir işlevdir (bazı dillerde yazılması gerekiyordu). Temel olarak, eğer doğru anlıyorsam, bunun lapplysadece C kodu ile her yinelemede bir R fonksiyonunu değerlendirdiği için vektörize edilmediğini mi söylüyorsunuz ?
David Arenburg

5
@DavidArenburg: "Vektörleşmeyi" bir şekilde tanımlamam gerekirse, sanırım, dilsel bir yaklaşım seçerdim; yani hızlı, yavaş, C, R veya başka herhangi bir şeyle yazılmış bir "vektör" ü nasıl kabul edeceğini ve işleyeceğini bilen bir işlev. R'de vektörleştirmenin önemi, C'de birçok fonksiyonun yazılması ve vektörlerin ele alınması, diğer dillerde kullanıcılar genellikle -eg- ortalamasını bulmak için girişin üzerinde dolaşır. Bu, vektörleşmeyi dolaylı olarak hız, verimlilik, güvenlik ve sağlamlıkla ilişkilendirir.
alexis_laz

65

Benim için, vektörleştirme öncelikle kodunuzun yazılmasını ve anlaşılmasını kolaylaştırmakla ilgilidir.

Vektörize edilmiş bir fonksiyonun amacı, bir for döngüsü ile ilişkili defter tutmayı ortadan kaldırmaktır. Örneğin:

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]])
}

Yazabilirsin:

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

Bu, aynı olanı (giriş verileri) ve farklı olanı (uyguladığınız işlev) görmeyi kolaylaştırır.

Vektörizasyonun ikincil bir avantajı, for-döngüsünün R'den ziyade genellikle C ile yazılmış olmasıdır. Bu, önemli performans avantajlarına sahiptir, ancak bunun vektörleşmenin temel özelliği olduğunu düşünmüyorum. Vektörizasyon temelde beyninizi kurtarmak, bilgisayar çalışmalarını kurtarmak değil.


5
C ve R fordöngüleri arasında anlamlı bir performans farkı olduğunu düşünmüyorum . Tamam, bir C döngüsü derleyici tarafından optimize edilebilir, ancak performansın ana noktası döngü içeriğinin verimli olup olmadığıdır. Ve açıkça derlenen kod genellikle yorumlanan koddan daha hızlıdır. Ama muhtemelen bunu söylemek istedin.
Roland

3
@Roland evet, bu for-loop'un kendisi değil, etrafındaki her şey (bir işlev çağrısının maliyeti, yerinde değişiklik yapma yeteneği, ...).
hadley

10
@DavidArenburg "Gereksiz kıvam küçük zihinlerin hobgoblinidir";)
hadley

6
Hayır, performansın kodunuzu vektörleştirmenin ana noktası olduğunu düşünmüyorum. Bir döngüyü lapply'ye yeniden yazmak, daha hızlı olmasa bile faydalıdır. Dplyr'in ana noktası, veri manipülasyonunu ifade etmeyi kolaylaştırmasıdır (ve hızlı olması da çok güzeldir).
hadley

12
@DavidArenburg, deneyimli bir R kullanıcısı olmanızdır. Çoğu yeni kullanıcı, döngüleri çok daha doğal bulur ve vektörel hale getirmeye teşvik edilmesi gerekir. Bana göre, colMeans gibi bir işlevi kullanmak mutlaka vektörizasyonla ilgili değil, daha önce yazmış olduğu hızlı kodu tekrar kullanmakla ilgili
hadley

49

Ben oldukça olduğunu Patrick Burns'ün görüşüne katılıyorum döngü gizleme ve yok kod vectorisation . İşte nedeni:

Bu Ckod snippet'ini düşünün :

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

Yapmak istediğimiz şey oldukça açık. Ancak görevin nasıl yerine getirildiği veya nasıl gerçekleştirilebileceği gerçekten değil. Bir for-loop varsayılan olarak bir seri yapıdır. İşlerin paralel olarak yapılıp yapılamayacağı veya nasıl yapılabileceği konusunda bilgi sahibi değildir.

En belirgin yol, kodun sıralı bir şekilde çalıştırılmasıdır . Kayıtları yükleyin a[i]ve açın b[i], ekleyin, sonucu saklayın c[i]ve her biri için bunu yapın i.

Bununla birlikte, modern işlemciler aynı işlemi gerçekleştirirken aynı talimat sırasında bir veri vektörü üzerinde çalışabilen vektör veya SIMD komut setine sahiptir (örneğin, yukarıda gösterildiği gibi iki vektör eklenir). İşlemci / mimarisine bağlı olarak, eklemek demek, dört sayı dan mümkün olabilir ve bir anda aynı talimat altında bir yerine.ab

Tek Talimatlı Çoklu Verilerden yararlanmak ve veri seviyesi paralelliğini gerçekleştirmek istiyoruz , örneğin, bir seferde 4 şey yükleyin, bir seferde 4 şey ekleyin, örneğin bir seferde 4 şey saklayın. Ve bu kod vektörleştirmedir .

Bunun, birden fazla hesaplama aynı anda gerçekleştirildiği kod paralelleştirmesinden farklı olduğuna dikkat edin.

Derleyicinin bu tür kod bloklarını tanımlaması ve otomatik olarak vektörleştirmesi harika bir iştir. Otomatik kod vektörleştirme , Bilgisayar Bilimi'nde zorlu bir araştırma konusudur. Ancak zamanla, derleyiciler daha iyi hale geldi. Buradaki otomatik vektörizasyon özelliklerini kontrol edebilirsiniz . Benzer şekilde burada . Ayrıca son bağlantıda ve (Intel C ++ derleyicisi) ile karşılaştırıldığında bazı karşılaştırmalar bulabilirsiniz .GNU-gcc LLVM-clang gccICC

gcc( v4.9Açıkım) örneğin -O2seviye optimizasyonunda kodu otomatik olarak vektörleştirmez . Dolayısıyla, yukarıda gösterilen kodu yürütürsek, sıralı olarak çalıştırılır. İşte 500 milyon uzunluğunda iki tamsayı vektörü eklemek için zamanlama.

Bayrağı eklememiz -ftree-vectorizeveya optimizasyonu düzeye değiştirmemiz gerekiyor -O3. ( Diğer ek optimizasyonları da -O3gerçekleştirdiğini unutmayın ). Bayrak , bir döngünün ne zaman başarılı bir şekilde vektörlendiğini bildirdiği için kullanışlıdır).-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    

Bu bize işlevin vektörleştirildiğini söyler. 500 milyon uzunluğundaki tamsayı vektörlerinde hem vektörlenmemiş hem de vektörize edilmiş sürümleri karşılaştıran zamanlamalar şunlardır:

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

Bu kısım sürekliliği kaybetmeden güvenli bir şekilde atlanabilir.

Derleyiciler her zaman vektörü oluşturmak için yeterli bilgiye sahip olmayacaktır. Paralel programlama için OpenMP belirtimini kullanabiliriz , bu da derleyicilere kodu vektörleştirmeleri talimatını vermek için bir simd derleyici yönergesi sağlar. Bellek çakışmalarının, yarış koşullarının vb. Olmadığından emin olmak önemlidir. Kodu manuel olarak vektörlerken yanlış sonuçlar doğuracaktır.

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

Bunu yaparak derleyiciden ne olursa olsun onu vektörleştirmesini rica ediyoruz. Derleme zamanı işaretini kullanarak OpenMP uzantılarını etkinleştirmemiz gerekecek -fopenmp. Bunu yaparak:

# 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

ki bu harika! Bu, her ikisi de OpenMP 4.0'ı destekleyen gcc v6.2.0 ve llvm clang v3.9.0 (her ikisi de homebrew, MacOS 10.12.3 aracılığıyla yüklenmiş) ile test edildi.


Bu anlamda, olsa Dizi Programlama Wikipedia sayfasında tüm diziler faaliyet gösterdiklerini dilleri bahseder genellikle böyle hitap vektörleştirilmiş operasyonlar , gerçekten döngü gizleme (aslında vektörleştirilmiş sürece) IMO.

R, hatta rowSums()veya colSums()C'deki kod durumunda, kod vektörleştirmesi IIUC'yi kullanmayın; sadece C'deki bir döngü. Aynı şey geçerli lapply(). Bu durumda, apply()R cinsindendir. Tüm bunlar bu nedenle döngü gizlidir .

Kısacası, R işlevini şu şekilde sararak:

Sadece bir yazma için-döngü içinde C! = kodunuzu vectorising.
Sadece bir yazma için-döngü içinde R! = kodunuzu vectorising.

Örneğin Intel Matematik Çekirdek Kütüphanesi (MKL) , vektörize fonksiyon biçimlerini uygular.

HTH


Referanslar:

  1. James Reinders'ın konuşması , Intel (bu cevap çoğunlukla bu mükemmel konuşmayı özetleme çabasıdır)

35

Bu nedenle, büyük cevapları / yorumları genel bir cevapta toplamak ve biraz arka plan sağlamak için: R, 4 tip döngüye sahiptir ( vektörleştirilmeden vektörleştirilmiş düzene kadar )

  1. R, forart arda her yineleme R işlevlerini çağırmaktadır döngü ( vektörlenmiş değil )
  2. Her yinelemede art arda R işlevlerini çağıran C döngüsü ( Vektörleştirilmemiş )
  3. R işlevini yalnızca bir kez çağıran C döngüsü ( Biraz vektörlü )
  4. Hiçbir R işlevini hiç çağırmayan ve kendi derlenmiş işlevlerini kullanan düz bir C döngüsü ( Vectorized )

Yani *applyaile ikinci tiptir. applyİlk türden daha fazlası hariç

Bunu kaynak kodundaki yorumdan anlayabilirsiniz

/ * .Dahili (lapply (X, FUN)) * /

/ * Bu özel bir .Internal, değerlendirilmemiş argümanlar var. Edilir
X ve EĞLENCE vaat böylece, bir kapatma sarmalayıcıdaki denir. FUN, örneğin bquote'ta kullanım için değerlendirilmemelidir. * /

Bu, lapplys C kodunun R'den değerlendirilmemiş bir işlevi kabul ettiği ve daha sonra onu C kodunun içinde değerlendirdiği anlamına gelir . Bu temelde lapplys .Internalçağrısı arasındaki farktır

.Internal(lapply(X, FUN))

Hangi FUNR işlevi tutan bir argümanı vardır

Ve colMeans .Internalçağrı hangi gelmez bir var FUNargüman

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

colMeans, tam olarak hangi işlevi kullanması gerektiğini lapplybilir , bu nedenle ortalamayı C kodu içinde dahili olarak hesaplar.

C kodundaki her bir yinelemede R fonksiyonunun değerlendirme sürecini açıkça görebilirsinizlapply

 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);
   }

Özetlemek , düz R döngüsüne göre iki olası avantajı olmasına rağmen, lapplyvektörleştirilmezfor

  1. Bir döngüde erişim ve atama C'de daha hızlı gibi görünmektedir (yani lapplybir işlev girerken) Fark büyük görünse de, yine de mikrosaniye seviyesinde kalıyoruz ve maliyetli olan her yinelemede bir R işlevinin değerlenmesidir. Basit bir örnek:

    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 tarafından belirtildiği gibi, derlenmiş bir C döngüsü yerine yorumlanmış bir R döngüsü çalıştırır


Kodunuzu vektörlerken, dikkate almanız gereken bazı şeyler vardır.

  1. Veri seti (diyelim çağrı bunu ederse df) sınıfının olduğu data.frame, bazı vectorized fonksiyonları (örneğin colMeans, colSums, rowSumsbu onların nasıl tasarlandığını olduğu çünkü, vs.), ilk bir matris dönüştürmek gerekir. Bu, büyük için bunun büyük dfbir yük oluşturabileceği anlamına gelir . Gerçi lapplybunu yapmak zorunda kalmayacak, çünkü gerçek vektörleri çıkarıyor df( data.framesadece bir vektör listesi gibi) ve bu nedenle, çok fazla sütununuz yoksa çok sayıda satırınız varsa lapply(df, mean), bazen daha iyi bir seçenek olabilir colMeans(df).
  2. Hatırlanması gereken bir diğer şey R gibi farklı işlevi, büyük bir çeşitlilik olmasıdır .Primitive, ve jenerik ( S3, S4) bakınız burada bazı ek bilgi için. Genel işlev, bazen pahalı bir işlem olan bir yöntem gönderimi yapmak zorundadır. Örneğin, meangenel olduğu S3ise fonksiyon sumolduğunu Primitive. Bu nedenle , yukarıda listelenen nedenlerle lapply(df, sum)karşılaştırıldığında bazı zamanlar çok verimli olabilircolSums

1
Çok uyumlu özet. Birkaç not: (1) C, "data.frame" leri nasıl kullanacağını bilir, çünkü bunlar "özniteliklere sahip" liste "lerdir; colMeanssadece matrisleri işlemek için üretilmiş olan vb. (2) Üçüncü kategorinizle biraz kafam karıştı; Ne demek istediğini söyleyemem. (3) Özellikle bahsettiğiniz için lapply, "[<-"R ve C arasında bir fark yaratmadığına inanıyorum ; ikisini de önceden bir "liste" (bir SEXP) tahsis eder ve her SET_VECTOR_ELTnoktayı ( C cinsinden) doldurur .
alexis_laz

2
Ben yaklaşık noktası almak do.callo C ÇEVREYLE bir işlev çağrısını builts olduğunu ve sadece onu değerlendirir; farklı bir şey yaptığı için döngü veya vectorization ile karşılaştırmak için zor bir zaman olmasına rağmen. Masraflı iteratif R işlev çağrısı (karşılaştırma olduğundan, sağa derece rerult erişmesini ve C ve R arasındaki farkları atama, hem fiyatının rağmen mikrosaniye düzeyinde ve sonucu etkilemez hakkında, aslında, konum R_loopve R_lapplycevabım ). (Yayınınızı bir karşılaştırma ölçütü ile düzenleyeceğim; umarım yine de umursamazsınız)
alexis_laz

2
Katılmamaya çalışmıyorum --- ve dürüstçe, katılmadığınız şey konusunda kafam karıştı. Daha önceki yorumum daha iyi ifade edilebilirdi. "Vectorization" terimi genellikle kapalı iki tanımı vardır, çünkü kullanılan terminoloji rafine etmeye çalışıyorum. Bunun tartışmalı olduğunu düşünmüyorum. Burns ve siz bunu sadece uygulama anlamında kullanmak istiyor gibi görünüyorsunuz, ancak Hadley ve birçok R-Core üyesi ( Vectorize()örnek olarak) UI anlamında da kullanıyor. Bu konudaki anlaşmazlıkların çoğunun bir terimi iki ayrı ama ilgili kavram için kullanmasından kaynaklandığını düşünüyorum.
Gregor Thomas

3
@DavidArenburg ve altında R veya C'de for döngüsü olup olmadığına bakılmaksızın UI anlamında vektörleştirme değil mi?
Gregor Thomas

2
@DavidArenburg, Gregor, karışıklığın "kod vektörleştirmesi" ile "vektörize edilmiş fonksiyonlar" arasında olduğunu düşünüyorum. R'de kullanım, ikincisine doğru eğimli görünmektedir. "Kod vektörleştirmesi" aynı talimatta "k" uzunluğunda bir vektör üzerinde çalışmayı açıklar. Bir fn sarma. döngüsel kod etrafında "vectorized fonksiyonlar" ile sonuçlanır (evet, mantıklı değil ve kafa karıştırıcı, katılıyorum, daha iyi döngü gizleme veya vektör i / p fonksiyonları olurdu ) ve kod vektörleştirme ile ilgisi yoktur . R'de, uygulama vektörleştirilmiş bir işlev olacaktır , ancak kodunuzu vektörleştirmez, daha çok vektörler üzerinde çalışır.
Arun
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.