Emulator in Python...

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

jens hat geschrieben:Simple6809 BASIC läuft auf meinem Rechner mit CPython aktuell mit rund 280.000 CPU cycles/sec. Also noch weit weg von den 895.000 der echten CPU...
...
Wobei ich glaube die Werte sind so schlecht, weil die Aktuelle Lösung mit subprocess und socket Kommunikation zwischen CPU und Peripherie schlecht ist. Glaube das bremst extrem.
Jup...

Hab mal ein Test Skript gemacht, welches ohne socket Kommunikation auskommt und direkt auf Konsole Läuft.

Siehe da, mit CPython gibt es dann 490.000 CPU cycles/sec.
PyPy braucht ein wenig zum warm werden, allerdings gibt es schon nach 10Sek. 6.900.000 CPU cycles/sec. :shock: Also schon mal fast 8fache Echtzeit ;) Ein wenig später geht es dann bis ~8.000.000 hoch...

Interessant ist, wie die Optimierung von PyPy läuft. Man kann zusehen wie es schneller wird. Bsp, das Test script:

Code: Alles auswählen

10 FOR I=1 TO 3
20 PRINT STR$(I)+" DRAGONPY"
30 NEXT I
Das erweitern wir mal zu einer Endlosschleife mit einem simplen:

Code: Alles auswählen

40 GOTO 10
dann mal mit RUN ansehen... Erst geht es "gemütlich", dann schneller und noch schneller und dann rast es ;)



Mit der TK Oberfläche sieht es z.Z. so aus:
Bild

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Hab nun mal mehr über multiprocessing nachgedacht und gleich mal einen Test gemacht:

https://github.com/jedie/DragonPy/blob/ ... concept.py

Ich möchte halt eine Trennung zwischen CPU/Speicher und der Peripherie/GUI.
In diesem Fall startet CPU/Speicher über multiprocessing die Peripherie/GUI. So rum, ist aber wohl doof: Funktioniert unter Windows. Aber unter Linux kommt es zu einem Fehler im pager.py:

Code: Alles auswählen

    old_settings = termios.tcgetattr(fd)
error: (25, 'Inappropriate ioctl for device')
Denke mal einfach, es müßte anders herrum gestartet werden: Peripherie/GUI muß CPU/Speicher starten.

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

jens hat geschrieben:Denke mal einfach, es müßte anders herrum gestartet werden: Peripherie/GUI muß CPU/Speicher starten.
So einfach ist das "umdrehen" nicht wirklich...
Zumindest weiß ich nicht, wie das einfach gehen kann als mein Versuch:

https://github.com/jedie/DragonPy/blob/ ... oncept2.py

Umständlich ist auch das komplette Beenden.

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Hab das "concept2.py" im Branch "multiprocessing2" angefangen umzusetzten:

https://github.com/jedie/DragonPy/compa ... rocessing2

Sieht recht erfolgversprechend aus. Mit dem Simple6809 ROM und der TKinter Oberfläche ist es spürbar schneller.

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

jens hat geschrieben:Hab das "concept2.py" im Branch "multiprocessing2" angefangen umzusetzten:

https://github.com/jedie/DragonPy/compa ... rocessing2
Der Branch ist nun im master, mit: https://github.com/jedie/DragonPy/commi ... 694a8c8daf

Somit sieht es nun so aus:

Code: Alles auswählen

 process_main.py                                     process_sub.py          
+-------------------------+                         +-----------------------+
|                         |                         |                       |
| BusCommunicationThread<--->multiprocessing.Queue<--------->Memory         |
|        +    ^           |                         |        +    ^         |
|        |    |           |                         |        |    |         |
|        |    |           |                         |        |    |         |
|        |    |           |                         |        |    |         |
|        v    +           |                         |        v    +         |
|       periphery         |                         |    6809 CPU Thread    |
|         +  ^            |                         |       +      ^        |
|         |  |            |                         |       |      |        |
|         |  |            |                         |       |      |        |
|         |  |            |                         |       v      +        |
|         v  +            |                         |  http control server  |
|     GUI mainloop        |                         |                       |
|                         |                         |                       |
+-------------------------+                         +-----------------------+

Als nächstes möchte ich mal versuchen Bottle als CPU http Server zu nutzten.

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Ich hab wieder mit --display_cycle nachgesehen, was dabei rum kommt.

Enttäuschend :(

Die aktuelle multiprocess Lösung:

CPython liegt bei ~115.000 CPU cycles/sec. mit Tk GUI und ~170.000 bei einfacher console.
PyPy legt bei Tk GUI mit nur ~120.000 los und kommt auch nur bis ~150.000 CPU cycles/sec.
Bei einfacher Console fängt PyPy bei ~225.000 an und kommt auf ~310.000 CPU cycles/sec.

Also immer noch weit weg von den 895.000 der echten CPU...

Wenn ich "console_6809_test.py" teste, die ohne multiprocess arbeitet, also all Threads in einem Prozess, sieht es so aus:
CPython ~450.000 und PyPy fängt bei ~5.400.000 an und kommt bis ~7.500.000 CPU cycles/sec.

Offensichtlich bremst die Interprocess Kommunikation um einiges aus.

Ich hatte multiprocessing.JoinableQueue() genutzt und mit .task_done() und .join() gearbeitet.
Da es aber eh nur zwei Processe sind, habe ich auf multiprocessing.Queue() umgestellt: https://github.com/jedie/DragonPy/commi ... 7bdeedff05

CPython mit Tk GUI: ~180.000 (statt ~115.000)
PyPy mit Tk GUI: ~215.000 und geht auf ~250.000 CPU cycles/sec.


Frage mir allerdings, ob multiprocess sich überhaupt lohnt, oder ich besser nur auf Threads setzten sollte.

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Ob sich Multiprocessing lohnt, das hängt stark vom Problem ab. Um sinnvoll eingesetzt zu werden, müssen die Prozesse gut getrennt sein und genügend Aufgaben zum Abarbeiten haben. Wenn du ständig Nachrichten zwischen den Prozessen austauscht, synchronisierst und wartest, dann ist der Overhead schnell so groß, dass alles ausgebremst wird.
Das Leben ist wie ein Tennisball.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

So... Bin gerade mächtig stolz... Denn mit https://github.com/jedie/DragonPy/commi ... b54296fc15 sehe ich nun:
````hCi`qyxr`DRAGON`DATA`LTD````````qvK`BASIC`INTERPRETER`qnp```````hCi`qyxr`BY`MICROSOFT```````````````````````````````````````````OK`
Ist nicht alles richtig, weil es quasi nur ein char = chr(byte) ist und die Kiste halt nicht wirklich ASCII liefert. Aber die Informationen, wie die ASCII-Tabelle ist, habe ich aber schon vor langer Zeit eingesammelt: http://archive.worldofdragon.org/index. ... le=CharMap bzw. https://gist.github.com/jedie/6975555 :lol:

Von den drei wichtigen Chips: SAM, PIA und VDG habe ich im Prinzip nur Dummy Funktionien Implementiert sie nichts machen. Hoffe mehr brauche ich auch nicht um im Text Modus mit dem BASIC Interpreter zu spielen. Das war ja eigentlich mein Ursprüngliches Ziel.

Nun muß ich sehen, wie ist das Display realisiere. Möchte gern genau die Umsetzung, wie das Original machen. Also ein Tkinter text-Widget zu füllen wäre doof. Ich brauche ein Starres Raster bei der ich die Zeichen gezielt setzten kann.
Evtl. also wirklich pygame nutzten?!?

EDIT: So, noch ein wenig gehackt und ta ta taaaa:
(C) 1982 DRAGON DATA LTD
16K BASIC INTERPRETER 1.0
(C) 1982 BY MICROSOFT
OK

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Nachdem die Eingabe von Zeichen funktioniert (wesentlich komplizierter als eine simple Serielle Schnittstelle) kann man das Dragon 32 / 64 ROM nun im Text-Modus nutzten. Sieht dann so aus:

Bild



Ist mit PyGame gemacht. Man sieht auch den Cursor blinken und Farben gehen auch:

Bild



Was noch nicht geht, ist aktuelle "Kleinbuchstaben" (Der Dragon kennt zwar eigentlich keine richtige Kleinschreibung, aber die Buchstaben werden invertiert dargestellt...)

Auch das Scrollen macht z.Z. Probleme. Anscheinend wird vom ROM nur jede zweite Zeile bewegt.... Wobei mir gerade beim schreiben spontan einfällt: Vielleicht wird dazu statt 1 Byte ein 16-bit Word genutzt, ich habe z.Z. allerdings nur 1-Byte lesen/schreiben implementiert!

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

jens hat geschrieben:Auch das Scrollen macht z.Z. Probleme. Anscheinend wird vom ROM nur jede zweite Zeile bewegt.... Wobei mir gerade beim schreiben spontan einfällt: Vielleicht wird dazu statt 1 Byte ein 16-bit Word genutzt, ich habe z.Z. allerdings nur 1-Byte lesen/schreiben implementiert!
An der nicht richtigen Trennung von byte/word anfragen lag ist. Ist behoben...

Nun überlege ich, ob ich das ganze multiprocessing in die Tonne hauen soll oder nicht. :K

Zumindest die jetzige Aufteilung macht keinen Sinn:

Code: Alles auswählen

 process_main.py                                     process_sub.py          
+-------------------------+                         +-----------------------+
|                         |                         |                       |
| BusCommunicationThread<--->multiprocessing.Queue<--------->Memory         |
|        +    ^           |                         |        +    ^         |
|        |    |           |                         |        |    |         |
|        |    |           |                         |        |    |         |
|        |    |           |                         |        |    |         |
|        v    +           |                         |        v    +         |
|       periphery         |                         |    6809 CPU Thread    |
|         +  ^            |                         |       +      ^        |
|         |  |            |                         |       |      |        |
|         |  |            |                         |       |      |        |
|         |  |            |                         |       v      +        |
|         v  +            |                         |  http control server  |
|     GUI mainloop        |                         |                       |
|                         |                         |                       |
+-------------------------+                         +-----------------------+
Über die verbindenden multiprocessing.Queue gehen einfach viel zu viele Daten.

Ich überlege nun, multiprocessing durch threads zu ersetzten und/oder anders zu Trennen.
So oder so, muß ich die GUI vom Rest trennen, oder? Ansonsten weiß ich nicht, wie ich den Tkinter mainloop und den CPU loop gleichzeitig aufrufen kann.

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Geht ganz gut vorran, ohne PyGame, nur mit Tk. Dabei gibt es nun die Original Schrift :D

Bild

und dieses Listing läuft auch so wie es soll:

Bild

Die cycles/sec Werte im Screenshot sind nicht ganz richtig. CPython kommt so auf ~400.000 und PyPy rennt recht unterschiedlich.

EDIT: Bilder ausgetauscht...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Nur eine Kleinigkeit: In den Titeln der Fenster sind die Anzahl der Zeilen und Spalten vertauscht.
Das Leben ist wie ein Tennisball.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Ups :oops:

Danke fürs melden ;)

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Mit dem multiprocessing bin ich noch nicht ganz durch... Ich über lege ob ich bei der alten Implementierung Fehler gemacht habe, bzw. Ob es nicht besser geht...

Vielleicht könnte ich die GUI im hauptprosess asynchron mit dem CPU unterprozess koppeln.
D.h. die GUI soll nicht die CPU 'stoppen' und sagen hier ist eine neue Tastatureingabe... Und die CPU sollte nicht warten, wenn ein neues Zeichen auf dem Bildschirm erscheinen soll...

Evtl. Sollte die interprocess Kommunikation jeweils in einem seperaten thread laufen?!?

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

So, hab das ganze nochmal reichlich umgebaut und aufgeräumt.

Im Prinzip sieht es grob nun so aus:

Code: Alles auswählen

    Main Thread                                     Sub Thread      
+------------------+                         +---------------------+
|                  |                         |                     |
| +-------------+  |  CPU cycles/sec queue   |                     |
| |            <------------------------------------+6809 CPU      |
| |             |  |                         |       +     ^       |
| |     GUI     |  |                         |       |     |       |
| |             |  | Display RAM write queue |    +--v-----+--+    |
| |  .--------------------------------------------+   Memory  |    |
| |  |          |  |                         |    +--+-----^--+    |
| |  |          |  |                         |       |     |       |
| |  |          |  |                         | +-----v-----+-----+ |
| |  |          |  |                         | |    Periphery    | |
| |  |          |  |     Keyboard queue      | |   MC6883 SAM    | |
| |  |          +--------------------------------->MC6821 PIA    | |
| |  |          |  |                         | |                 | |
| +--+-----^----+  |                         | |                 | |
|    |     |       |                         | +-----------------+ |
|    |     |       |                         |                     |
| +--v-----+----+  |                         |                     |
| |             |  |                         |                     |
| |   Display   |  |                         |                     |
| |             |  |                         |                     |
| +-------------+  |                         |                     |
+------------------+                         +---------------------+
[/size]

Ich habe also im prinzip zwei Threads. Die Daten zwischen den Thread wird mit Queue.Queue() ausgetauscht, wobei neue Daten immer mit .put_nowait() abgesetzt werden. Denn ein neues Zeichen auf dem Bildschirm muß ja nicht "in Echtzeit" erscheinen. Die CPU kann ja schon mal weiter machen ;)

Das ganze sollte sich relativ schnell auch mit multiprocessing betreiben lassen. Eigentlich dürfte das aber nur was bringen, wenn die GUI viel Rechenzeit konsumiert, die dann dem CPU Thread fehlt. Aber ich glaube das ist nicht der Fall.

EDIT: btw. das Emulieren eines TRS-80 Color Computer kurz CoCo funktioniert nun auch ;) Man kann nun auch eingaben machen...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

``Queue.put_nowait`` macht nicht das was du vermutest! Wenn ein anderer Thread gerade auf die Warteschlange zugreift und du ein put_nowait machst, dann wird das Element *nicht* der Warteschlange hinzugefügt. Das nowait bedeutet *nicht*, dass das Element ohne Warten der Warteschlange hinzugefügt wird, sondern dass bei blockierter Warteschlange das Element nicht eingefügt wird und dir stattdessen eine Exception um die Ohren fliegt.

Wenn du nicht-blockierendes Warten haben willst, bzw. höchstens so lange warten willst wie das Einfügen oder Rausholen eines Elements dauert, dann musst du eine weitere Indirektstufe über einen Thread einführen:

Code: Alles auswählen

while True:
    outqueue.put(inqueue.get())
Der Thread, welcher outqueue-Elemente entgegennimmt, kann dann beliebig lange auf den Elementen rumarbeiten.
Das Leben ist wie ein Tennisball.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Oh! Wichtiger Hinweis, das wußte ich wirklich nicht :oops:

In meinem Fall sind das allerdings auch nur zwei Threads. Der Haupt-Thread und der Neben-Thread...

Ich Frage mich, ob ich in dieser "nur zwei Threads" Konstellation, überhaupt mit "warten" rechnen muß...
Auf die schnelle werde ich erstmal alle put_nowait durch ein put() ersetzten.

Ob das ganze mit dem PyPy Problem in Relation ist? -> http://www.python-forum.de/viewtopic.php?f=1&t=34472

EDIT: In der Doku: https://docs.python.org/2.7/library/que ... .Queue.put steht dazu eigentlich nichts. Also das "eingaben" verloren gehen können.
btw. Frage mich ob ich nicht auch besser Queue.LifoQueue() nutzten sollte. Die Reihenfolge sollte natürlich beibehalten werden. Bei allen Tests bisher war das aber auch der Fall bei mir. Vielleicht wird das erst relevant, wenn mehrere Threads gleichzeitig drauf zugreifen?

EDIT2: Ich hab nun .put() für Display und Eingaben verwendet: https://github.com/jedie/DragonPy/commi ... 41401065c8

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

So, ich habe mir den Code noch einmal angeschaut, ein no_wait-Aufruf sollte beim Einfügen tatsächlich funktionieren, so lange noch genügend Slots verfügbar sind. In wie weit blockiert wird, das hängt dann von der deque-Implementierung ab.

Code: Alles auswählen

    def put(self, item, block=True, timeout=None):
        '''Put an item into the queue.

        If optional args 'block' is true and 'timeout' is None (the default),
        block if necessary until a free slot is available. If 'timeout' is
        a non-negative number, it blocks at most 'timeout' seconds and raises
        the Full exception if no free slot was available within that time.
        Otherwise ('block' is false), put an item on the queue if a free slot
        is immediately available, else raise the Full exception ('timeout'
        is ignored in that case).
        '''
        with self.not_full:
            if self.maxsize > 0:
                if not block:
                    if self._qsize() >= self.maxsize:
                        raise Full
                elif timeout is None:
                    while self._qsize() >= self.maxsize:
                        self.not_full.wait()
                elif timeout < 0:
                    raise ValueError("'timeout' must be a non-negative number")
                else:
                    endtime = time() + timeout
                    while self._qsize() >= self.maxsize:
                        remaining = endtime - time()
                        if remaining <= 0.0:
                            raise Full
                        self.not_full.wait(remaining)
            self._put(item)
            self.unfinished_tasks += 1
            self.not_empty.notify()

Code: Alles auswählen

    # Put a new item in the queue
    def _put(self, item):
        self.queue.append(item)
Ich finde es ein etwas unerwartetes Verhalten, aber OK.
Das Leben ist wie ein Tennisball.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

OK, also man kann sagen, das nie Eingaben verlohren gehen. Es ist nur ein Unterschied ob blockiert wird oder ein raise Full gemacht wird, wenn maxsize erreicht ist. oder?
jens hat geschrieben:Frage mich ob ich nicht auch besser Queue.LifoQueue() nutzten sollte.
Das war natürlich blödsinn: Habe Fifo gelesen und nicht Lifo... :oops:

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Wenn ich nichts übersehen habe, was mir natürlich nie passiert :oops:, dann sollte das so sein. Ja.
jens hat geschrieben:Das war natürlich blödsinn: Habe Fifo gelesen und nicht Lifo... :oops:
Hehe, ein Fifo gäbe aber bestimmt sehr interessante Effekte. Aus den Effekten auf die Fehlerursache zu schließen wäre bestimmt eine "spannende" Sache :shock:
Das Leben ist wie ein Tennisball.
Antworten