Warum sind Schleifen in R langsam?

83

Ich weiß, dass Schleifen langsam sind Rund ich stattdessen versuchen sollte, Dinge vektorisiert zu machen.

Aber wieso? Warum sind Schleifen langsam und applyschnell? applyruft mehrere Unterfunktionen auf - das scheint nicht schnell zu sein.

Update: Es tut mir leid, die Frage war schlecht gestellt. Ich habe Vektorisierung mit verwechselt apply. Meine Frage hätte lauten sollen:

"Warum ist die Vektorisierung schneller?"

Isomorphismen
quelle
3
Ich hatte den Eindruck, dass das "Anwenden viel schneller als für Schleifen" in R ein Mythos ist . Lassen Sie die system.timeKriege in den Antworten beginnen ...
joran
1
Viele gute Informationen hier zum Thema: stackoverflow.com/questions/2275896/…
Chase
7
Für die Aufzeichnung: Übernehmen ist NICHT Vektorisierung. Anwenden ist eine Schleifenstruktur mit verschiedenen (wie in: nein) Nebenwirkungen. Siehe die Diskussion @Chase Links zu.
Joris Meys
4
Schleifen in S ( S-Plus ?) Waren traditionell langsam. Dies ist bei R nicht der Fall ; Daher ist Ihre Frage nicht wirklich relevant. Ich weiß nicht, wie die Situation mit S-Plus heute ist.
Gavin Simpson
4
Es ist mir unklar, warum die Frage heftig abgelehnt wurde - diese Frage ist sehr häufig bei Personen, die aus anderen Bereichen zu R kommen, und sollte in die FAQ aufgenommen werden.
Patrickmdnet

Antworten:

69

Schleifen in R sind aus demselben Grund langsam, aus dem jede interpretierte Sprache langsam ist: Jede Operation bringt viel zusätzliches Gepäck mit sich.

Schauen Sie R_execClosureineval.c (dies ist die Funktion, die aufgerufen wird, um eine benutzerdefinierte Funktion aufzurufen). Es ist fast 100 Zeilen lang und führt alle Arten von Operationen aus - Erstellen einer Ausführungsumgebung, Zuweisen von Argumenten zur Umgebung usw.

Überlegen Sie, wie viel weniger passiert, wenn Sie eine Funktion in C aufrufen (Args auf Stack, Jump, Pop Args drücken).

Deshalb erhalten Sie Timings wie diese (wie Joran im Kommentar hervorhob, ist es nicht wirklich applyschnell; es ist die interne C-Schleife mean , die schnell ist. Es applyist nur normaler alter R-Code):

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

Verwenden einer Schleife: 0,342 Sekunden:

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

Mit Summe: unermesslich klein:

sum(A)

Es ist ein wenig beunruhigend, weil die Schleife asymptotisch genauso gut ist wie sum; Es gibt keinen praktischen Grund, warum es langsam sein sollte. Mit jeder Iteration wird nur mehr zusätzliche Arbeit geleistet.

Also bedenken Sie:

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

(Dieses Beispiel wurde von Radford Neal entdeckt )

Weil (in R ein Operator ist und tatsächlich jedes Mal, wenn Sie ihn verwenden, eine Namenssuche erfordert:

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

Oder im Allgemeinen haben interpretierte Operationen (in jeder Sprache) mehr Schritte. Natürlich bieten diese Schritte Vorteile auch: Sie konnte nicht tun , dass (Trick in C.

Owen
quelle
10
Was ist der Sinn des letzten Beispiels? Mach keine dummen Sachen in R und erwarte, dass es sie schnell macht?
Chase
6
@Chase Ich denke, das ist eine Möglichkeit, es zu sagen. Ja, ich meinte, eine Sprache wie C hätte keinen Geschwindigkeitsunterschied zu verschachtelten Klammern, aber R optimiert oder kompiliert nicht.
Owen
1
Also () oder das {} im Schleifenkörper - all diese Dinge beinhalten Namenssuchen. Oder im Allgemeinen macht der Interpreter in R mehr, wenn Sie mehr schreiben.
Owen
1
Ich bin mir nicht sicher, welchen Punkt Sie mit den for()Schleifen machen wollen? Sie machen überhaupt nicht dasselbe. Die for()Schleife iteriert über jedes Element von Aund summiert sie. Der apply()Aufruf übergibt den gesamten Vektor A[,1](Sie Ahaben eine einzelne Spalte) an eine vektorisierte Funktion mean(). Ich sehe nicht, wie dies der Diskussion hilft und die Situation nur verwirrt.
Gavin Simpson
3
@Owen Ich stimme Ihrem allgemeinen Punkt zu, und es ist ein wichtiger; Wir verwenden R nicht, weil es Geschwindigkeitsrekorde bricht. Wir verwenden es, weil es einfach zu bedienen und sehr leistungsfähig ist. Diese Kraft ist mit dem Preis der Interpretation verbunden. Es war nur unklar, was Sie im for()vs- apply()Beispiel zeigen wollten . Ich denke, Sie sollten dieses Beispiel entfernen, da die Summierung zwar den größten Teil der Berechnung des Mittelwerts ausmacht, Ihr Beispiel jedoch nur die Geschwindigkeit einer vektorisierten Funktion mean()über die C-ähnliche Iteration über Elemente zeigt.
Gavin Simpson
77

Es ist nicht immer so, dass Schleifen langsam und applyschnell sind. Es gibt eine nette Diskussion darüber in der Mai 2008 Ausgabe von R News :

Uwe Ligges und John Fox. R Helpdesk: Wie kann ich diese Schleife vermeiden oder beschleunigen? R News, 8 (1): 46-50, Mai 2008.

Im Abschnitt "Loops!" (ab S. 48) heißt es:

Viele Kommentare zu R besagen, dass die Verwendung von Schleifen eine besonders schlechte Idee ist. Dies ist nicht unbedingt wahr. In bestimmten Fällen ist es schwierig, vektorisierten Code zu schreiben, oder vektorisierter Code kann eine große Menge an Speicher verbrauchen.

Sie schlagen weiter vor:

  • Initialisieren Sie neue Objekte vor der Schleife in voller Länge, anstatt ihre Größe innerhalb der Schleife zu erhöhen.
  • Machen Sie keine Dinge in einer Schleife, die außerhalb der Schleife ausgeführt werden können.
  • Vermeiden Sie Schleifen nicht nur, um Schleifen zu vermeiden.

Sie haben ein einfaches Beispiel, bei dem eine forSchleife 1,3 Sekunden dauert, aber nicht applygenügend Speicherplatz hat.

Karl
quelle
35

Die einzige Antwort auf die gestellte Frage lautet: Schleifen sind nicht langsam, wenn Sie einen Datensatz durchlaufen müssen, der eine Funktion ausführt, und diese Funktion oder die Operation nicht vektorisiert ist. Eine for()Schleife ist im Allgemeinen so schnell wie apply(), aber möglicherweise etwas langsamer als ein lapply()Anruf. Der letzte Punkt wird beispielsweise in dieser Antwort ausführlich auf SO behandelt und gilt, wenn der Code, der beim Einrichten und Betreiben der Schleife erforderlich ist, einen wesentlichen Teil der gesamten Rechenlast der Schleife ausmacht .

Viele Leute denken, for()Schleifen seien langsam, weil sie als Benutzer schlechten Code schreiben. Wenn Sie ein Objekt erweitern / vergrößern müssen, müssen Sie im Allgemeinen (obwohl es mehrere Ausnahmen gibt) auch kopieren, sodass Sie sowohl den Aufwand für das Kopieren als auch das Vergrößern des Objekts haben. Dies ist nicht nur auf Schleifen beschränkt, sondern wenn Sie bei jeder Iteration einer Schleife kopieren / wachsen, wird die Schleife natürlich langsam, da Sie viele Kopier- / Wachstumsoperationen ausführen.

Die allgemeine Redewendung für die Verwendung von for()Schleifen in R lautet, dass Sie den erforderlichen Speicher zuweisen, bevor die Schleife beginnt, und dann das so zugewiesene Objekt ausfüllen. Wenn Sie dieser Redewendung folgen, werden die Schleifen nicht langsam sein. Dies ist es, was apply()für Sie verwaltet wird, aber es ist nur unsichtbar.

Wenn für die Operation, die Sie mit der for()Schleife implementieren, eine vektorisierte Funktion vorhanden ist , tun Sie dies natürlich nicht . Ebenso nicht verwenden apply()etc , wenn eine vektorisiert Funktion vorhanden ist (zB apply(foo, 2, mean)wird besser über geführt colMeans(foo)).

Gavin Simpson
quelle
9

Nur zum Vergleich (lesen Sie nicht zu viel hinein!): Ich habe eine (sehr) einfache for-Schleife in R und in JavaScript in Chrome und IE 8 ausgeführt. Beachten Sie, dass Chrome die Kompilierung mit nativem Code und R mit dem Compiler durchführt Paket kompiliert zu Bytecode.

# 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: Übrigens hat es in S-Plus 1162 ms gedauert ...

Und der "gleiche" Code wie JavaScript:

// 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;
Tommy
quelle