Stärken des x86 nutzen - aber wie?


  • Mod

    Im IRC kam die Diskussion auf, in welche Richtung man PrettyOS lenken könnte. Ein Vorschlag war, die Stärken des x86 zu nutzen. Typische Antwort. "Was? Der hat Stärken?" Daher die Frage: Wie sollte man vorgehen, um die Architektur eines modernen x86-Prozessors (zunächst noch die 32 Bit-Version) zu nutzen? 😕



  • Erster Schritt wäre, auf die Datentypen uint8_t, uint16_t und bool zu verzichten 😉



  • Erhard Henkes schrieb:

    Im IRC kam die Diskussion auf, in welche Richtung man PrettyOS lenken könnte. Ein Vorschlag war, die Stärken des x86 zu nutzen. Typische Antwort. "Was? Der hat Stärken?"

    Diese Antwort ist finde ich gar nicht so verkehrt. Das was man als Stärken interpretieren kann, sind ja erstmal nur Eigenschaften. Welche Eigenschaften sich als Stärke erweisen kann man nur sagen, wenn man auch einen Bezug zu dem Ziel hat, was man erreichen möchte. Ein Ziel ist ja, dass PrettyOS zum Lernen brauchbar ist. Dann kann man die "Stärken von x86" so umsetzen, dass sich viele Datenstrukturen auf Objekte in C abbilden lassen.

    abc.w schrieb:

    Erster Schritt wäre, auf die Datentypen uint8_t, uint16_t und bool zu verzichten 😉

    Das hat sicherlich Erläuterungsbedarf, falls das ernst gemeint war.



  • Das kann nicht ernst gemeint sein, weil es dafür int_fast8_t und int_fast16_t gibt. Diese werden bei x86 normalerweise auf int getypedeft und sind somit so schnell wie int32_t. Wenn man int8_t und int16_t nimmt, dann braucht man auch genau 8 bzw. 16 Bits und kann sich ganz bestimmt nicht auf fehlende Performance berufen. 😉
    bool sollte ja ein Macro für _Bool sein und ich denke, dass der Compiler auch dafür intern int nimmt, wobei ich das hier nicht weiß.



  • Zum Teil übertrieben, zum Teil ist es wirklich so, dass wenn man diese Datentypen benutzt und auch noch die Ausrichtung der Variablen im Speicher beeinflusst (mit __attribute__((packed) z.B.), kann die arme 32 Bit CPU nicht anders, als weitere Zyklen einfügen. In PrettyOS kann man sich z.B. die gdt.o mit objdump anschauen:

    00000054 <_gdt_install>:
      54:	83 ec 10             	sub    $0x10,%esp
      57:	66 c7 05 00 00 00 00 	movw   $0x2f,0x0
      5e:	2f 00 
      60:	c7 05 02 00 00 00 00 	movl   $0x0,0x2
      67:	00 00 00 
      6a:	66 c7 05 02 00 00 00 	movw   $0x0,0x2
      71:	00 00 
      73:	c6 05 04 00 00 00 00 	movb   $0x0,0x4
      7a:	c6 05 07 00 00 00 00 	movb   $0x0,0x7
      81:	66 c7 05 00 00 00 00 	movw   $0x0,0x0
      88:	00 00 
      8a:	c6 05 06 00 00 00 00 	movb   $0x0,0x6
      91:	c6 05 05 00 00 00 00 	movb   $0x0,0x5
      98:	66 c7 05 0a 00 00 00 	movw   $0x0,0xa
      9f:	00 00 
      a1:	c6 05 0c 00 00 00 00 	movb   $0x0,0xc
      a8:	c6 05 0f 00 00 00 00 	movb   $0x0,0xf
      af:	66 c7 05 08 00 00 00 	movw   $0xffff,0x8
    ...
    

    Instruktionen mit Prefix 66h (Zeilen 3, 7, 11 usw.) kann man sich als Stolpersteine vorstellen, so was wie "Du, 32 Bit CPU, zieht mal die Handbremse an und mach mal erstmal Kleinkram mit diesem 16 Bit Dings da" 🙂
    Bezüglich bool bin ich der Meinung, dass es auf der Kernel-Ebene nichts zu suchen hat. bool ist ein abstraktes Ding, wo Informatiker gesagt haben, es sei false oder true. Reale CPU kennt kein false und kein true. CPUs können bei sich nur ein gewisses Zero Flag setzen, was soviel bedeutet wie, Null oder oder ungleich Null. Mehr kann die reale CPU nicht. Deswegen ist es strategisch besser im Code etwas mit Null oder ungleich Null zu vergleichen...



  • Gibt es Messungen, wo die böse 66 zeigt, daß sie langsam macht?



  • Man kann es hier nachlesen: http://www.agner.org/optimize/ pdf Dokumente in "2. Optimizing subroutines in assembly..." und "3. The microarchitecture of Intel..."
    Eigene Messungen habe ich keine gemacht...



  • Ja, klar, wir machen alle Felder in der GDT 32 Bits breit. Super Idee. 😃

    Spaß beiseite. Ich gehe mal davon aus, dass du meinst, man sollte jeden Eintrag in zwei uint32_t-Einträge aufsplitten. Das wäre natürlich möglich, würde das Verständnis des Codes aber erstens extrem erschweren und zweitens nicht wirklich viel mehr Performance bringen, da die GDT nunmal nur einmal initialisiert wird und einmal ein paar Zyklen gewonnen bringt nicht viel. 😉


  • Mod

    Dennoch interessanter Punkt, am Beispiel GDT aber nicht nutzbringend verwertbar.



  • Ist ja eigentlisch Schade zu sehen das der gcc es nicht schafft das befüllen der Structs zu optimieren. Immerhin werden schon die Funktionsaufrufe und damit verbundene Berechnungen wegoptimiert und durch Konstanten ersetzt. Da sollte es doch nicht mehr so schwer sein zwei 16 Bit Konstanten zusammenzusetzen und mit einer 32 Bit operation in den Speicher zu schreiben.

    Ich habe das auch mal Testweise mit -O3 und ohne packed ausprobiert, da kommt exakt der gleiche Maschinencode bei raus.

    Aber noch was zum eigentlichen Thema:
    Eine Idee wäre APIC Unterstützung. Das würde auch die Grundlage für Multiprozessor/Multicore Unterstützung legen. Für den Anfang könnte man weitere evtl. vorhandene CPUs/Kerne dann erstmal im Leerlauf lassen um sich nicht grundlegende Probleme zu verkomplizieren. Hatte ich im IRC ja auch schon kurz erwähnt als wir das grob in der Vorlesung behandelt hatten. Die CPU Unterstützung soll wohl schon seit dem Pentium Pro vorhanden sein und kann über cpuid abgefragt werden. Der I/O APIC Teil, der im Chipsatz liegt, soll auf Boards für Einzelprozessorsysteme allerdings erst ab 2000 Standard geworden sein und war vorher nur im höheren Preissegment vorhanden.

    Ein recht neues Thema wäre Hardwarevirtualisierung. Ich wüsste nicht wie man das Sinnvoll für ein OS (abgesehen von einem Exokernel) nutzen könnte. Außerdem braucht man dafür recht aktuelle Hardware und Emulatoren dürften damit auch noch nicht viel anfangen können.

    Ansonsten fällt mir grad nur noch MMX/SSE ein, was aber nur wenig mit dem Betriebsystem zu tun hat. Man braucht eigentlich nur dafür sorgen das die Register beim Prozesswechsel mit gesichert werden. Alles andere ist Sache der Usermode Programme.



  • Tobiking2 schrieb:

    Ist ja eigentlisch Schade zu sehen das der gcc es nicht schafft das befüllen der Structs zu optimieren. Immerhin werden schon die Funktionsaufrufe und damit verbundene Berechnungen wegoptimiert und durch Konstanten ersetzt. Da sollte es doch nicht mehr so schwer sein zwei 16 Bit Konstanten zusammenzusetzen und mit einer 32 Bit operation in den Speicher zu schreiben.

    gcc ist Open Source, niemand hindert Dich daran, die Sache zu verbessern 😉



  • Tobiking2 schrieb:

    Ein recht neues Thema wäre Hardwarevirtualisierung. Ich wüsste nicht wie man das Sinnvoll für ein OS (abgesehen von einem Exokernel) nutzen könnte. Außerdem braucht man dafür recht aktuelle Hardware und Emulatoren dürften damit auch noch nicht viel anfangen können.

    Also zumindest qemu kann SVM. Auf AMD-Maschinen kann man es auch mit KVM nutzen (also "verschachtelte" Virtualisierung machen).


  • Mod

    Nur zur Erläuterung der verwendeten Abkürzungen:
    SVM = Secure Virtual Machine http://de.wikipedia.org/wiki/Secure_Virtual_Machine
    KVM = Kernel-based Virtual Machine http://de.wikipedia.org/wiki/Kernel-based_Virtual_Machine http://www.linux-kvm.org/page/Main_Page



  • abc.w schrieb:

    In PrettyOS kann man sich z.B. die gdt.o mit objdump anschauen:

    ...
    

    Tobiking2 schrieb:

    Ist ja eigentlisch Schade zu sehen das der gcc es nicht schafft das befüllen der Structs zu optimieren.

    @beide --> http://gcc.gnu.org/bugzilla/show_bug.cgi?id=22141 wenn es euch wirklich ein anliegen ist ...



  • Was 8/16 Bit angeht, bzw. die fast_xx_t Typen, gab es auf der Boost Liste mal ne Diskussion. Ergebnis war wenn ich mich richtig erinnere, dass auf aktuellen x86 CPUs es mittlerweile total wurscht ist, ob man nun 16 Bit oder gleich 32 liest. Bzw. oft sogar besser die kleineren Typen zu verwenden, wenn man damit Platz sparen kann.



  • hustbaer schrieb:

    Ergebnis war wenn ich mich richtig erinnere, dass auf aktuellen x86 CPUs es mittlerweile total wurscht ist, ob man nun 16 Bit oder gleich 32 liest. Bzw. oft sogar besser die kleineren Typen zu verwenden, wenn man damit Platz sparen kann.

    Das könnte stimmen. Habe neulich aus Neugier eine kleine Testapplikation mit GRUB gebootet, um die Takte fürs 16 und 32 Bit Lesen zu messen. Die Testapplikation war recht primitiv, weil kein Windows und kein Linux, sondern eben "standalone". Die Interrupts wurden gesperrt und in denen Testfunktionen wurde 1 Million mal von einer Adresse gelesen.
    Hier das Ergebnis auf meinem Schmierzettel:
    1 Mio. mal ein 16 Bit Wort lesen:
    1000406
    1000472
    1000428
    1000461
    1000406

    1 Mio. mal ein 32 bit Wort lesen:
    1000406
    1000549
    1000461
    1000395
    1000395

    Zum Auslesen des Time stamp counters mit drum und dran kostet konstant 407 Takte. D.h. diese 407 Takte muss man noch von jeder der obigen Zahlen abziehen.
    Also, für mich sieht es so aus, dass es keinen Unterschied ausmacht.
    Ich schäme mich auch ein wenig dafür, was ich oben mit "Handbremse" und so was geschrieben habe.
    Das ist ärgerlich. Sobald man selbst anfängt, den Dingen auf den Grund zu gehen, stellt man unerwartet Dinge fest, die nichts mit dem gemeinsam haben, was man irgendwo gelesen hatte o.ä. Auf nichts ist Verlass...
    Das Gerüst der Testapplikation habe ich noch, ist übrigens C++ und ein Überbleibsel von meinem Traum, einen C++ Kernel zu schreiben 😉



  • Wie man aber sieht, dauert es genauso lange, ein Word zu lesen wie ein DWord. Das heißt, man kann auch immer gleich DWords lesen.

    (Man könnte auch sagen, es dauert genauso lange, zwei Millionen Bytes als Words zu lesen wie vier Millionen Bytes als DWords zu lesen)

    Deshalb sollte man offensichtlich immer aufeinanderfolgende Wordzugriffe zu DWordzugriffen zusammenfassen (deinen Ergebnissen nach).


Anmelden zum Antworten