Warum sollte dieser Lua-Optimierungs-Hack die Leistung verbessern?

7

Ich schaue mir ein Dokument an, das verschiedene Techniken beschreibt, um die Leistung des Lua Skriptcodes zu verbessern, und ich Ich bin schockiert, dass solche Tricks benötigt werden. (Obwohl ich Lua zitiere, habe ich ähnliche Hacks in Javascript gesehen).

Warum sollte diese Optimierung erforderlich sein?

  

Zum Beispiel der Code

%Vor%      

läuft 30% langsamer als dieser:

%Vor%

Sie deklarieren sin function lokal neu.

Warum wäre das hilfreich? Es ist die Aufgabe des Compilers, das trotzdem zu tun. Warum muss der Programmierer den Job des Compilers machen?

Ich habe ähnliche Dinge in Javascript gesehen; und deshalb muss es natürlich einen sehr guten Grund geben, warum der interpretierende Compiler seine Aufgabe nicht erfüllt. Was ist das?

Ich sehe es wiederholt in der Lua-Umgebung, in der ich herumspiele; Personen, die Variablen als lokal deklarieren:

%Vor%

Was passiert hier, dass Leute die Arbeit des Compilers machen müssen? Ist der Compiler verwirrt, indem er format findet? Warum ist das ein Problem, mit dem sich ein Programmierer befassen muss? Warum wurde 1993 nicht dafür gesorgt?

Ich habe auch ein logisches Paradoxon getroffen:

  1. Die Optimierung sollte nicht ohne Profiling erfolgen
  2. Lua kann nicht profiliert werden
  3. Lua sollte nicht optimiert werden
Ian Boyd 10.01.2011, 05:01
quelle

6 Antworten

34
  

Warum wäre das hilfreich? Es ist die Aufgabe des Compilers, das trotzdem zu tun. Warum muss der Programmierer den Job des Compilers machen?

Lua ist eine dynamische Sprache. Compiler können in statischen Sprachen viele Überlegungen anstellen, wie zum Beispiel konstante Ausdrücke aus der Schleife herausziehen. In dynamischen Sprachen ist die Situation etwas anders.

Lua's wichtigste (und auch einzige) Datenstruktur ist die Tabelle. math ist auch nur eine Tabelle, obwohl sie hier als Namespace verwendet wird. Niemand kann Sie daran hindern, die math.sin -Funktion irgendwo in der Schleife zu ändern (selbst wenn das nicht ratsam wäre), und der Compiler kann das nicht wissen, wenn er den Code kompiliert. Daher führt der Compiler genau das aus, wozu Sie ihn angewiesen haben: Suchen Sie in jeder Iteration der Schleife die Funktion sin in der Tabelle math und rufen Sie sie auf.

Wenn Sie nun wissen, dass Sie math.sin nicht ändern werden (d. h., Sie rufen dieselbe Funktion auf), können Sie sie in einer lokalen Variablen außerhalb der Schleife speichern. Da es keine Tabellensuche gibt, ist der resultierende Code schneller.

Die Situation ist ein wenig anders bei LuaJIT - es verwendet Tracing und einige erweiterte Magie, um zu sehen, was Ihr Code in Runtime macht, so dass er die Schleife tatsächlich optimieren kann, indem er den Ausdruck außerhalb des Schleife, und andere Optimierungen, abgesehen davon, dass es tatsächlich zum Maschinencode kompiliert wird, was es schnell verrückt macht.

In Bezug auf die 'redefinierenden Variablen als lokal' - bei der Definition eines Moduls wollen Sie oft mit der ursprünglichen Funktion arbeiten. Beim Zugriff auf pairs , max oder irgendetwas, das ihre globalen Variablen verwendet, kann Ihnen niemand versichern, dass es bei jedem Aufruf die gleiche Funktion ist. Zum Beispiel definiert stdlib viele globale Funktionen neu.

Wenn Sie eine lokale Variable mit demselben Namen wie die globale Variable erstellen, speichern Sie die Funktion im Wesentlichen in einer lokalen Variablen und durch lokale Variablen (die lexikalisch definiert sind, dh sie sind im aktuellen Bereich und auch in verschachtelten Bereichen sichtbar). Vorrang vor globals haben Sie immer die gleiche Funktion aufrufen. Sollte jemand später das globale ändern, hat dies keine Auswirkungen auf Ihr Modul. Nicht zu vergessen, es ist auch schneller, weil Globals in einer globalen Tabelle nachgeschlagen werden ( _G ).

Update : Ich lese gerade Lua Performance-Tipps von Roberto Ierusalimschy, einer von Lua Autoren, und es erklärt ziemlich alles, was Sie über Lua, Leistung und Optimierung wissen müssen. IMO die wichtigsten Regeln sind:

  

Regel # 1 : Tu es nicht.

     

Regel # 2 : Tu es noch nicht. (nur für Experten)

    
Michal Kottman 10.01.2011, 12:45
quelle
11

Der Grund, warum es nicht standardmäßig gemacht wird, weiß ich nicht. Warum es jedoch schneller ist, ist, weil Einheimische in ein Register geschrieben werden, während ein Global bedeutet, es in einer Tabelle (_G) nachzuschlagen, die dafür bekannt ist, etwas langsamer zu sein.

Was die Sichtbarkeit anbelangt (wie bei der Format-Funktion): Ein Local verdunkelt das Globale. Wenn Sie also eine lokale Funktion mit demselben Namen wie eine globale deklarieren, wird stattdessen die lokale Funktion verwendet, solange sie sich im Gültigkeitsbereich befindet. Wenn Sie stattdessen die globale Funktion verwenden möchten, verwenden Sie _G.function.

Wenn du wirklich schnell Lua willst, könntest du LuaJIT

ausprobieren     
jpjacobs 10.01.2011 09:24
quelle
9
  

Ich sehe es wiederholt in der Lua-Umgebung, in der ich herumspiele; Personen, die Variablen als lokal deklarieren:

Das ist standardmäßig falsch.

Es ist wohl nützlich, lokale Referenzen anstelle von Tabellenzugriffen zu verwenden, wenn eine Funktion immer wieder verwendet wird, wie in Ihrer Beispielschleife:

%Vor%

Bei externen Schleifen ist der Aufwand für das Hinzufügen eines Tabellenzugriffs jedoch völlig vernachlässigbar.

  

Was passiert hier, dass Leute die Arbeit des Compilers machen müssen?

Weil die beiden obigen Codebeispiele nicht genau das Gleiche bedeuten.

  

Es ist nicht so, dass sich die Funktion ändern kann, während meine Funktion läuft.

Lua ist eine sehr dynamische Sprache, und Sie können nicht dieselben Annahmen treffen wie in anderen restriktiveren Sprachen wie C. Die Funktion kann ändern, während Ihre Schleife läuft. Angesichts der dynamischen Natur der Sprache kann der Compiler nicht davon ausgehen, dass sich die Funktion nicht ändert. Oder zumindest nicht ohne eine komplexe Analyse Ihres Codes und seiner Auswirkungen.

Der Trick ist, dass, selbst wenn Ihre zwei Codeabschnitte gleich aussehen, in Lua nicht. Auf der ersten Seite sagen Sie ausdrücklich, dass Sie "bei jeder Iteration die sin-Funktion innerhalb der Mathe-Tabelle erhalten". Auf der zweiten Seite verwenden Sie immer wieder einen einzelnen Verweis auf die gleiche Funktion.

Bedenken Sie Folgendes:

%Vor%     
kikito 10.01.2011 16:04
quelle
3

Das Speichern von Funktionen in lokalen Variablen entfernt die Tabellenindizierung, um den Funktionsschlüssel in jeder Iteration der Schleife nachzuschlagen, die mathematischen sind offensichtlich, da sie den Hash in der Mathentabelle suchen müssen, die anderen nicht indexiert in die _G (globale Tabelle), die nun _ENV (Umgebungstabelle) ab 5.2 ist.

Außerdem sollte man in der Lage sein, lua mit seiner debug-hooks-API zu profilieren, oder mit den lua-Debuggern, die herumliegen.

    
Necrolis 10.01.2011 05:18
quelle
1

Meine Annahme ist, dass in der optimierten Version, weil der Verweis auf die Funktion in einer lokalen Variablen gespeichert wird, nicht bei jeder Iteration der for-Schleife ein Tree-Traversal durchgeführt werden muss (zum Nachschlagen auf math.sin ) .

Ich bin mir nicht sicher, ob die lokalen Referenzen auf Funktionsnamen gesetzt sind, aber ich nehme an, dass eine Art globaler Namespace-Lookup erforderlich ist, wenn kein lokaler gefunden wird.

Andererseits könnte ich WEG von der Basis sein;)

Edit: Ich nehme auch an, dass der Lua-Compiler dumm ist (was für mich sowieso eine allgemeine Annahme über Compiler ist;))

    
Demian Brecht 10.01.2011 05:10
quelle
1

Dies ist nicht nur ein Fehler / Feature von Lua , viele Sprachen, einschließlich Java und C , werden schneller ausgeführt, wenn Sie auf lokale Werte anstatt auf Werte außerhalb des Bereichs zugreifen, z. B. von einer Klasse oder einem Array.

In C++ zum Beispiel ist es schneller, auf ein lokales Mitglied zuzugreifen als auf Variablenmitglieder einer Klasse zuzugreifen.

Dies würde 10.000 schneller zählen:

%Vor%

als:

%Vor%

Der Grund, warum Lua globale Werte innerhalb einer Tabelle enthält, liegt darin, dass der Programmierer die globale Umgebung schnell speichern und ändern kann, indem er einfach die Tabelle ändert, auf die _G verweist. Ich stimme zu, dass es schön wäre, einen "syntaktischen Zucker" zu haben, der die globale Tabelle _G als Sonderfall behandelt; sie alle als lokale Variablen im Dateiumfang (oder etwas Ähnliches) umschreiben, natürlich hindert uns nichts daran, dies selbst zu tun; vielleicht eine Funktion optGlobalEnv (...), die die _G-Tabelle und ihre Mitglieder / Werte in den 'Dateibereich' mit Hilfe von unpack () oder etwas 'lokalisiert'.

    
Trae Barlow 08.11.2012 12:28
quelle