2012-03-29 18 views
6

ich schreibe einen einfachen emulator in go (sollte ich? Oder soll ich zurück zu c?). Wie auch immer, ich hole die Anweisung und dekodiere sie. An dieser Stelle habe ich ein Byte wie 0x81, und ich muss die richtige Funktion ausführen.tabelle der funktionen vs schalter in golang

sollte ich habe so etwas wie dieses

func (sys *cpu) eval() { 
    switch opcode { 
    case 0x80: 
     sys.add(sys.b) 
    case 0x81: 
     sys.add(sys.c) 
    etc 
    } 
} 

oder so etwas wie dieses

var fnTable = []func(*cpu) { 
    0x80: func(sys *cpu) { 
     sys.add(sys.b) 
    }, 
    0x81: func(sys *cpu) { 
     sys.add(sys.c) 
    } 
} 
func (sys *cpu) eval() { 
    return fnTable[opcode](sys) 
} 

1.Welche besser ist?
2.Welche ist schneller?
auch
3.kann ich eine Funktion inline deklarieren?
4.i habe eine cpustruct in der ich die Register usw. habe, wäre es schneller, wenn ich die Register und alle als globals habe? (ohne die struct)

vielen Dank.

+0

Ich wäre überrascht, wenn der Compiler keine Sprungtabelle für so etwas erstellen würde, besonders wenn 'opcode' ein Byte ist. Ich bin interessiert, die Ergebnisse Ihrer Benchmarks zu sehen. –

+0

Nur ein Hinweis für Ihre nächsten Fragen: Ihre Frage 4 steht nicht wirklich zum Thema Ihrer * allgemeinen * Frage. IMO, obwohl eine Frage mehrere Fragen zu Aspekten der allgemeinen Frage/Thema enthalten kann, sollten solche off-topic Dinge in einer separaten Frage gefragt werden. – Kissaki

+0

Überprüfen Sie im Zweifelsfall einfach die Baugruppenausgabe. –

Antwort

2
  1. Die erste Version sieht besser aus, YMMV.

  2. Benchmark es. Hängt davon ab, wie gut der Compiler bei der Optimierung ist. Die "Sprungtabelle" -Version könnte schneller sein, wenn der Compiler nicht hart genug versucht, um zu optimieren.

  3. Abhängig von Ihrer Definition von was "Funktion inline deklarieren" ist. Go kann Funktionen/Methoden nur auf der obersten Ebene deklarieren und definieren. Aber Funktionen sind erstklassige Bürger in Go, also kann man Variablen/Parameter/Rückgabewerte und strukturierte Typen von Funktionstyp haben. An allen diesen Stellen kann der Variablen/Feld/Element ... auch function literal zugewiesen werden.

  4. Evtl. Trotzdem würde ich vorschlagen, den CPU-Status nicht in einer globalen Variablen zu belassen. Sobald Sie möglicherweise entscheiden, mehradrige zu gehen emuliert, wird es willkommen ;-)

+0

danke für die Antwort. sollte ich eine test eingabedatei mit 10000 operationen von 80 und 81 mal machen, und sie mal? oder sollte ich noch viele Opcodes drin haben? Ich kann diese Art von Benchmarking durchführen. Über das Inline-Ding dachte ich an Inline-Funktionen von C, damit der Code der Funktion in die Orte integriert werden kann, an denen er verwendet wird. kann ich das machen? also sollte ich den Zustand wie ich jetzt in einer Struktur halten. opfern Sie ein wenig Geschwindigkeit für etwas Bequemlichkeit. Zuletzt, sollte ich es weiter machen, oder soll ich es in C machen? Ich lerne gehen (als ein neues System lang), und deshalb habe ich es in Gang gesetzt. – pvinis

+1

- Der Compiler kann wahrscheinlich anderen Code für umschaltbare [non] zusammenhängende Wertebereiche erzeugen, Bereiche, die alle Werte abdecken (256 für einen Byteausdruck) usw. Am besten experimentiert man, wenn man Compiler die Interna nicht kennt (mich nicht). - Inlining ist automatisch in Go für Funktionen, die den Kriterien des Compilers entsprechen (wie klein/kurz oder nur aus einer Return-Anweisung, ...). - Der Emulator wird wahrscheinlich in C ein bisschen schneller sein (Go testet beispielsweise Array-Grenzen-Checks). Aber ich denke, du wirst es in Go früher/einfacher arbeiten lassen. Ich würde Go wählen, zumindest wirst du etwas Neues lernen ;-) – zzzz

+0

cool. Vielen Dank!!! – pvinis

15

sein ich einige Benchmarks haben und die Tabelle Version ist schneller als die Switch-Version, sobald Sie mehr als etwa 4 Fälle haben.

Ich war überrascht zu entdecken, dass der Go-Compiler (gc, jedenfalls nicht sicher über gccgo) nicht schlau genug zu sein scheint, einen dichten Switch in einen Sprungtisch zu verwandeln.

aktualisieren: Ken Thompson geschrieben auf der Mailingliste Go the difficulties of optimizing switch beschreibt.

+0

Ich habe gerade einige eigene Benchmarks in diesem Bereich durchgeführt und bin zu dem Ergebnis gekommen, dass Switch-Case eine Größenordnung schneller ist. Die Größe meines Befehlssatzes liegt momentan bei 30. Vielleicht wurde GC kürzlich verbessert? –

+0

Es ist mir komisch, dass ein Schalter schneller als ein Array-Lookup wäre, besonders für große Einträge. Ich verstehe das nicht. :/ – weberc2

+2

Für ausreichend große Switches ist es nicht schneller. Kleinere Switches können schneller sein, da sie den Verzweigungsprädiktor und den Codecache der CPU besser nutzen können. –

0

Wenn Sie einen bestimmten Ausdruck haben und ihn für eine große Anzahl von Datenzeilen auswerten möchten, können Sie ihn nur einmal in den Lambda-Baum kompilieren und bei jeder Iteration keine Schalter berechnen alle;

Zum Beispiel gegeben solchen ast: {* (a, {+ (b, c)})}

Compile-Funktion (in sehr grober Pseudo-Sprache) wird in etwa so sein:

func (e *evaluator) compile(brunch ast) { 
    switch brunch.type { 
    case binaryOperator: 
     switch brunch.op { 
     case *: return func() {compile(brunch.arg0) * compile(brunch.arg1)} 
     case +: return func() {compile(brunch.arg0) + compile(brunch.arg1)} 
     } 
    case BasicLit: return func() {return brunch.arg0} 
    case Ident: return func(){return e.GetIdent(brunch.arg0)} 
    } 
} 

So kehrt schließlich kompiliert die func, das muss aufgefordert werden, jede Reihe Ihrer Daten und es wird überhaupt keine Schalter oder andere Berechnungskram geben. Es bleibt die Frage über Operationen mit Daten verschiedener Typen, das ist für Ihre eigenen Forschung;) Dies ist ein interessanter Ansatz, in Situationen, wenn es keine Sprungtabelle Mechanismus zur Verfügung :) aber sicher, Func Call ist komplexer Operation dann springen.