R schnelle Einzelelement-Suche von Liste vs data.table vs Hash

8

Eines der Probleme, mit denen ich oft konfrontiert bin, ist, dass ich eine beliebige Zeile aus einer data.table nachschlagen muss. Ich bin gestern auf ein Problem gestoßen, bei dem ich versucht habe, eine Schleife zu beschleunigen und profvis zu verwenden. Ich fand heraus, dass das Nachschlagen von data.table der teuerste Teil der Schleife war. Ich entschied mich dann zu versuchen, den schnellsten Weg zu finden, um einen einzelnen Gegenstand in R zu suchen.

Die Daten haben oft die Form eines data.table mit einer Schlüsselspalte des Zeichentyps. Die übrigen Spalten sind typischerweise numerische Werte. Ich habe versucht, eine zufällige Tabelle mit ähnlichen Eigenschaften zu erstellen, mit denen ich oft zu tun habe, was & gt; 100 K Zeilen bedeutet. Ich habe die native Liste, data.table package und hash package verglichen. Die native Liste und data.table waren für die Leistung einzelner Elemente vergleichbar. Hash schien um zwei Größenordnungen schneller zu sein. Die Tests bestanden aus 10 Sätzen von 10.000 Schlüsseln, die nach dem Zufallsprinzip abgetastet wurden, um das Zugriffsverhalten zu variieren. Bei jeder Suchmethode wurden die gleichen Schlüssel verwendet.

Letztlich wäre es meine Präferenz, entweder die Zeilensuche für data.table schneller zu machen, anstatt eine Hashtabelle meiner Daten zu erstellen oder festzustellen, dass dies nicht möglich ist, und einfach das Hash-Paket zu verwenden, wenn ich es tun muss schnelles Nachschlagen. Ich weiß nicht, ob es möglich wäre, aber könnten Sie eine Hash-Tabelle mit Verweisen auf die Zeilen in der data.table erstellen, um eine schnelle Suche mit dem Hash-Paket zu ermöglichen? Ich weiß, dass diese Art von Sache in C ++ möglich ist, aber meines Wissens erlaubt R solche Dinge nicht, weil es keine Zeiger gibt.

Zusammenfassen: 1) Benutze ich data.table korrekt für die Suchvorgänge und daher ist das die Geschwindigkeit, die ich für eine einzelne Zeilensuche erwarten sollte? 2) Wäre es möglich, einen Hash von Zeigern zu den data.table-Zeilen zu erstellen, um so eine schnelle Suche zu ermöglichen?

Testsystem:

Windows 10 Pro x 64

R 3.2.2

data.table 1.9.6

Hash 2.2.6

Intel Core i7-5600U mit 16 GB RAM

Code:

%Vor%

Das waren die Ergebnisse:

%Vor%

Es scheint so, dass die hash Suche ungefähr zwei Größenordnungen schneller ist als die native Liste oder data.table in diesem speziellen Szenario.

Update: 2015-12-11 15:00 Uhr PST

Ich habe Feedback von Neal Fultz erhalten, in dem vorgeschlagen wurde, das native Environment-Objekt zu verwenden. Hier ist der Code und das Ergebnis, das ich bekommen habe:

%Vor%

Tatsächlich scheint die Umgebung in diesem Szenario für den Zugriff auf einzelne Elemente schneller zu sein. Danke, Neal Fultz, dass du diese Methode aufgezeigt hast. Ich weiß es zu schätzen, dass ich die in R verfügbaren Objekttypen genauer verstehe. Meine Fragen stehen immer noch: verwende ich data.table richtig (ich erwarte das, aber ich bin offen für Kritik) und gibt es einen Zeilenzugriff auf die Zeilen eines data.table mit einer Art Zeigermagie, die einen schnelleren Zugriff auf einzelne Zeilen ermöglicht.

Erläuterung: 2015-12-11 3:52 PM PST

Es gab einige Erwähnungen, dass mein Zugriffsmuster in der innersten Schleife meines Tests ineffizient ist. Ich stimme zu. Ich versuche, die Situation, mit der ich es zu tun habe, so genau wie möglich nachzuahmen. Die Schleife, in der dies tatsächlich stattfindet, erlaubt keine Vektorisierung, weshalb ich sie nicht verwende. Mir ist klar, dass dies nicht unbedingt der "R" Weg ist, Dinge zu tun. Das data.table in meinem Code liefert Referenzinformationen und ich weiß nicht unbedingt, welche Zeile ich brauche, bis ich mich in der Schleife befinde. Deshalb versuche ich herauszufinden, wie ich so schnell wie möglich auf ein einzelnes Objekt zugreifen kann, vorzugsweise mit dem Daten werden immer noch in data.table gespeichert. Dies ist auch zum Teil eine Frage der Neugier, kann es getan werden?

Update 2: 2015-12-11 4:12 PM PST

Ich habe Feedback von @jangrorecki erhalten, dass die Verwendung von Sys.time() ein ineffizientes Mittel zur Messung der Leistung einer Funktion ist. Ich habe seitdem den Code geändert, um system.nanotime() pro Vorschlag zu verwenden. Der ursprüngliche Code wurde aktualisiert und das Timing resultiert.

Die Frage steht immer noch: Ist dies der schnellste Weg, eine Zeilensuche von data.table durchzuführen, und wenn ja, ist es möglich, einen Hash der Zeiger auf die Zeilen für eine schnelle Suche zu erstellen? An dieser Stelle bin ich sehr gespannt, wie weit R geschoben werden kann. Als jemand, der aus C ++ kam, ist das eine lustige Herausforderung.

Fazit

Ich habe die Antwort von Neal Fultz akzeptiert, weil sie darüber gesprochen hat, was ich eigentlich wissen wollte. Das heißt, das ist nicht die Art, wie data.table verwendet werden sollte, also sollte niemand dies so interpretieren, dass data.table langsam ist, es ist tatsächlich unglaublich schnell. Dies war ein ganz besonderer Anwendungsfall, auf den ich neugierig war. Meine Daten kommen als data.table und ich wollte wissen, ob ich einen schnellen Zeilenzugriff bekommen könnte, während ich sie als data.table belasse. Ich wollte auch die Zugriffsgeschwindigkeit data.table mit einer Hash-Tabelle vergleichen, die oft für schnelle, nicht vektorisierte Item-Suche verwendet wird.

    
Matthew Crews 11.12.2015, 21:29
quelle

2 Antworten

6

Für ein nicht vektorisiertes Zugriffsmuster sollten Sie die integrierten environment -Objekte ausprobieren:

%Vor%

Hier können Sie sehen, dass es sogar zipper ist als hash :

%Vor%

BEARBEITEN:

Durch data.table:::'[.data.table' zu gehen ist aufschlussreich, warum du dt verlangsamen siehst. Wenn Sie mit einem Zeichen indexieren und ein Schlüsselsatz vorhanden ist, führt dies eine ganze Menge Buchhaltung durch und wird dann in bmerge abgelegt, was eine binäre Suche ist. Die binäre Suche ist O (log n) und wird langsamer, wenn n zunimmt.

Umgekehrt verwenden die Umgebungen Hashing (standardmäßig) und haben eine konstante Zugriffszeit in Bezug auf n.

Um dies zu umgehen, können Sie manuell eine Karte und einen Index erstellen:

%Vor%

Wenn ich jedoch so viel Buchhaltungscode in der data.table-Methode sehe, würde ich auch einfache alte data.frames ausprobieren:

%Vor%

Wenn wir wirklich paranoid sind, könnten wir die [ -Methoden komplett überspringen und direkt über die Spalten laufen.

Hier sind einige weitere Timings (von einer anderen Maschine als oben):

%Vor%

Um Ihre Fragen zu beantworten, verwenden Sie data.table nicht "falsch", aber Sie verwenden es auch nicht so, wie es beabsichtigt war (vektorisierter Zugriff). Sie können jedoch manuell eine Karte erstellen, um durch den Index zu gehen und den größten Teil der Leistung zurückzuerhalten.

    
Neal Fultz 11.12.2015, 22:53
quelle
5

Der von Ihnen gewählte Ansatz scheint sehr ineffizient zu sein, da Sie den einzelnen Wert aus dem Dataset mehrmals abfragen.

Es wäre viel effizienter, alle auf einmal abzufragen und dann den gesamten Stapel zu wiederholen, anstatt 1e4 nacheinander abzufragen.

Siehe dt2 für einen vektorisierten Ansatz. Trotzdem fällt es mir schwer, mir den Anwendungsfall dafür vorzustellen.

Eine andere Sache ist 450K Zeilen von Daten ist ziemlich wenige, um einen angemessenen Benchmark zu machen, können Sie völlig unterschiedliche Ergebnisse für 4M oder höher bekommen. In Bezug auf den Hash Ansatz würden Sie wahrscheinlich auch Speichergrenzen schneller erreichen.

Außerdem ist Sys.time() möglicherweise nicht der beste Weg, um das Timing zu messen, lesen Sie gc Argument in ?system.time .

Hier ist der Benchmark, den ich mit der Funktion system.nanotime() aus dem microbenchmarkCore -Paket gemacht habe.

Es ist möglich, den data.table-Ansatz noch weiter zu beschleunigen, indem Sie test_lookup_list in data.table kollabieren und die Zusammenführung zu test_lookup_dt durchführen, aber um sie mit der Hash-Lösung zu vergleichen, müsste ich sie auch vorverarbeiten.

%Vor%

%Vor%     
jangorecki 11.12.2015 23:33
quelle

Tags und Links