Ergebnis 1 bis 8 von 8

Thema: Was ist schneller: eval(code) oder procedure object?

  1. #1

    Was ist schneller: eval(code) oder procedure object?

    Guten Abend.
    Heute, eine einfache kleine Frage:
    Welche Methode bietet die bessere Performance?

    1). Eine Reihe von Strings zu speichern und via eval(code) ab zu rufen
    oder
    2). Ein Proc Objekt zu erstellen mit den gleichen Funktionen und über .call ein zu setzen.

    Falls ich raten müsste würde ich meine Wette auf das Proc Objekt legen, sonst würde ich keinen Sinn darin sehen ein derartiges Objekt überhaupt zu benutzen. Allerdings, wer bin ich schon um derartige Fragen zu beantworten.

    Vielen Dank für eure Hilfe.

    Grüße!
    Cornix.

  2. #2
    Ich kann dir nicht sagen was schneller ist, aber der Verwendungszweck ist ein anderer.

    Ein Proc ist die Objektrepräsentation eines Codeblocks den man an einen Funktionsaufruf hängen kann wie etwa bei Array#each.
    Eval hingegegen wird genutzt um kleinere Codefragmente dynamisch zusammenbauen zu können.

    Mal abgesehen davon darfst du nicht vergessen, dass es bei Code nicht immer nur um die reine Performance frage geht. Ich würde der Proc Variante auch dann den Vorzug geben wenn er langsamer wäre, einfach weil der Code klarer ist.

  3. #3
    Code mit eval ausführen ist auf jeden Fall (sehr sehr viel) langsamer, da der Code erst geparst werden muss. ALLERDINGS eine Methode einmalig mit eval definieren und sie hinterher häufig ausführen geht wiederum schneller als Procs auszuführen.

    Aber The_Burrito hat Recht: Der Verwendungszweck ist ein anderer. Procs sind deshalb langsamer als nativ definierte Methoden, weil sie mehr können als Methoden. Procs binden den umliegenden Kontext und können auf alle Variablen und Methoden zugreifen, auf die du im Scope Zugriff hattest. Das bedeutet das Procs sehr viel mächtiger sind als eval.

    Mal ein, zugegebenermaßen etwas sinnfreies, Beispiel:

    Code:
    class Foo
    
      def initialize
        @x = 5
      end
    
      def get_block
        y = 8
        Proc.new do
          @x + y
        end
      end
    end
    
    foo = Foo.new
    block = foo.get_block
    block.call #=> 13
    Angenommen get_block würde einen String "@x + y" zurückgeben, so würde es zu einem Fehler führen. Denn sobald du den String außerhalb von Foo aufrufst sind die Variablen @x nicht mehr vorhanden. Ja außerhalb von get_block ist nicht einmal die Variable y mehr vorhanden. Nun könntest du natürlich folgendes machen:
    Code:
    BlockVariables = Struct.new[:x, :y]
    class Foo
      def initialize
        @x = 5
      end
    
      def get_eval_string
        y = 8
        [BlockVariables.new(@x, y), "variables.x + variables.y"]
      end
    end
    foo = Foo.new
    variables, str = foo.get_eval_string
    eval(str) #=> 13
    Das geht aber nur wenn du lediglich lesenden Zugriff auf die Variablen haben willst. Was ist aber, wenn du in deinem Code die Instanzvariable @x ändern willst? Das geht wirklich nur über Procs. Eval-Strings können das nicht. Sie könnten höchstens von außen auf das Foo Objekt zugreifen, oder aber über eine andere Methode innerhalb des Foo-Objekts evaluiert werden (z.B. mit instance_eval). Beides ist aber nur möglich wenn du Zugriff auf das Foo-Objekt hast. Das wiederum bedeutet du musst die Referenz auf das Foo Objekt die ganze Zeit mitschleppen, oder aber in einer globalen Variable speichern (*urks*).

    Procs sind gerade durch ihre Eigenschaft, in ihrem Scope Variablen zu lesen und zu ändern, sehr mächtig und können dadurch für vielerlei Zwecke eingesetzt werden, wo eval nicht so leicht möglich ist. Ein Beispiel wo die RGSS Procs einsetzt: Wenn du eine Message anzeigst wird der Interpreter in einen Haltezustand gesetzt (damit er die nächste Zeile Eventcode erst ausführt, wenn du die Message weggeklickt hast). Damit der Interpreter aber aus diesem Haltezustand wieder hinauskommt, wird ein Proc gesetzt der ausgeführt werden soll, sobald die Message weggeklickt wurde, und der den Haltezustand vom Interpreter wieder entfernt. Wie hätte man das anders lösen können? Nun, man hätte den Haltezustand als öffentliches Attribut definieren müssen und den Interpreter global abspeichern müssen, damit bekannt ist welcher Interpreter gerade die Message anzeigt (es können ja mehrere parallele Interpreter aktiv sein, aber nur einer zeigt gerade die Message an). Das wäre nicht einmal eine besonders schlechte Alternative. Hier einen Proc zu benutzen ist okay, aber nicht zwingend notwendig. Wichtig ist aber: Du hättest hier keinen eval-String als Ersatz nutzen können. Denn die grundlegende Eigenschaft, den Haltezustand innerhalb des Interpreters ändern zu können, hat nur ein Proc, nicht aber ein eval-String.

    Zudem musst du bedenken: eval-Strings werden erst auf ihre Syntax geprüft, wenn sie evaluiert werden. Procs sind Teil des Programmcodes und werden sofort geprüft. Du kannst also auf diese Weise Syntaxfehler erzeugen, die erst sehr spät (oder womöglich gar nicht) zum Absturz führen. Weiterhin sind evals in der Lage Code zu konstruieren. Procs können das nicht, hier musst du andere Techniken anwenden. D.h. du könntest z.B. den Script-Befehl des Makers gar nicht anders als über eval lösen (sinnvoll wäre höchstens noch gewesen die Script-Befehle in einzelne Methoden zu evaluieren, so dass sie nicht jedes Mal erneut evaluiert werden müssen). Gleichzeitig entstehen so aber unter Umständen auch Sicherheitslecks. Angenommen du willst den Namen des Spielerheldens als Variable innerhalb des evals ansprechen, dann kann der Nutzer durch einen fies gewählten Heldennamen jeden beliebigen Code in dein Programm einhacken. Solange dein Programm unverschlüsselt ist, mag das nur ein einfacher Trick sein um Cheats anzuwenden. Wenn du dein Programm verschlüsselst um z.B. deine Grafiken nicht zugänglich zu machen, dann ist das ein einfacher Weg um die Verschlüsselung zu knacken.

    Zu guter letzt noch: eval ist unsauber, weil es für viele Probleme eine auf den ersten Blick einfache Lösung bietet. Dabei kommen teilweise aber ziemlich bescheuerte Problemlösungen raus. Mein Lieblingsbeispiel ist von Dubealex. Der hat in einem seiner Scripte folgenden Schwachsinn geschrieben (kein Zitat, sondern nur sinngemäß nachgeschrieben):
    Code:
    line1 = "Erste Zeile"
    line2 = "Zweite Zeile"
    line3 = "Dritte Zeile"
    line4 = "Vierte Zeile"
    # gib alle Zeilen aus
    1.upto(4) do |zeile|
      eval("print line#{zeile}")
    end
    Tja, wie hätte man das sinnvoller lösen können? Genau, über einen Array ^^° Deswegen: Bevor man eval einsetzt, lieber erstmal überlegen ob man es wirklich braucht. Ich setze bisher eval nur für Metaprogrammierung ein (um Methoden automatisch generieren zu lassen). Dafür gibt es zwar auch elegantere Lösungen, z.B. über define_method, welches einen Proc in eine Methode umwandelt. Aber wie bereits gesagt: über eval erzeugte Methoden sind genauso schnell wie nativ geschriebene Methoden. Bei Procs ist das nicht der Fall. Die sind langsamer, weil sie den umliegenden Scope binden und damit mächtiger als Methoden sind.

    Geändert von -KD- (11.09.2010 um 16:12 Uhr)

  4. #4
    Vielen Dank für eure Hilfe. Sehr ausführliche Erklärungen.

    Vielleicht hätte ich allerdings die Frage in Kontext mit ihrer Verwendung in meinem Projekt stellen sollen.
    Was ich nämlich zu erreichen suche ist die AI für gewisse Feinde zu erstellen.

    Meine bisherige Methode war diese:
    Ich habe die einzelnen Zeilen für den AI-Code als Strings in ein Array gespeichert. Jeder Feind kannte seinen AI-Index.
    In dem Update-Prozess des Feindes wurde dann über:
    Code:
    For line in AI_Code[@ai_index]
     eval(line)
    end
    der AI-Code aufgerufen.
    Es hat funktioniert.

    Allerdings habe ich mich selbst gestern gefragt, nachdem ich die Hilfsdatei des RMXP durchstöbert habe, ob es nicht über ein Proc Objekt eleganter zu lösen wäre.
    Da diese Funktionen allerdings sehr sehr oft durchgeführt werden, pro Frame werden mehrere Zeilen Code für jeden Feind auf der Map durchgeführt, wollte ich bevor ich mit der Umsetzung beginne fragen ob es denn für die Performance irgendwelche Vorteile, oder noch wichtiger, sogar Nachteile geben würde.

    Nebenbei gibt es ja den einen Nachteil, dass ich über die eval(line) Methode den Feind im AI-Code als self ansprechen kann, mit dem Proc Objekt müsste ich es wohl über eine Globale Variable lösen denke ich.

    Wo ich gerade dabei bin, kann ich einem Proc Objekt einen Parameter in der .call Funktion angeben?

  5. #5
    eval dürfte hier schon sehr viel Performance fressen. Zumal du es für jede Zeile neu ausführst (der Overhead also noch größer ist, als würdest du alle Zeilen gleichzeitig evaluieren).

    Erstmal zu deiner Frage mit Procs und Parametern: Natürlich kannst du call Parameter mitgeben:
    Code:
    block = Proc.new do |x, y|
      puts x+y
    end
    block.call(3, 1) #=> 4
    Weiterhin kannst du Procs über instance_eval an einen Objekt-Kontext binden (mir fällt gerade ein: auch eval-Strings kannst du an jedes beliebige Binding binden, in dem du als weiteren Parameter ein Binding übergibst. So gesehen war also meine vorherige Behauptung, Procs wären mächtiger als eval, falsch).

    Code:
    block = Proc.new do |x|
      puts screen_x + x
    end
    $game_player.instance_eval &block
    Hier wird also block innerhalb von $game_player ausgeführt. Er hat also auch Zugriff auf alle Instanzvariablen und Methoden von $game_player, gleichzeitig aber auch Zugriff auf alle lokalen Variablen im Scope.

    btw. gibt es noch eine weitere Möglichkeit, wie du deinen Gegnern "KI-Code" mitgeben kannst:
    Code:
    module KI
      class Aggressive
        def decide_what_to_do(enemy)
          # hier kommt der KI-Code rein
        end
      end
      class Defensive
        def decide_what_to_do(enemy)
          # hier kommt der KI-Code rein
        end
      end
      class Passive
        def decide_what_to_do(enemy)
          # hier kommt der KI-Code rein
        end
      end
      # ...
    end
    
    # ....
    mein_gegner.ki = KI::Defensive.new
    anderer_gegner.ki = KI::Aggressive.new
    # ...
    Diese Lösung ist hochgradig performant (performanter als Procs und deutlich performanter als eval). Außerdem lassen sich deine Gegner-Objekte problemlos abspeichern (Procs lassen sich nämlich nicht abspeichern!). Weiterhin ist das ein recht üblicher Ansatz für derlei Probleme, auch in anderen Programmiersprachen, wo es eval und Procs nicht gibt.

  6. #6
    Ah, vielen Dank. Das ist ein ganzer Haufen von Informationen die für mich sehr sehr nützlich sind.

    Mein Hauptproblem war es eigentlich, dass ich, wenn ich den Code als Text gespeichert hatte und ihn über eval(code) aufrufen ließ auf alle Variablen zugreifen, und den Feind über self ansprechen konnte.
    Als ich versucht hatte es auch Proc Objekte zu übertragen musste ich den Feind, mit meinem begrenzten Wissenstand, als globale Variable definieren um auf ihn zugreifen zu können.

    Nun kann ich also entweder den Code aus dem Proc Objekt über .instance_eval direkt in meinen Feind einbinden als auch Parameter im .call Befehl durchgeben um dadurch meinen Feind als Parameter zu übergeben.

    Eine Frage habe ich jedoch. Für jeden Feind ein neues Objekt der AI Klasse zu erzeugen, wie in deinem letzten Beispiel, anstatt die AI als Konstante zu definieren und jeden Feind auf den selben Code zugreifen zu lassen, ist dieser Weg nicht aufwendiger für den Computer?

    Ich meine, ich könnte das Proc Objekt ja als Konstante speichern und jeden Feind dieses Proc Objekt als AI Code benutzen lassen. Wäre dies nicht besser als eine AI-Instanz im Feind zu erzeugen und zu speichern?

  7. #7
    Du kannst die AI-Objekte auch als Konstanten speichern. Dann verbrauchst du weniger Speicherplatz. Aber das ist jetzt nicht soo~ tragisch, denn soviel Speicher kosten die Objekte nicht.
    Du kannst auch das Singleton-Pattern anwenden und deinen KI-Klassen eine Methode geben, welche immer dieselbe Instanz zurückgibt.
    Code:
    module KI
      class KI
        def self.instance()
          @instance ||= new()
        end
      end
      class Aggressive < KI
        def decide_what_to_do(enemy)
          # ...
        end
      end
      # ...
    end
    # ...
    ki = KI::Aggressive.instance
    ki2 = KI::Aggressive.instance
    ki == ki2 # true
    Du solltest dich auf diese Identität aber nicht verlassen. Beim Abspeichern und Neuladen der KIs haben die wieder eine eigene Instanz. Aber wie gesagt: du sparst dadurch letztlich nur etwas Speicher, mehr nicht.

  8. #8
    Nun, falls es nur Vorteile bringt doch keine Nachteile dann ist es mir recht.

    Vielen Dank für die ausführliche Hilfe.


    Ich habe noch eine kleine Frage, könntest du mir genau erklären was dieses "&" Zeichen in folgender Zeile bedeutet?
    Code:
    $game_player.instance_eval &block
    Dass es nicht funktioniert ohne das Zeichen zu verwenden habe ich bereits feststellen müssen.

    Geändert von Cornix (13.09.2010 um 14:02 Uhr)

Berechtigungen

  • Neue Themen erstellen: Nein
  • Themen beantworten: Nein
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein
  •