R'de döngüler neden yavaş?


87

Döngülerin yavaş olduğunu Rve bunun yerine işleri vektörleştirilmiş bir şekilde yapmaya çalışmam gerektiğini biliyorum.

Ama neden? Döngüler neden yavaş ve applyhızlıdır? applybirkaç alt işlevi çağırır - bu hızlı görünmez.

Güncelleme: Üzgünüm, soru kötü sorulmuştu. Vektörleştirme ile karıştırıyordum apply. Sorum olmalıydı,

"Vektörleştirme neden daha hızlıdır?"


3
R'deki "döngülere göre çok daha hızlı uygulama" nın biraz efsane olduğu izlenimindeydim . Let system.timecevapları savaşları ... başlayacak
Joran

1
Konuyla ilgili pek çok iyi bilgi burada: stackoverflow.com/questions/2275896/…
Takip

7
Kayıt için: Uygula vektörleştirme DEĞİLDİR. Uygula, farklı (olduğu gibi: yok) yan etkilere sahip bir döngü yapısıdır. @Chase bağlantılarına bakın.
Joris Meys

4
İçinde Döngüler S ( S-Plus ?) Yavaş geleneksel idi. Bu R ile durum böyle değildir ; bu nedenle Sorunuz gerçekten alakalı değildir. Bugün S-Plus ile ilgili durumun ne olduğunu bilmiyorum .
Gavin Simpson

4
Sorunun neden ağır bir şekilde reddedildiği bana açık değil - bu soru diğer bölgelerden R'ye gelenler arasında çok yaygındır ve SSS bölümüne eklenmelidir.
patrickmdnet

Yanıtlar:


69

R'deki döngüler yavaştır, çünkü herhangi bir yorumlanan dil yavaştır: her işlem etrafında çok fazla ekstra bagaj taşır.

Bak R_execClosureiçindeeval.c (bu kullanıcı tanımlı bir işlev çağrısı için denilen işlevdir). Yaklaşık 100 satır uzunluğundadır ve her türlü işlemi gerçekleştirir - yürütme için bir ortam oluşturmak, ortama argümanlar atamak, vb.

C'de bir işlevi çağırdığınızda ne kadar az olduğunu düşünün (yığın, atlama, args için değiştirgeleri itin).

İşte bu yüzden böyle zamanlamalar elde edersiniz (joran'ın yorumda belirttiği gibi, aslında applybu hızlı değildir; hızlı olan dahili C döngüsüdür mean . applySadece normal eski R kodudur):

A = matrix(as.numeric(1:100000))

Döngü kullanma: 0,342 saniye:

system.time({
    Sum = 0
    for (i in seq_along(A)) {
        Sum = Sum + A[[i]]
    }
    Sum
})

Toplam kullanarak: ölçülemeyecek kadar küçük:

sum(A)

Bu biraz endişe verici çünkü asimptotik olarak döngü aynı derecede iyidir sum; yavaş olması için pratik bir neden yok; sadece her yinelemede daha fazla ekstra çalışma yapıyor.

Öyleyse düşünün:

# 0.370 seconds
system.time({
    I = 0
    while (I < 100000) {
        10
        I = I + 1
    }
})

# 0.743 seconds -- double the time just adding parentheses
system.time({
    I = 0
    while (I < 100000) {
        ((((((((((10))))))))))
        I = I + 1
    }
})

(Bu örnek Radford Neal tarafından keşfedildi )

Çünkü (R bir operatördür ve aslında onu her kullandığınızda bir ad araması gerektirir:

> `(` = function(x) 2
> (3)
[1] 2

Veya genel olarak, yorumlanan işlemlerin (herhangi bir dilde) daha fazla adımı vardır. Tabii ki, bu adımlar aynı zamanda faydalar da sağlıyor: Bu numarayı C'de yapamazsınız( .


10
Öyleyse son örneğin amacı nedir? R'de aptalca bir şey yapıp hızlı olmasını beklemeyin mi?
Chase

6
@Chase Sanırım bunu söylemenin bir yolu. Evet, C gibi bir dilin iç içe geçmiş parantezlerle hız farkı olmayacağını söyledim, ancak R optimize etmiyor veya derlemez.
Owen

1
Ayrıca () veya döngü gövdesindeki {} - tüm bunlar ad aramalarını içerir. Veya genel olarak, R'de daha fazla yazdığınızda, tercüman daha fazlasını yapar.
Owen

1
for()Döngülerle hangi noktayı yapmaya çalıştığından emin değilim ? Aynı şeyi hiç yapmıyorlar. for()Döngü her öğesi üzerinde yineleme edilir Ave bunları toplayarak. apply()Çağrı tamamı vektör geçiyor A[,1](senin Abir vektörleştirilmiş işleve tek bir sütun vardır) mean(). Bunun tartışmaya nasıl yardımcı olduğunu ve durumu karıştırdığını anlamıyorum.
Gavin Simpson

3
@Owen Genel bakış açınıza katılıyorum ve bu önemli bir konu; R'yi hız rekorları kırdığı için kullanmıyoruz, kullanımı kolay ve çok güçlü olduğu için kullanıyoruz. Bu güç, yorumun bedeliyle birlikte gelir. for()Vs apply()örneğinde ne göstermeye çalıştığınız belirsizdi . Bence bu örneği kaldırmanız gerekir, çünkü toplama ortalamanın hesaplanmasının büyük kısmıdır, tüm örneğinizin gerçekten gösterdiği bir vektörleştirilmiş fonksiyonun, mean()elemanlar üzerindeki C benzeri yineleme üzerindeki hızıdır .
Gavin Simpson

79

Döngülerin yavaş ve applyhızlı olması her zaman geçerli değildir . R News’un Mayıs 2008 sayısında bununla ilgili güzel bir tartışma var :

Uwe Ligges ve John Fox. R Yardım Masası: Bu döngüden nasıl kaçınabilir veya daha hızlı hale getirebilirim? R News, 8 (1): 46-50, Mayıs 2008.

"Döngüler!" Bölümünde (48. sayfadan itibaren) derler ki:

R ile ilgili birçok yorum, döngü kullanmanın özellikle kötü bir fikir olduğunu belirtir. Bu mutlaka doğru değildir. Bazı durumlarda, vektörleştirilmiş kod yazmak zordur veya vektörleştirilmiş kod büyük miktarda bellek tüketebilir.

Ayrıca şunları da öneriyorlar:

  • Döngü içindeki boyutlarını artırmak yerine, yeni nesneleri döngüden önce tam uzunlukta başlatın.
  • Döngünün dışında yapılabilecek şeyleri döngü içinde yapmayın.
  • Döngülerden kaçınmak için döngülerden kaçınmayın.

forDöngünün 1.3 saniye sürdüğü ancak applyhafızanın yetersiz kaldığı basit bir örnekleri var .


35

Sorulan Sorunun Tek Cevabı; döngüler vardır değil yavaş eğer ne yapmak gereken bazı işlevi yerine bir veri seti ve bu işlevin üzerinde iterate veya operasyon vectorized değildir. Bir for()döngü genel olarak apply()bir lapply()çağrı kadar hızlı olacaktır , ancak muhtemelen bir çağrıdan biraz daha yavaş olacaktır . Son nokta da bu, örneğin, SO üzerinde kaplıdır Yanıt ve kurma ve işletme dahil kod geçerlidir döngü genel hesaplama yükünün önemli bir parçasıdır döngü .

Çoğu insan for()döngülerin yavaş olduğunu düşünmesinin nedeni, kullanıcının kötü kod yazmasıdır. Eğer genişletmek gerekirse Genelde (birkaç istisna vardır gerçi), / sen kopyalama yükü de var öyle kopyalama gerektireceğini, bir nesneyi büyümek ve nesne büyüyor. Bu sadece döngülerle sınırlı değildir, aynı zamanda bir döngünün her yinelemesinde kopyalarsanız / büyürseniz, tabii ki döngü yavaş olacaktır, çünkü birçok kopyalama / büyütme işlemine maruz kalıyorsunuz.

for()R'de döngüleri kullanmanın genel deyimi, ihtiyacınız olan depolamayı döngü başlamadan önce ayırmanız ve ardından bu şekilde ayrılan nesneyi doldurmanızdır. Bu deyimi izlerseniz, döngüler yavaş olmayacaktır. Bu apply()sizin için yöneten şeydir , ancak sadece görünmezdir.

Elbette, for()döngü ile gerçekleştirdiğiniz işlem için vektörleştirilmiş bir fonksiyon varsa , bunu yapmayın . Benzer şekilde, yok kullanımı apply()bir vektörlenmiş işlev varsa (örneğin, vb apply(foo, 2, mean)iyi ile gerçekleştirilir colMeans(foo)).


9

Bir karşılaştırma olarak (çok fazla okumayın!): R'de ve JavaScript'te Chrome ve IE 8'de (çok) basit bir for döngüsü çalıştırdım. Chrome'un yerel koda ve R derleyiciyle derleme yaptığına dikkat edin paket bayt koduna göre derlenir.

# In R 2.13.1, this took 500 ms
f <- function() { sum<-0.5; for(i in 1:1000000) sum<-sum+i; sum }
system.time( f() )

# And the compiled version took 130 ms
library(compiler)
g <- cmpfun(f)
system.time( g() )

@Gavin Simpson: Btw, S-Plus'ta 1162 ms sürdü ...

Ve JavaScript ile "aynı" kod:

// In IE8, this took 282 ms
// In Chrome 14.0, this took 4 ms
function f() {
    var sum = 0.5;
    for(i=1; i<=1000000; ++i) sum = sum + i;
    return sum;
}

var start = new Date().getTime();
f();
time = new Date().getTime() - start;
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.