Hast du diesmal noch wirklich geschaut, wann der Zug abfährt?
Ähm, ja, knapp nach einer halben Stunde sollte der dann da sein.
Bist du dir da sicher?
Ja man, in 26 Minuten fährt er von Klais 14.
Klais 14 abfahrt die CE 689 nach der Gruppe Hauptbahnhof. Bitte steig dich ein.
Ach Flo, ernsthaft?
Uff, also das war mal wieder knapp.
Hey Stefan, diesmal hab ich's auch geschafft.
Der Systemcall heute mit folgenden Themen.
Mit alten Speicherabstraktionen, die heute noch für Anwendungsfälle geeignet sind.
Und warum Multithreading nicht mehr händisch gemacht werden sollte.
Herzlich willkommen zu dieser Folge des Systemcalls.
Dem Podcast, in dem wir euch mehr oder weniger aktuelle Forschung im Betriebssystem vorstellen.
Mein Name ist Stefan Naumann und hier zugeschaltet ist wie immer der tolle Florian Rommel. Moin Flo.
Hallo Stefan, heute sind wir sogar ganz besonders aktuell, denn heute geht es um das Fachgruppentreffen in Hamburg.
In der vorigen Sendung haben wir mit Alexander Zübke gesprochen, dem Promotionspreisträger der Fachgruppe.
In dieser Sendung stellen wir euch zwei interessante Vorträge vom Fachgruppentreffen vor.
Das heißt jetzt natürlich nicht, dass die anderen Vorträge nicht auch gut waren, aber wir wollten zwei auswählen.
So Flo, worum geht es bei dir? Bei mir geht es um die Frage, ob die bekannten Speicherabstraktionen noch für die heutigen Anwendungsfälle geeignet sind. Und dafür rede ich über eine Präsentation der Hamburger selber. Und zwar über Xmap.
Als erstes haben sich die Hamburger die bekannten Arten der Speicherverwaltung angeschaut. Und das ist eben Read und Write.
Also damit kann man von einem Storage Device, also einer SSD oder einer Festplatte, Speicher in den Rahmen laden. Und auch wieder zurückschreiben mit Write eben.
Und das muss man natürlich explizit machen. Also ich muss sagen, ich möchte jetzt Read von dieser Stelle in dieser Datei und zwar da und dahin.
Genau, genau. Also das ist die ganz traditionelle Art. Also ich habe einen Puffer im Speicher, den allokier ich mir irgendwie.
Und mit Read lese ich oder mit ähnlichen Systemcalls lese ich von einem Hintergrundspeicher Daten in diesen Puffer rein. Und mit Write kann ich das dann auch wieder umkehren, also wieder zurückschreiben.
Und was ist dann die andere Möglichkeit?
Ja, die andere Möglichkeit ist filebacked memory mapped.io. Mit Mmap macht man das. Und da kann ich eine Datei direkt in den Speicher einblenden.
Also ich kann im Betriebssystem sagen, ich habe hier eine Datei auf der Festplatte und ich möchte, dass die jetzt in meinem Speicher eingeblendet wird. Und dann sorgt das Betriebssystem dafür.
Also das heißt, ich mache jetzt Open auf meine Datei, dann mache ich Mmap und dann lädt mein Betriebssystem die gesamte Datei in meinen Speicher rein? Was passiert, wenn ich jetzt eine 4TB Datei habe und eine 2Gb Arbeitsspeicher?
Genau, also die Verwaltung übernimmt dann das Betriebssystem. Das heißt, die Datei ist noch nicht unbedingt von vornherein da geladen, sondern erst mal legt das Betriebssystem nur so eine Verwaltungsstruktur an in seinen internen Verwaltungsdaten und sagt dann hier, soll diese Datei liegen?
Und wenn ich dann drauf zugreife, dann ist da erst mal nichts gemapped. Also in meinem virtuellen Speicher ist da erst mal nichts hinterlegt. Dann kommt es zu einem Pagevault, wenn ich einen Zugriff mache.
Und dann kann das Betriebssystem on demand sozusagen bei diesem Pagevault die Übertragung machen. Also die Daten von der Festplatte lesen und diese eine Seite, also die Stelle an der Datei jetzt in den Speicher laden.
Und wenn ich dann vom Pagevault wieder zurückkehre in meinen Prozess, dann sind dort eben Daten und es sieht für mich oder es sieht für die Anwendung so aus, als wären die Daten da schon immer gewesen.
Das wird eben auch gerne verwendet von Anwendungen, weil man sich eben keine Gedanken machen muss um, ja wie groß ist mein Puffer, Überadressübersetzungen.
Ich greife einfach direkt an einer Speicherstelle zu. Also die Offset-Berechnung ist ganz einfach und ich muss mich nicht selber darum kümmern, welche Teile der Datei geladen sind, welche wieder zurückgeschrieben werden müssen, wann ich das mache.
Genau, das Betriebssystem übernimmt eben alles. Genau deswegen ist es ja angenehm zu benutzen.
Aber wenn ich jetzt gerade an Datenbanken oder so denke, dann die wollen ja gerade sehr genau festlegen, wann Daten zurückgeschrieben werden.
Genau, die wollen eigentlich die Kontrolle schon haben. Einerseits natürlich, weil sie sowas wie Transaktionssicherheit gewährleisten wollen.
Also sie wollen genau wissen, ok, ab jetzt, ab diesem Zeitpunkt ist das Ganze jetzt auf der Festplatte tatsächlich geschrieben worden oder auf der SSD.
Sozusagen falls mein System abstürzt, bin ich sozusagen auf der sicheren Seite dann.
Auf der anderen Seite geht es da auch um Performance. Wenn ich natürlich ständig diese Pagevaults erzeuge, ja dann verliere ich einfach Zeiten. Also dann sind die Zugriffszeiten höher.
Und natürlich die Datenbank hat natürlich auch das Wissen darüber, was für Seiten noch gebraucht werden.
Genau, ja genau, das ist eben auch was die Hamburger eben in dem Vortrag rüberbringen wollten. Also die Datenbank weiß es eigentlich besser, wie das Betriebssystem ist.
Das Betriebssystem macht irgendwas on demand und hin und wieder nimmt es Seiten auch mal wieder weg, wenn der Speicher knapp wird.
Aber eigentlich weiß die Datenbank selber viel besser, wann Seiten wieder unmapped werden sollen und so weiter, weil die Datenbank ja oft weiß, ob die Seite jetzt den Next wieder benutzt oder nicht.
Ja nicht nur wann, sondern auch welche vor allem.
Genau, deswegen benutzen Datenbanken M-App auch nicht, also die 2-backed-Memory-Mapped-IO, sondern sie machen das eben meistens also heute überall das klassische Read/Write oder verwandtesystem-Calls.
Dazu legen sie sich halt einen Puffer an oder mehrere und lesen dann explizit Dinge da rein, also aus ihrem Hintergrundspeicher, aus der Datei, wo die Datenbank dann tatsächlich resistiert ist und schreiben da rum und schreiben es dann explizit wieder zurück. Genau aus diesen Gründen.
Ok, was haben denn die Hamburger jetzt genau vorgeschlagen?
Genau, also sie haben jetzt die Idee, die die Datenbanken eben bisher verfolgen, also diesen Puffer, ein bisschen erweitert und zwar wollten sie diese Verwaltung, wo welche Teile der Datei liegen mit Hashtables, wollten sie da kriegen.
Deswegen war die Idee, sie besorgen sich einen gigantischen Puffer und belegen einen so großen Puffer wie die Datei, das können auch 4TB sein wie auch immer.
Das ist aber erstmal nicht schlimm, weil wenn ich so ein anonymes Mapping beim Betriebssystem anlege, also anonym heißt das, dass da keine Datei hinterlegt ist aus der Sicht des Betriebssystems, dann ist dieses erstmal nicht besetzt.
Weil auch hier kann ich den gleichen Trick machen, den wir vorher auch schon bei den Fireback-Bereichen hatten, da weiß das Betriebssystem, ok, wenn ich jetzt hier so einen Puffer anlege, so einen gigantischen, dann muss da erstmal noch nichts hinterlegt sein.
Das kann ich ungemannt machen, wenn ich dann Zugriff habe, gibt es einen Page Vault, das Betriebssystem kann dann entsprechend physikalische Seiten dort hinmappen in den virtuellen Adressraum und dann kann ich darauf zugreifen.
Also das heißt, sie holen sich erstmal ein gigantisches Stück virtuellen Speicher?
Ja genau.
Also sie nutzen erstmal aus dem Linux da gar nichts dahinter, weil es total faul ist? Genau, genau, das heißt auch wenn ich da mehr Terabyte hinmappe, dann habe ich die nur nicht wirklich verbraucht.
Erst wenn ich da irgendwas hinschreibe, dann verbrauche ich tatsächlich einzelne Seiten, also wenn ich an eine bestimmte Stelle was schreibe, dann habe ich eben eine 4096-byte-Seite verbraucht.
Das ist jetzt erstmal schön und gut, weil damit entfällt diese Verwaltung mit Hashtabellen, dass ich hier mir speichern muss, ok, von diesem Offset bis zu diesem Offset habe ich, also der Datei habe ich an dieser Speicherstelle geschrieben, von diesem Offset bis zu diesem Offset habe ich was an dieser Speicherstelle,
sondern ich kann einfach so tun, als wäre die ganze Datei da gemapped und mache dann mit "Nach wie vor" "Read&Write", also lade meine Datei an die Stelle, also mit "Read" erstmal.
Also ich lade dann mit "Read" den Offset so und so an den Offset so und so in diesen Materialmapping rein, dass die Datei quasi nach wie vor richtig, aber ein Stückchen aussieht?
Genau, aber nicht die ganze Zeit, ein Datei natürlich, weil das wäre wahrscheinlich dann zu viel Speicher.
So, das funktioniert erstmal gut, ich habe nur ein Problem, ich will natürlich auch dann Sachen wieder wegbekommen, also ich muss Teile wieder anmappen können, also aus diesem Bereich rausziehen wieder.
Sehr gut, würde man ja anmappen benutzen, ne?
Genau, das geht dann eben auch mit dem entsprechenden Systemcall und jetzt haben Sie mal die Performance von diesem ganzen Ding gemessen und das haben Sie einfach mal so angestellt, dass Sie sich eben so eine Seite gemapped haben, da was hingeschrieben haben und dann wieder anmappt durchgeführt haben mit so einem Bereich.
Und das hinschreiben, damit die Seite auch tatsächlich populiert wird durch physische Speicher?
Genau, genau, was hinschreiben, aber sonst haben wir den Fall, das passiert alles lazy, also on demand, dann würde da einfach nichts gemapped sein.
Genau, also Sie mappen anonymen Speicher dahin, also im leeren Bereich, schreiben was hin, also für diesen Performanstest erstmal gar nicht aus einer Datei, sondern Sie schreiben einfach von der Einzelne oder so und dann machen Sie dann anmappen und das führen Sie jetzt in einer Schleife aus.
Und damit testen Sie einfach die Performance von dieser Lösung, also dieser Lösung der Speicherverwaltung oder die Performance eines gewissen Aspekts.
Und das ist jetzt, wie es sich rausstellt, ziemlich langsam, also damit kommen Sie so circa auf 500.000 Allokationen hinschreiben und wieder free, also unmappst pro Sekunde.
Und das ist, wenn man sich das mal vergleicht mit den Zugriffsgeschwindigkeiten von modernen SSDs, die 1,6 Millionen I/O-Operationen pro Sekunde schaffen, ziemlich langsam.
Also dieser Allokator oder dieser...
Dieser Mechanismus im Grunde im Kern ist schon ziemlich langsam.
Genau, genau, dieser Mechanismus, der würde jetzt unsere SSD nicht saturieren können.
Okay, probieren wir das doch mal mit mAdvice, denn das kann ja verschiedene Möglichkeiten speicherlos werden.
Genau, genau, also das war tatsächlich auch, Sie sind vorhin auch gesehen, aber das war tatsächlich die nächste Idee.
Also das Problem ist jetzt, dass der Linux-Kern ja intern noch diese Verwaltungsdaten hat.
Und wenn ich dann einen anonymen Bereich erstelle und den dann wieder wegnehme, dann muss ich diese ganzen Operationen durchführen und das auch wieder verwaltungsabhängig.
Vor allem, ich habe ja den Bereich als ganzes erstellt, jetzt will ich ja einzelne Seiten wieder loswerden.
Also ich muss ja die datenstruktur des VMA, heißt das im Linux-Kern, dann der Stelle aufspalten, was natürlich Zeit frisst.
Ja, ja, ja, das auch noch. Ich weiß gar nicht, ob das in diesem Benchmark gemessen wurde, ehrlich gesagt, weil Leute mit Resonanzen hatten, die sich tatsächlich nur einen Bereich gemappt, der eine Seite groß ist.
Aber gut, auch dann haben Sie diesen Operationen. Ja, das ist richtig. Naja, wie auch immer, es gibt noch diesen mAdviceSystemCall und der erlaubt dem Nutzer, dieses Systemcalls, bestimmte Operationen auf solchen Bereichen durchzuführen.
Und da gibt es das Don't Need Flag. Damit kann ich sagen, dass ich einen bestimmten Bereich nicht mehr brauche.
Was dann der Kernel damit macht, ist nicht ganz klar, aber er kann sich auf jeden Fall entscheiden, wenn er wieder Speicher braucht, die Seite einfach wegzunehmen.
Also die physische Seite, den Hauptspeicher dahinter liegt. Ja genau, also den Hauptspeicher dahinter liegt.
Der Bereich bleibt intakt sozusagen in der Verwaltungsdatenstruktur, aber tatsächlich die Daten, die dahinter liegen, also die Seiten, die können weggenommen werden und für irgendwas anderes verwendet werden, müssen aber nicht.
Aber wenn sie weggenommen werden, dann kriege ich halt beim Next Zugriff wieder einen Pagefold und dann wird dann wieder eine genuhtes Seite gemappt. Also ich sage dem Betriebssystem einfach, hier der Speicher, der ist mir ab jetzt egal.
Kannst ihn mir schon mal wegnehmen. Genau, solange ich da nicht wieder drauf zugreife, sozusagen, also wieder was hinschreibe, dann kannst du dann machen, was du willst.
Und das haben Sie jetzt eben auch in der gleichen Schleife ausgeführt, also jetzt sieht die Schleife eben so aus, dass Sie dann also wieder den Bereich erstellen, Zugriff machen und dann dieses mAdviceDon't Need machen.
Und das Ganze hat sich schon mal ein bisschen verbessert. Damit kommen Sie auf gerade so über eine Million dieser Prozesse pro Sekunde. Und das ist schon mal besser, also eine deutliche Verbesserung.
Aber das kommt natürlich immer noch nicht an die 1,6 Millionen Operationen pro Sekunde von der SSD, oder? Genau, vor allem wenn man da natürlich noch bedenkt, dass wir ja nicht nur eine SSD haben können, sondern eine große Datenbank betreibt, um die 10 SSDs und dann reizen wir ja die Geschwindigkeit erst recht nicht aus.
Das ist immer noch recht langsam, würde ich sagen, also wir haben viel overhead. Und jetzt haben Sie sich gedacht, ok, jetzt führen wir dieses mAdvice, das führen wir ja jetzt in einer Schleife aus.
Was ist, wenn wir das jetzt erst am Ende machen, also nach der Schleife und dann machen wir den ganzen Bereich einfach mal, um es auszuprobieren.
Und wenn man das macht, dann hat man immer noch keinen Vorteil, weil Linux in dem Fall, also für Linux implementiert, das Ganze führt für jede einzelne Seite, muss es den Tlb, den Transaction Looker Side Buffer, also diesen expliziten Eintrag invalidieren. Und das muss nicht nur auf den momentanen Prozessor Kern passieren, sondern auf allen CPUs, die im System sind.
Genau, also der Tlb ist ein kleiner Cache für Paging-Datenstrukturen und die müssen wir jetzt natürlich anpassen, weil wir nehmen gerade etwas raus. Das heißt, wenn ein anderer Prozessor diese Seite in seinem Tlb hat, wäre es, wenn wir die nicht rausnehmen, möglich für diesen Prozessor auf diese Seite immer noch zuzugreifen.
Genau, obwohl er an der Stelle eigentlich gar nicht mehr existiert. Das ist eben so ein kleiner Lookup Cache, damit man nicht die Seitenverwaltungsstrukturen immer lesen muss. Und die müssen jetzt natürlich an allen Prozessoren, die im System sind, rausgenommen werden und dafür muss man einen Interprozessor interrupt schicken, also eine Nachricht an die anderen Prozessoren und das hat natürlich auch einen gewaltigen Overhead.
Und wenn ich das natürlich für jede einzelne Seite mache, wie der Linux Kern das macht, dann erzeugt das einen großen Overhead. Dazu ist es möglich, das haben sie ja auch ausprobiert, Linux zu patchen, damit er das eben dann nicht macht, in dem Fall, dass man sowieso einen gesammelten Bereich freigibt, also mit mAdvice.donate.
Und damit haben sie es auch geschafft, dass es dann tatsächlich schneller wird und sich auf das Vierfache, also dann vier Millionen dieser Vorgänge beschleunigt.
Kurz zur Nachtrag, das Ding heißt Prozess mAdvice, dieser Systemruf in der Schleife. Oder nach der Schleife. Okay, aber wir sind immer noch zu langsam, vor allem für mehrere SSDs. Können wir da noch was machen?
Genau, wir sind immer noch zu langsam für mehrere SSDs und das ist jetzt, wo ihre Lösung ins Spiel kommt, nämlich Xmap. Und wie der Name schon nahe liegt, eine exokernelartige Speicherverwaltung für einen speziellen Speicherbereich.
Und exokernelartig heißt dann in dem Fall, dass die Steuerung und die Verwaltung weitgehend im User Space stattfinden oder den User Space überlassen wird und eben keine automatische Behandlung von PageFold stattfindet, also dass da automatisch von irgendwo her Sachen hingelegt werden wie bei MAP.io.
Genau, und im Prinzip die Kontrolle dann an die Anwendung gegeben wird. Genau, auch hier wieder implementiert ist das Ganze jetzt für Linux, also als Kernelmodul. Aber prinzipiell ist das Ganze jetzt portabel.
Also wie ist das aufgebaut? Wir haben einen großen Bereich, also wie wir eben vorher auch schon hatten, einen beliebigen großen Bereich, den nennen sie Surface.
Der heißt ebenso, weil er ist nicht besiedelt mit Seiten, sondern das ist sozusagen nur mein virtueller Bereich, der nicht eins zu eins mit physischen Hauptspeicher gedeckt wird.
Und ich habe einen festen Pool an Seiten, die ich dann auch verwenden kann, um diesen Surface zu bevölkern. Und diese Seiten werden auch nicht vom Betriebssystem für andere Dinge verwendet, also die sind fest meinem Xmap zugeordnet.
Also mein Surface ist im Grunde im virtuellen Adressraum? Genau. Also da habe ich einen sehr großen Bereich an virtuellen Seiten zur Verfügung und meinen festen Pool an Seiten, das sind physische Seiten, die ich dem Betriebssystem schon weggenommen habe.
Ja genau, die sind schon dafür reserviert. Die Verwende des Betriebssystems sind nicht für andere Sachen zugeordnet, damit ich sie da an irgendwelchen Stellen in diesem Bereich mappen kann.
Der Vorteil dafür sind, dass die Seiten schon da sind, also ich umgehe den langsamen Linux-Page-Alloquator. Ich muss diese Seiten auch nicht nullen, normalerweise muss der Linux-Kernel-Seiten immer nullen, weil sie ja zwischen Prozessen hin und her wandern können.
Aber jetzt bleiben sie ja im gleichen Prozess. Genau, und dann wäre es dann besser, würde es die Isolation durchbrechen, wenn die Seiten noch irgendwelche Daten von vorherigen Prozessen haben würden.
Genau, aber jetzt bleiben sie im gleichen Prozess, ich brauche sie nicht nullen. Genau, und ich muss auch kein Bookkeeping über diese Seiten machen, da ich diese Seiten immer nur einmal einlagern kann. Also sowas wie Fork oder so, kann ich mit diesen XMAP nicht machen.
Du kannst zum Beispiel die Seiten dann in der Stelle auch nicht teilen mit anderen Prozessen.
Genau, das kann ich nicht machen, aber dafür entfällt dann auch dieser Bookkeeping-Overhead.
Genau, und dann gibt es noch sogenannte Interfaces, die mappen und anmappen eben die Seiten, also die enthalten Listen mit diesen freien Seiten.
Da kann es mehrere Pro XMAP geben für die Lokalität, also dass ich CPU-lokale Interfaces habe, damit der Zugriff da schneller ist.
Aber auch das bleibt der Anwendung überlassen, wie sie die Interfaces zuordnet.
Genau, die Funktion ist dann so, dass man eine XMAP anlegt und man gibt auch einen Backingspeicher an.
Das ist dann ähnlich wie das traditionelle Memorymap, also nicht wie die Reach-Write, sondern man gibt erst mal explizit den Backingspeicher an, also eine Datei aus dem SSD zum Beispiel.
Und die Applikation sagt dem System dann, also durch entsprechende Reach-System-Cores, welche Teile dieses Backingspeichers gelesen oder dann wieder zurückgeschrieben werden.
Und da kann ich quasi gleich auch mehrere Stellen auf einmal angeben, also da haben sie die Systemcores ein bisschen erweitert oder missbraucht.
Genau, das heißt, ich entscheide, also die Anwendung entscheidet, hier soll jetzt aus der Datei, in der diese App XMAP verbunden ist, soll jetzt dieser Bereich in den Speicher geladen werden.
Dann kann ich darauf arbeiten und danach kann ich wieder sagen, ok, jetzt soll dieser Bereich zurückgeschrieben werden auf die Platte.
Dann wird das gemacht. Und so behalte ich diese explizite Kontrolle und ja, vermeide den ganzen Overhead und habe trotzdem keinen eigenen großen Overhead durch irgendwelche Hashtables oder muss mich irgendwie mit mAdvice rumschlagen, dass ich Sachen anmappen kann.
Genau, und das haben sie jetzt auch gemessen und da kommen sie auf beeindruckende Ergebnisse, nämlich sie schaffen diesen ganzen Prozess, also hinmappen, irgendwas schreiben und dann wieder anmappen.
Den schaffen sie dann in 300 Millionen pro Sekunde, also wenn man dann viele Fels hat, die das machen oder über 300 Millionen pro Sekunde sogar bei 120 Fels.
Genau, und beim Lesen von Seiten, also aus dem Hintergrundspeicher, sind sie dann bis zu 120 Prozent schneller als der vorher beschriebene Ansatz, der ohne XMAP arbeitet.
Aber was sie doch damit machen, ist im Grunde Teile vom Linux Kern zu umgehen, sie haben ja irgendwie das Modul geschrieben.
Genau, das ist die Idee.
Ja, genau, sie haben im Grunde das Modul geschrieben und handeln jetzt irgendwie mit dem Modul aus, was für Seiten gelesen und was für Stellen von Teilen gelesen werden sollen, wohin.
Und da das Modul einen viel kleineren Anwendungsfall hat, sag ich mal, geht dann alles viel schneller.
Genau, und die Sache ist die, also man hat natürlich, also die Idee ist natürlich, dass man für die nach wie vor, für alle anderen Anwendungsfälle normale Wappings hat.
Also mein virtualer Speicher hat dann vielleicht eine XMAP, vielleicht auch mehrere, die eben dann meine Datenbank benutzt für ihren Storage, aber die anderen Sachen, also wie zum Beispiel der Code oder so, der Executable und andere Dinge,
das sind natürlich nach wie vor normale Wappings, die ganz normal traditionell vom Linux Kern verwaltet werden.
Hat natürlich den Vorteil, dass wir kompatibel bleiben mit allen anderen Anwendungen.
Genau.
Also im Vergleich, wenn ich jetzt einfach sagen würde, mein Read-System-Core verhält sich jetzt anders, dann würde das aber mal alles zermrechen.
Dann gibt es halt Anwendungen, die sich halt genau auf die alte Implementation verlassen.
Genau.
Also ich frage mich halt, ob ich das gut finde, weil wenn ich jetzt, jetzt fange ich an dafür ein eigenes Modul zu schreiben, dann fange ich vielleicht an, weiß nicht, für scheduling ein eigenes Modul zu schreiben, weil meine drei Anwendungen, die ich im Hinterkopf habe, ganz bisschen Scheduling brauchen oder?
Ja, also das ist jetzt schwieriger, weil es jetzt nicht nur die eigene Anwendung betrifft, also wahrscheinlich, aber auch sowas ist vorstellbar und ja vielleicht, vielleicht auch schon ein bisschen passiert in Linux, weil da gibt es ja auch real-time priorities für bestimmte Threads und so weiter, also weitgehend real-time, muss man da natürlich mal sagen, aber auch das ist ja vielleicht schon passiert, wie man darauf schaut.
Ja gut, und so gesehen, Scheduling ist natürlich auch relativ einfach, weil das kann ich in Visual Space noch machen. Also Go macht das ja zum Beispiel, die nehmen ja...
Genau, diese Green Threads.
Ja, die können sich entscheiden, ob sie User-Land-Threads mitnehmen oder Kern-Threads.
So zusammenfassend passt der Name schon ganz gut, weil sie bauen so eine EXO-Kernel-artige Speicherverwaltung, aber neben der anderen Speicherverwaltung, also ich kann nach wie vor meine normalen UNIX, bekannten UNIX-Abstrakts nutzen, aber habe zusätzlich für diesen speziellen Anwendungsfall diese, diese, diese X-Map.
Genau, und kann die halt dann immer nutzen, genau, wenn ich sie brauche, weil einfacher zu nutzen ist natürlich Memoral-Map, also das ist klar, also jetzt so aus Entwicklersicht.
Und EXO kehren deshalb, weil das Betriebssystem im Grunde sehr, sehr wenig Funktionalität hat, ne?
Genau, genau, weil es so viel wie möglich die Anwendung überlässt, also das Betriebssystem übernimmt dann nur das Multiplexing der Ressourcen.
Ja, ok. Im Grunde kann ich da ja fast mit einschlagen, die gleiche Kerbe. Bei mir geht es nämlich um Multi-Threading und warum das nicht mehr händisch gemacht werden sollte.
Wir sind ja jetzt in so Systemen, wo das Moore Scaling aufgehört hat, also man hat halt früher gesagt, irgendwie alle so und so viele Monate ist die Hardware doppelt so schnell, das ist halt schon lange nicht mehr so.
Ja, der Bereich wurde auch immer länger.
Ja, der Bereich wurde immer länger.
Also der Zeitraum.
Und vor allem, also wenn wir uns angucken, die Prozessoren in den letzten 20 Jahren, die Tag-Fragments hat sich im Grunde nicht mehr erhöht. Was dazu gekommen ist, sind mehrere Kerne, ein bisschen anderes Pipelining, vielleicht ein bisschen Caching und da sind eine ganze Menge Performance dazu gekommen.
Man muss jetzt wirklich anstrengen, wenn ich mir einen neuen Prozessor kaufe, dass meine Anwendung da besser drauf läuft. Das heißt, meine Anwendung muss Multi-Threaded sein.
Ja.
Ok, dann gibt es irgendwie beliebige Patterns, man kann so einen Fork-Join-Pattern benutzen, also ich forke meine Anwendung weg und irgendwann warte ich dann, dass die fertig werden.
Man könnte so eine Single-Threaded Event-basierte Anwendung machen, so die JavaScript zum Beispiel.
Ja, das klassische Beispiel.
Genau, JavaScript, das klassische Beispiel, da gibt es nur einen Thread, der führt JavaScript-Code aus und irgendwann sagt JavaScript-Code, ok, ich bin fertig oder warte mal irgendwie auf diesen Formel, oder await dieses asynchrone Objekt, was auch immer.
Und dann kann der Interpreter anfangen, im Hintergrund diesen Teil zu laden. Und dann gibt es Thread Pools, die das halt irgendwie auflösen.
Aber selbst in diesem Modell ist es sehr schwierig, die Performance zu stärken. Also glaube ich, das Ding auslagere und dann asynchroner aufwarte, ok, dann kann ich damit meine Performance stärken, aber jetzt selbst ist es ja nach wie vor Single-Threaded.
Genau, gerade wenn ich 128 Threads habe, sage ich mal, werde ich halt sehr schnell an die Grenze kommen, weil ich nur einen Thread habe, der wirklich neue Requests liefern kann. Und wenn wir uns jetzt so eine Multi-Threaded, mehr-Thread-Anwendung quasi, eine mehrfertige Anwendung quasi anschauen, dann ist das irgendwie immer so ein Producer-Consumer-Problem.
Es gibt irgendwie ein paar Threads, die produzieren Daten, packen die in eine Queue rein und es gibt ein paar Threads, die konsumieren die Daten und machen damit irgendwas, berechnen damit irgendwas.
Ok, das ist doch nicht schlimm, das ist halt so.
Ja genau, also genug Abstraktion sieht so eine Multi-Thread-Anwendung häufig wie ein Producer-Consumer-Problem aus. Jetzt habe ich natürlich gesagt, ich habe eine Queue in der Mitte. Werfe ich jetzt also irgendwie mehr und mehr Threads auf diese Queue drauf, dann kann das passieren, dass meine Systeme, also die Performance meines Systems sich verringert. Weil jetzt entsteht Contention oder wie haben wir das genannt, Strittigkeit oder sowas.
Ja genau, also es entsteht ja auch, es muss ja nicht eine Queue sein oder? Also ich kann ja auch mit geteiltem Speicher arbeiten.
Genau, ja, wie gesagt, hohe Abstraktionen ist in Zweifel keine Queue, es könnte geteilter Speicher sein, es könnte Mutate sein irgendwo.
Aber Contention passiert ja auch.
Genau, und jetzt natürlich die Frage, also wenn wir so einen Threadpool uns überlegen, also kurz überlegen, was war ein Threadpool, wir erstellen uns eine Menge von Threads und die warten dann also, und wenn es Aufgaben gibt, arbeiten die dann ab. Dann kann ich sagen, oh dieser Thread führt jetzt bitte diese Aufgabe oder Task, wie man sagt, aus.
Genau, also der klassische Ansatz eigentlich, also dass ich da irgendwie sage, ok, ich will irgendwie Dinge parallel machen, mache ich einen Threadpool, damit ich nicht auf einmal 1000 Threads habe, die da erstellt werden, weil das wäre auch nachterhaft. Und der Threadpool hat für sich oftmals die Anzahl der CPU an Threads, wenn man denkt, ok, die führen sich halt, also ich habe irgendwie vier CPUs, dann sollte ich auch vier Threads haben, weil dann ist mein Rechensystem optimal ausgelastet.
Genau, der hat jede CPU was zu tun.
So kenne ich das.
Genau, die Frage ist halt, wie findet man eigentlich die richtige Anzahl von Threads in so einem Threadpool?
Ja, warum ist die nicht die Anzahl der CPUs? Das ist doch erstmal eine logische Annahme oder eine eindeutige Annahme.
Eine logische Annahme, wenn ich einen Benchmark habe, der CPU bound ist, dann ist das wahrscheinlich sogar so. Also wenn ich, wenn ich heißt, ich habe, was will ich sagen, wenn x264 oder so, also irgendein Videoinkode, der sehr sehr viel rechnen muss und gar nicht so viel auf Festplatte zugreifen muss, dann ist das wahrscheinlich so, der wartet relativ selten auf Festplatte, dafür wartet er relativ häufig, wartet in Anstrittlichen relativ häufig auf CPU-Ergebnisse, sag ich mal.
Wenn ich jetzt natürlich eine Anwendung habe, die sehr sehr viel auf Festplatte zugreift, die die ganze Zeit irgendwas lädt, keine Ahnung, sagen wir G-SIP oder so, oder ein Exzet, dann lädt der ganz viele Daten, macht damit eine relativ kleine Berechnung und schreibt die Daten irgendwann wieder raus. Und da kommt es halt dann drauf an, also irgendwann so eine Festplatte lädt halt bestimmt durchsatzdaten und mehr geht halt nicht.
Und dann warten wir eine Festplatte und dann könnte ich eigentlich mehr haben.
Ja und dann warten wir eine Festplatte. Wenn ich mehr dazu packe, dann wird die Contention auf die Festplatte, also gerade wenn wir jetzt eine richtig mechanische Festplatte haben, auch noch größer. Weil die muss ja den Kopf bewegen und wenn du noch mehr Requestchen schickst, dann kommt die ganze durcheinander und versucht irgendwie mehrere Dateien gleichzeitig zu lesen und das führt alles ganz großes Chaos.
Aber gut, auch bei einer SSC haben wir den Effekt, weil natürlich irgendwann die Geschwindigkeit ist ja auch begrenzt, auch wenn der Random Zugriff besser ist.
Ja, es ist kein mechanischer Kopf mehr, der sich bewegen muss. Okay, also man könnte jetzt hingehen und sagen, wir machen so ein kleines wasserstandsbasiertes System. Also es gibt irgendwie Minimalwert an Fests, die wir haben, oder die aktiv sein können und Maximalwert an Fests und das kann so ein bisschen hin und her schwanken. Wenn es mehr Arbeit gibt, dann gibt es mehr Fests und wenn es nicht so viel Arbeit gibt, dann gibt es weniger Fests.
Okay und auch wenn ich sehe, wenn die Fests zum Beispiel alle warten, dann kann ich irgendwie die Anzahl höher machen, weil ich dabei nicht mehr CPU frei ist, um was zu berechnen oder so. Muss ich natürlich auch meine Aula-Auslastung auch irgendwie mit einbeziehen.
Na, so einfach ist es nicht. Also klar, die wasserstandsbasierten Systeme, die machen nur nach Anzahl oder Länge der Arbeit, nach Anzahl der Tasks. So gehen die erstmal vor.
Aber wie würde wasserstand, also was ist der Wasserstand an der Stelle?
Die Anzahl der Arbeit, die Anzahl der Tasks.
Achso, also wenn ich viel Arbeit habe, dann wird die Anzahl an Fests höher?
Genau.
Okay.
Also, ja gut, was man natürlich noch machen kann, man kann natürlich die Konfiguration auf den Nutzen überlassen, dann ist man komplett verloren.
Ja, dann macht der irgendwas, so wie ich einfach Anzahl der CPUs und von meinem Rechner und dann läuft der auch mit 16 CPUs oder mit 48, mit 4 Fests.
Genau, also entweder er nimmt irgendwo ein Default, das irgendwo in einem Wiki steht, was vor zehn Jahren mal ausgefunden wurde, oder ja, genau so was, also mit Anzahl der CPUs, also irgendwie mehr oder weniger etikettet guessing. Also und natürlich die meisten Programme haben auch verschiedene Phasen.
Also was ich gerade bei dem Gehz zum Beispiel gesagt habe, liest erst ein, berechnet dann eine Weile, schreibt dann raus. Das sind drei Phasen, die eventuell sogar verschiedene Anzahl von Fests wollen.
Und das natürlich, wenn ich anfange das konfigurierbar zu machen, dann steigt mir selbst der Nutzer aus, der der größte Experte ist.
So, was ich noch festgestellt habe, es gibt da relativ wenige Libraries oder Bausteine für Fettpools. Also insbesondere, es gibt keine Erweiterung von POSIX dafür, also für den P-Fests.
Deshalb implementieren viele Anwendungen ihre eigenen Systeme. Macht natürlich nicht einfacher, das zu konfigurieren.
Gut, dann hat Apple mit dem Guant Central Dispatch auch schon so eine Teillösung dafür gebaut.
Also die haben so ein, also das ist ein Fettpool, oder?
Genau, das ist ein Fettpool, da kann man ein paar Systeme weit, Tasks hinschicken.
Aber das wohl oftmals beschränkt, ich habe kein Apple, das habe ich nicht ausprobiert, aber das ist oftmals beschränkt auf so ein paar UI-Aktionen.
Also das ist wohl nicht so generisch, wie man das gerne hätte.
Okay, aber die Idee ist ja schon mal gut, dass ich ja nicht nur, weil wenn jede Anwendung das irgendwie für sich selber macht, dann hat die Anwendung ja auch nur sich im Blick und gar nicht das ganze System.
Also wenn ich laute Anwendungen habe, die alle nur auf sich schauen und dann ihre von mir aus, wenn sie alleine laufen würden, optimalen Größen des Fettpools haben würden, naja, dann geht das schief.
Weil wenn die gleichzeitig laufen, dann laufen vielleicht viel zu viele Fetts.
Genau. Und was die Forscher jetzt hier aus, also von der TU Delft, bzw. jetzt Uni Hannover, hier vorschlagen, ist, man sollte den Fettpool eben als zentrale Aufgabe des OS sehen, also das System sehen.
Und sie haben das jetzt mal implementiert, ich gehe davon aus, sie haben das im User Space implementiert, und so haben sie einen Controller gebaut.
Der prüft regelmäßig bestimmte Metriken und passt dann die Anzahl der Fetts an.
Als Beispiel haben sie dann die Anzahl der gelesenen und geschriebenen Bytes, also quasi einen gewissen IO-Durchsatz genommen.
Ziel soll natürlich sein, wir wollen den Durchsatz maximieren und die Zwergzahl minimieren.
Also was machen wir jetzt? Es gibt drei Phasen von diesem Controller.
Erst eine quasi Start-up-Phase, solange es quasi noch signifikante Änderungen gibt, passen wir die Anzahl der Fetts so ein bisschen an.
Also so eine Art Bergsteigerverfahren. Sagt ihr das was, Flo?
Nein, erklär mal.
Im Grunde des Bergsteigerverfahrens, wir laufen, also wir stehen irgendwo an dem Berg quasi, jetzt wollen wir irgendwie hoch, wir können aber nicht sehen, welche Richtung es ist.
Und wir stehen irgendwo, im Startpunkt, wir laufen jetzt erstmal in irgendeine Richtung.
Sagen wir, wir erhöhen die Anzahl der Fetts. Und dann gucken wir nochmal, hat sich die Situation verbessert, stehen wir jetzt höher oder niedriger als vorher? Und dann passen wir quasi die Richtung an.
Okay, und so kommen wir irgendwann auf den Berg? So kommen wir irgendwann quasi auf ein halbwegs nahes Maximum ran.
Genau, und was passiert, wenn wir auf dem Gipfel sind? Genau, dann sind wir erstmal stabil. Und im Grunde haben wir jetzt ja die ideale Anzahl an Fetts gefunden.
Aber der Optimalkont könnt ihr jetzt natürlich verschieben. Sagen wir, eine neue Phase von unserer Anwendung hat stattgefunden, wir machen jetzt große Berechnungen auf unsere CPU, nicht mehr so viele IO.
Also werden wir zyklisch andere Fettpool-Größen ausprobieren. Also wenn sich die Zielmetriken maßgeblich verändert oder halt nach Timeout, also wenn sie zyklisch mal machen.
Oder wenn sich die Zielmetriken wieder verändert haben, dann finden wir das durch eine Timeout raus. Oder dann finden wir es auch raus, dass wir bei einem Timeout wieder diesen Test machen.
Genau, und dadurch, wenn wir dann andere Poolgrößen mal ausprobieren und quasi wieder in die erste Phase des Bergsteigerfahren einsteigen, können wir halt auf feststellen, okay, sind wir noch am Maximum Gipfel, sind wir an das Maximum an Durchsatz und minimalen Wertzahl oder sind wir doch ganz gut gewesen.
Dadurch, dass wir das nicht ständig machen, verlieren wir dadurch auch gar nicht so viel Zeit.
Ja, das klingt doch ganz gut, aber bringt das jetzt tatsächlich auch Vorteile, also gegenüber irgendwelchen statischen Fettpool-Größen?
Genau, eine Sache muss man noch erwähnen, es gibt noch zwei Parameter, die man einstellen kann. Einerseits ist das Intervall, also diese Timeout, und ein gewisser Stabilitätsfaktor, also wie empfindlich sollen wir eigentlich auf diese Veränderung der Metrik, dieser IO-Durchsatz reagieren.
So, übrigens Ergebnisse. Was sie gemacht haben, ist auf einer Vierkern-CPU Dateien lesen und schreiben. Alle Tasken kommen auf einmal an und alle Tasken identisch. Das heißt, sie haben wirklich kleine Aufgaben genommen, irgendwie Open, Read, Write, Close.
Da haben sie drei Varianten davon gebaut, einmal eine Variante ohne F-Sync, einmal eine Variante mit F-Sync und einmal eine Variante, wo erst ein paar Tasken kommen ohne F-Sync und dann ein paar Tasken kommen mit F-Sync.
F-Sync nur, weil es heißt, dass die geschriebenen Änderungen dann tatsächlich auch auf dem Hintergrundspeicher ankommen, also dann tatsächlich geschrieben werden.
Das heißt, wenn ich das F-Sync quasi weglasse, dann...
Dann könnt ihr noch gepuffert sein im Arbeitsschritt.
Genau, ich schreibe im Grunde gegen den PageCache, weil die Dateien werden in Linux im Hauptspeicher, nicht in den Hauptspeicherseiten, und wenn ich quasi keinen Sync mache, dann schreibe ich da dagegen.
Genau, klar, irgendwann passiert das dann aber natürlich noch nicht gleich.
Genau, und sie haben das getestet gegen ein festes Optimum, also das haben sie quasi händisch mal rausgefunden, oder rausgemessen auf dieser Maschine, wie viele Fets sind eigentlich so das Optimum. Sie haben gegen den Wasserstandansatz getestet, der war aber fest auf 64 Fets. Ich glaube, der Watermark-Ansatz, der war da nicht gut eingestellt, glaube ich.
Okay, also das ist ein kleiner Kritikpunkt.
Quasi ein kleiner Kritikpunkt. Es ist natürlich ein bisschen komisch jetzt aus unserer Perspektive diesen... zu kritisieren.
Aber du denkst, das waren zu viele Fets?
Ja, also es ist halt bei der Ergebnisdarstellung immer auf 64 Fets, und ich glaube, dann brauche ich keinen Wasserstandansatz, dann kann ich auch fest einfach auf 64 setzen, aber vielleicht habe ich das so falsch verstanden, das kann sein.
Was es jetzt zeigt, ist, die Variante ohne irgendwelche Synchs, da macht es überhaupt keinen Unterschied, wie viele Fets wir haben. Also wir schreiben im Grunde gegen den Page-Graph, und das ist vollkommen egal, wie viele Fets wir haben, das zeigt eigentlich keinen Unterschied mehr.
Und wenn wir jetzt Synchten und die Veränderung tatsächlich zurückschreiben auf die Festplatte, dann kommen wir mit dem vorgestellten Ansatz im Grunde auf eine identische Performance zum Optimum, aber mit weniger Fets.
Sie haben das dann auch getestet gegen RocksDB, da haben sie dann gezeigt, dass quasi verschiedene Intervalle, wie häufig man quasi neue Poolgrößen ausprobiert, auch einen ziemlich großen Performance-Unterschied machen können.
Was sie dann sagen, oder was ich rausstelle, ist im Grunde, die Metrik ist kritisch. Was für Fluktuation, auf welche Fluktuation wollen wir reagieren? Und wenn wir zu sensibel reagieren, dann führt das zum instabilen System, weil wir die ganze Zeit hinterher rennen zwischen, oh Gott, jetzt wird total wenig geschrieben, jetzt brauche ich mehr Fets, oder es wird total viel geschrieben, dann brauche ich weniger Fets, oder andersrum.
Und dann ist auch der Overhead ja sehr groß, wenn ich das mache, nehme ich mal.
Genau, dann haben wir auch noch diesen Overhead. Also das heißt, die Wahl der Metrik ist sehr kritisch, und im Zweifel auch für meine Anwendung, und wie viel Fluktuation quasi wir erlauben.
Aber wenn ich das jetzt sozusagen mal ein bisschen kritisch betrachte, dann kann ich sagen, gut, ich habe jetzt halt die eine Stellschraube durch eine andere gesetzt. Also vorher konnte ich einstellen, wie viel Fetz ich haben, jetzt muss ich die Metrik einstellen, und irgendwie diese Parameter, wie oft ich irgendwas, also diese Phasen dadurch laufe.
Ja, im Grunde schon. Also dieses System ist schon für IO-intensive Programme gedacht. Also wenn du eine Datenbank hast, die sehr sehr viel schreiben muss, wenn du eine Gzip oder sowas hast, wo du sehr sehr viel Daten einliest, wenn du wirklich einen IO-intensiven Ansatz hast, ein IO-intensives Programm hast, dann kann dir das wirklich viel bringen. Wenn du jetzt natürlich ein Programm hast, das sehr wenig IO macht, und auf Fassen hat, wo es da keiner IO gibt, dann verfehlt das ein bisschen sein Ziel, das da ist natürlich rechter.
Könnte zumindest sein, also so war es nur daher gesagt, ich habe jetzt nichts gemessen.
Ja, du könntest natürlich recht haben, also wenn du jetzt wirklich eine rechenzeitintensive Anwendung hast, dann muss man wahrscheinlich die CDU-Lasse nehmen oder vielleicht eine Mischung aus beiden auch. Ja, dann muss man auch ein bisschen rumprobieren, bis man da das Richtige gefunden hat, was einen Großteil der Anwendung, sage ich mal, abdeckt. Und sie schlagen dann auch vor, das Ganze in den Körner zu integrieren, um mehr Zugriff auf Metriken zu haben, um die Bookkeeping genauer machen zu können. Und sie sagen auch durch IO-Uing, dass ja dieser Systemruf, wo man im Grunde mehrere Systemrufe asynchron an den Kern geben kann.
Genau, ohne dass ich tatsächlich System-Calls machen muss über so geteilten Speicher, geht das um den System-Col-Overhead zu verhindern.
Genau, im Grunde schreibe ich ja in so einen Ringpuffer rein, der Kern liest diesen gleichen Ringpuffer und arbeitet dann meine Ergebnisse auch wieder zurück durch so Mechanismus.
Genau, und dadurch, dass das gibt, dieses IO-Uing ist im Grunde fast das System schon soweit, dass wir so ein Threadpooling betreiben. Der Kern arbeitet ja schon System-Calls ab und wenn wir jetzt anfangen quasi intelligenten Threadpool da draus zu machen, dann wären wir ja schon da, dann ist ja schon die Körnerzeitimplementierung davon. Mehr oder weniger.
Also die Idee ist jetzt tatsächlich, das mit IO-Uing zu kombinieren, dass das dann auch so abgearbeitet wird über diesen Controller.
Ja, das wird auf jeden Fall in dem Paper als zukünftige Arbeit aufgeführt.
Ja, auf jeden Fall eine interessante Sache.
Definitiv, ja.
Okay, und damit wären wir am Ende der Sendung und wie immer am Ende der Sendung kommt die Frage "Flo, was haben wir eigentlich gelernt?"
Ja, ich habe gelernt, dass es nicht unbedingt das Optimum ist, einfach vier Threads zu erstellen und dann bin ich fertig, sondern dass wir vielleicht uns doch überlegen sollten, ob wir bessere Methoden für Threadpools einführen.
Ja, und ich habe gelernt, wenn man so Excel-Körnernartig am Linux-Kern vorbei arbeitet, dass man dann bei wenigstens Dateioperationen zu mehr Performance kommen kann.
Ja, und wie immer findet ihr die Links zu den Papern und Vorträgen in den Show-Nodes und dann bleibt mir nur noch zu sagen Florian Rommel, vielen Dank.
Vielen Dank Stefan.
Und euch vielen Dank für die Aufmerksamkeit.
Vielen Dank für die Aufmerksamkeit.
[Musik]
SWR 2020