 Ja willkommen bei dem Vortrag über den Linux Kernel Scheduler. Mein Name ist Markus oder Markus Parkmann auch genannt Skosu Online. Schön, dass ihr alle gekommen seid und ich hoffe euch heute ein bisschen einen Einblick zu geben, wie der Kernel Scheduler so ein bisschen läuft. Das wird nicht unbedingt bis ins hundertprozentige Detail gehen, weil das ist einfach viel zu viel Code, den man da erklären müsste, aber so im Groben, dass ihr dann Eindruck bekommt. Zuerst einmal zur Motivation, warum will man sich überhaupt über den CPU Scheduler Gedanken machen? Eigentlich funktioniert er ja schon und ist überall am Laufen in allen Rechnern auf der Welt, sowohl Windows, Linux, FreeBSD, OpenBSD, was es auch alles gibt. Überall läuft ein Scheduler und es funktioniert ganz gut, so denkt man. Allerdings haben Scheduler die Eigenschaft mehrere Male pro Sekunde, also mehrere hundert bis tausend Male pro Sekunde aufgerufen zu werden und entsprechend kann man an der Stelle sehr viel einsparen oder in die Richtung optimieren, in die man gerne möchte. Allein für Linux hat Google zum Beispiel, Google Benustial, ganz stark Linux in ihren Servern. Es gibt eine Schätzung aus dem Jahr 2011, dass Google alleine ungefähr 900.000 Server Betrieben hat im Jahr 2011. Das wird jetzt garantiert nicht weniger sein und es gibt auch sehr viele andere große Betreiber, sowas wie Facebook, Amazon etc., die alle auf Linux bauen und die entsprechend riesige Masken an Servern über der Welt verteilt haben. Außerdem, was nicht zu vernachlässigen ist, es gibt 1,6 Milliarden Android-Geräte auf der Welt und die laufen alle mit diesem Scheduler, der mindestens 100 bis 1.000 Male pro Sekunde aufgerufen wird. Entsprechend gibt es da sehr viel Potenzial, das Ganze zu verbessern. Was für Herausforderungen hat man bei einem CPU-Scheduler überhaupt? Das Problem ist bei einem CPU-Scheduler, wir haben ein Echtzeitproblem. Wir müssen in Echtzeit auswählen, welcher Task als nächstes auf die CPU darf. Das ist recht problematisch. Wenn man das Ganze offline berechnet, ist das Problem super gelöst, aber wir haben halt nicht die Möglichkeit das offline zu machen, sondern wir müssen es online machen. Wir müssen so wenig wie möglich Zeit verwenden, um das Ganze zu berechnen, welcher Task als nächstes kommt. Außerdem müssen wir schnell auf Interaktion reagieren, sowohl vom User als auch, wenn wir zum Beispiel disk.io haben, dann müssen wir schnell darauf reagieren, dass wir jetzt zum Beispiel Daten bekommen haben, damit wir manche Prozesse einfach nicht ausbremsen. Soweit zur Motivation. Mein Talk wird so ein bisschen die Grundlagen erst mal beschreiben, wie das Ganze in Linux überhaupt aussieht mit dem Scheduler, dann die aktuelle Implementierung, der komplett fair Scheduler ein bisschen näher beschreiben, wie der funktioniert und am Ende noch eine Sicht auf die Realität bieten, inwieweit Änderungen am Scheduler tatsächlich sichtbar sind auch in Messergebnissen und in Benchmarks. Fangen wir mit den Grundlagen an. Erstmal ein bisschen zur Historie. Es gab mittlerweile drei Scheduler, soweit ich weiß, im Linux Kernel. Der erste war ein O von N Scheduler, ist jeder mit der O Notation vertraut. Im Sinne bedeutet es einfach nur, dass die Dauer, wie lange der Scheduler braucht, um einen Task auszuwählen, abhängig davon ist, wie viele Tasken es gibt. Das ist dieses N hier in der Notation. Und wenn wir einen O von N Scheduler haben, ist das halt direkt abhängig davon, wie viele Tasken wir am Laufen haben. Entsprechend war der Scheduler nicht sehr groß und nicht gut geeignet für Massen an Tasks. Dementsprechend gab es dann kurze Zeit später, also 26 bis 2623, den O von N Scheduler. Der hat ein System von mehreren Run Cues in Anführungsstrichen benutzt, um die Tasks zu managen. Hat dann immer die Run Cues ausgetauscht, etc. War ganz ekliges Design irgendwie. Auf jeden Fall hatte der eine konstante Dauer, um einen Task auszuwählen. Das war schon ziemlich schnell. Und jetzt gibt es halt seit 2623 ein komplett liefer Scheduler und der ist, wie der Name schon sagt, komplett fair oder versucht bis zumindest zu sein und hat trotzdem nur eine Komplexität von Logarithmus von N, also Logarithmischer Aufwand. Parallel dazu gibt es einen Brainfuck Scheduler, der ist entwickelt worden von einem Colin Collievers. Der war damals an der Schedulerentwicklung maßgeblich beteiligt, gab wohl aber ein bisschen Streit und ist jetzt aus dem Mainline Kernel komplett raus. Hat aber trotzdem einen Brainfuck Scheduler, der gemaintaint wird, parallel zum Linux Kernel Scheduler. Der ist aber hauptsächlich dafür gedacht, auf kleinen Systemen mit wenigen Tasken zu laufen, also so was wie Handys oder so was. Jetzt erstmal ein bisschen zu den Grundlagen. Linux oder der Linux Scheduler an sich unterscheidet nicht zwischen Prozessen oder Threads. Diese Unterscheidung ist quasi nur in Bezug auf welchen Memory ein Prozess oder ein Task hat, gibt es da diese Unterscheidung zwischen Prozessen und Threads. Für den Linux Kernel macht das keinen großen Unterschied. Entsprechend ist der Scheduler auch auf sogenannten Tasks, die er auf den arbeitet und nicht auf Prozessen oder Threads. Jeder Task ist entweder lauffähig, also Running oder nicht lauffähig, also blocked oder wartet auf irgendwas, zum Beispiel ein Timer Interrupt oder etc. Das sind so die zwei Kategorien, die es gibt. Wenn er blockt oder auf irgendwas wartet, sind wir überhaupt nicht interessiert an diesem Task. Der Scheduler ist überhaupt nicht interessiert und er fällt auch nicht in diese Komplexitätsklasse, die es da gibt. Nur diese Running Tasks sind halt von dem CFS oder also dem komplett fair Scheduler oder allgemein dem Linux Scheduler beachtet und die anderen nicht. Wie vielleicht die meisten von euch wissen, gibt es den Linux, die sogenannten Nice Level. Davon gibt es 40 Stück. Die sind im User Space von minus 20 bis 19. Minus 20 ist die höchste Priorität, die es gibt. Rund 19 ist die geringste Priorität. Damit werden oftmals Kernelprozesse etc. richtig eingestuft. Intern im Linux Kernel wird das Ganze jedoch ein bisschen anders abgebildet. Da sind es die Prioritäten 100 bis 140. Und davon, also die unteren 0 bis 100 Prioritäten sind für den Real-Time-Scheduler. Darauf werde ich aber auch nicht in diesem Vortrag eingehen. Und jetzt stelle ich natürlich die Frage, auf welchen Architekturen soll denn überhaupt zu was laufen und auf welcher Hardware? Da habe ich einen kleinen XKCD gefunden, der ist schon ein bisschen älter. Aber da wir dieses Flash-Problem mittlerweile gelöst haben, können wir eigentlich mit den Prozessoren weitermachen, die wir in dem Linux Kernel unterstützen. Wir haben ja mittlerweile HTML-Video etc. Entsprechend kann der Linux Kernel mittlerweile 8192 CPUs unterstützen. Nur auf manchen Systemen natürlich, 64 Bit wahrscheinlich vorausgesetzt, wie auch immer. Ich kenne keine einzige Maschine, die überhaupt mehr als 1000 CPUs hat, aber es gibt solche wahrscheinlich. Wie auch immer, auf dieser Hardware müssen alle Scheduler gut performen und laufen. Das ist eine riesige Herausforderung, genauso wie, dass wir nicht einheitliche CPUs haben. Es gibt zum Beispiel sowas wie Hyperthreading von Intel, wo wir einfach mehrere Threads in einer richtigen CPU haben. Und diese Threads teilen sich die Hardware unter sich auf. Und das ist ein riesen Problem für den Scheduler, weil er das beachten muss. Zum Beispiel, wenn wir zwei Threads oder zwei Tasks auf einem Core benutzen, der Hyperthreading macht, ist das theoretisch, also für den Linux Kernel sind das zwei CPUs. Aber wenn wir noch einen zweiten Core frei haben, auf dem nichts läuft, ist das nicht optimal. Außerdem gibt es zum Beispiel von ARM, jetzt sowas wie die Big Little Architektur, wo wir einfach zwei CPU-Cores oder zwei Multi-Prozessor-Cores vereinen. Der eine ist für Low Power und der andere ist für High Power und Durchsatzgedacht. Und da müssen wir natürlich auch, also das sind zwei Cores, die sind total unterschiedlich. Entsprechend ist es sehr schwierig abzuschätzen, wo wir denn jetzt hinschedulen wollen. Wollen wir lieber Energie sparen oder wollen wir lieber hohen Durchsatz machen, das sind halt alles offene Fragen, die der Scheduler irgendwie beantworten muss. Außerdem gibt es verschiedene Speicher-Echthekturen, sowas wie Non-Uniform Memory Access. Dabei ist das Problem, dass an verschiedenen CPUs, die eingesetzt werden, verschiedene Rams quasi dran liegen. Entsprechend können wir nicht aus allen CPUs auf den gleichen Speicher zugreifen. Das ist relativ problematisch, weil dann die Migrationskosten für einen Task, wenn wir den von einer CPU auf die andere verschieben, die steigen natürlich immens an, weil wir dann auch den ganzen Speicher-Kontext kopieren müssten. Außerdem gibt es noch verschiedene Cache-Hirachien. Wenn wir zum Beispiel nicht auf die Caches achten, dann haben wir Probleme, dass die Caches nicht mehr so genannt hot sind. Und entsprechend können die Prozesse da zum Teil nicht den Cache richtig ausnutzen, weil er einfach, vor allem wenn der Task migriert wird, dann ist der Cache auf der neuen CPU einfach kalt und das ist ein Problem. Weil wir haben den vorher auf der CPU gelaufen ist, haben wir ihn schon angewärmt, entsprechend ist der Task dort schneller. Ich habe es schon ein bisschen angesprochen, was für Verhalten erwünschen wir uns von dem CPU Scheduler überhaupt. Es gibt da verschiedene Ziele, die wir erreichen wollen. Zum einen natürlich Energiesparen auf irgendwelchen mobilen Geräten, Durchsatz in irgendwelchen Server-Farmen und Interaktivität bei irgendwelchen Desktop-PCs vielleicht und auch mobil. Diese drei wichtigen Verhalten, die wir wollen, sind sehr nah miteinander verbunden. Zum Beispiel wenn wir den Durchsatz erhöhen, kann die mobile CPU in unserem Handy sofort wieder in den Schlafmodus gehen, wenn er fertig ist und das spart eventuell mehr Energie, als wenn wir mit einer geringen Frequenz da den Task bearbeiten. Interaktivität ist zum einen natürlich Benutzereingaben, aber zum anderen auch sowas wie Disk.io oder Netzwerk, wo wir einfach auf den Task oder wo der Task darauf wartet, dass etwas fertig ist und entsprechend können wir den Durchsatz für diesen Task erhöhen, indem wir eine hohe Interaktivität haben und der Task schnell auf diese neue ankommenden Pakete reagieren kann. Was haben wir denn für Möglichkeiten das überhaupt zu optimieren, das Verhalten? Wir können natürlich Task länger auf der CPU lassen, damit gibt es weniger Context-Switches, also die Task wechseln sich weniger ab, dadurch sparen wir ein bisschen an Zeit und wir sparen oder wir halten die Caches auch länger warm quasi für den Task. Es ist jetzt auch als zweiter Punkt angemerkt, wenn ja Context-Switches kann man aber auch anders erreichen, indem man zum Beispiel die Interaktivität runter setzt. Außerdem ist es sehr wichtig die Tasks Intelligenz zu verteilen. Wenn zum Beispiel mehrere Tasks den gleichen Speicherbereich nutzen, ist es sehr sinnvoll, die zusammen auf eine CPU zu bringen, sodass die Caches einfach den gleichen Inhalt caching können. Dann spart man sich da ein bisschen mehr Cachings Aufwand und auch die Synchronisation der Caches von mehreren CPUs, das ist damit nicht so problematisch. Ja, kommen wir zum Completely Fair Scheduler. Der Completely Fair Scheduler ist der moment eingesetzte Scheduler in Linux-Görne und dieser Scheduler ist so wie jeder angewiesen so eine Art Run Queue zu haben. Diese Run Queue nennt sich Run Queue, weil es alle Tasks enthält, die gerade am Laufen sind. Entsprechend sind diese ganzen Blog von denen ich geredet habe, sind da nicht enthalten und tauchen da auch nicht auf. Diese Run Queue ist beim Completely Fair Scheduler als Red Black Tree implementiert. Ein Red Black Tree ist quasi ein binärer Suchbaum, der sortiert ist und das Red Black dazu ist, um den möglichst balanciert zu halten, damit man da nicht die Grenzen springt. Der Completely Fair Scheduler benutzt so was wie eine virtuelle Laufzeit, um zu beschreiben, wie gut oder wie viel ein Task gelaufen ist. Diese virtuelle Runtime oder virtuelle Laufzeit ist prioritätsabhängig und abhängig davon, wie lange er schon gelaufen ist tatsächlich auf der CPU. Dazu gibt es diese Formel quasi. Die virtuelle Runtime ist halt genau die Runtime in Nanosekunden im Bezug auf das Gewicht des Tasks und das Gewicht des Tasks ist abhängig von der Priorität, die wir angegeben haben. Wir haben ja diese 40 Prioritäten und im Kernel gibt es so eine Tabelle, die nennt sich Priority to Weight, die übersetzt, setzt halt einfach die Prioritäten direkt auf die Gewichte. Und wie man hier sieht, minus 20 hat das höchste Gewicht, das ist unsere höchste Priorität mit 88.761 und die geringste Priorität, Priorität 19 hat halt das geringste Gewicht. Und wenn man sich jetzt die Formel ein bisschen näher anguckt, dann sieht man sofort je höher das Gewicht, desto weniger zählt die tatsächliche Laufzeit, die der Task auf der CPU hatte. Entsprechend ist die virtuelle Runtime langsamer am Steigen für hochpriori tasks als für niedere prior tasks. Der große Vorteil von dieser virtuellen Laufzeit, also V-Runtime, ist, dass man sie direkt vergleichen kann. Das heißt, man hat zwei Tasks und beide haben irgendeine V-Runtime und man kann sie direkt vergleichen, wie fair sie zueinander stehen. Wenn die V-Runtime sehr stark, sehr unterschiedlich ist, dann will man möglichst den Task mit der geringeren V-Runtime benutzen und auf die CPU schieben, damit dieser Fairness-Unterschied möglichst angeglichen wird. Entsprechend ist auch der Red Black Tree, den der completely fair Scheduler benutzt, sortiert nach diesen virtuellen Laufzeiten und entsprechend wird sofort, wenn der, also wird immer der, mit der kleinsten virtuellen Laufzeit der Task ausgewählt und auf die CPU getan. So wird dann halt auch automatisch angepasst, wenn irgendwelche Unfairheiten vorherrschen, dass die Tasks dann sich anpassen können. Dadurch, dass wir immer halt diesen geringsten Tasks nehmen mit der geringsten Laufzeit oder virtuellen Laufzeit, haben wir auch bei CWS eine Fairness von O von N. Das ist recht gut. Diese virtuelle Laufzeit ist im Kernelcode aber auch ein bisschen problematisch. Man muss darauf achten, wie man sie benutzt. Wie ich gesagt habe, ist die Runtime in Nanosekunden, entsprechend ist auch die virtuelle Runtime in Nanosekunden angegeben und jeder Integer oder jede Zahl tendiert dazu, irgendwann überzulaufen. Und da wir hier nur 64-Bit Anzeint-Integer haben, wird das irgendwann passieren. Bei einem normalen System kann das ein Jahr dauern. Es kann aber auch bei geringen Prioritäten entsprechend viel schneller gehen. Dementsprechend müssen auch alle Vergleiche etc. so aufgebaut sein, dass wir nicht riskieren, dass wir durch die Mathematik-Leve benutzen, irgendwelche Probleme mit der mit überläufen haben. Als Beispiel habe ich hier angegeben, wenn wir Runtime 1 mit einer Runtime 2 vergleichen, das funktioniert so einfach nicht. Wenn wir uns überlegen, dass Runtime 2 gerade übergelaufen ist und Runtime 1 am oberen Ende der Anzeint 64-Bit Integers liegt, dann müsste dieser Vergleich eigentlich wahr sein, ist es aber nicht in der Realität. Entsprechend müssen wir das ein bisschen umformulieren, sodass wir von der V-Runtime 2 minus die V-Runtime 1 berechnen. So haben wir den den den Überlauf ein bisschen raus, weil also die Arithmetik ist vollständig auch über überläufe. Und wenn wir das dann noch als Intkasten, dann haben wir auch einen Vorzeichen davor und können entsprechend die Tasks vergleichen. Nun sind wir noch lange nicht am Ende bezüglich Tasks oder Tasks im completely fair Scheduler. Es gibt seit vielen Jahren sogenannte Task Groups, das sind Gruppen von Tasks, die genauso gescheduled werden können wie normale Tasks. Entsprechend benutzt der completely fair Scheduler, Scheduling Entities anstatt der Tasks direkt. Scheduling Entities können Tasks sein oder halt komplette Task Gruppen. So ergibt sich dann ein Baum von Run Queues oder wenn man sich das hier anguckt, haben wir oben eine Run Queue, das ist der ganz normale Fall. Darunter sind zwei Scheduling Entities, hier abgekürzt SE und jedes Scheduling Entity gehört zu einem Task direkt. Was aber dann mit Task Groups passiert ist, dass Scheduling Entities auch zu kompletten Gruppen gehören können. Und diese Scheduling Entities enthalten dann entbrechend keine Tasks, sondern komplette neue Run Queues. Und der completely fair Scheduler geht dann halt von oben durch, wählt jeweils immer ein Task aus oder eine Scheduling Entity, die laufen soll. Und wenn das ein Task ist, dann wird das direkt auf die CPU gescheduled. Wenn es kein Task ist, macht er den gleichen Algorithmus in dieser Run Queue nochmal. Und das ist natürlich beliebig fachtelbar am Ende. Wird im Moment nicht benutzt. Es gab aber mal ein Kernel Feature, also in den meisten Distributionen wird es nicht benutzt. Es gab aber mal ein Kernel Feature, das nennte sich Auto Grouping, was automatisch nach den Benutzer-Konsolen, die Task gruppiert hat, sodass über die ganzen Benutzer automatisch fernes sichergestellt werden kann. Ja, jetzt haben wir bislang nur eine CPU betrachtet. Der completely fair Scheduler betrachtet aber alle CPUs und muss entsprechend auch zwischen den CPUs die Tasks hin und her wechseln können. Dafür benutzt es sowas wie Scheduling Domains. Scheduling Domains sind dann sowas wie, wo zwei CPUs, z.B. die Hyperthreading-CPUs zusammengebunden sind, ergeben sich Scheduling Domains, die dann diese zusammenfassen. Die Tiefe im Baum entspricht dann entsprechend den Kosten, um ein Task von einer Scheduling Domain in eine andere zu verschieben. Normalerweise auf normalen Systemen, also so wie bei euch allen wahrscheinlich, ist es so aufgebaut, dass auf dem untersten Level die Symmetric-Multithreading-CPUs zusammengefasst sind. Also sowas wie Hyperthreading, da werden die beiden CPUs, die in einem Core stecken und Hyperthreading machen, zusammengefasst als eine Domain. Die Kosten, um da einen Task zu migrieren, sind quasi null, entsprechend kann das der Scheduler auch sehr schnell machen. Im Leveler drüber sind dann Symmetric Multi-Processing, d.h. die mehreren Cores, die in jedem Prozessor stecken quasi. Da ist es immer noch relativ günstig, einen Task zu migrieren auf eine andere CPU innerhalb dieses Cores, weil es, also weil meist gibt es sowas wie Level 3-Cash, die dann über alle CPUs innerhalb dieses Cores gehen und entsprechend haben wir da immer noch so ein bisschen Caches, die quasi warm bleiben. Am teuersten und die höchste Scheduling-Domain auf normalen Rechnern ist dann sowas wie bei Dual Socket-Systemen, wo dann tatsächlich Hardware, also zwei Hardware-Prozessoren auf zwei Sockets stecken, die dann verschiedene RAM-Bänke haben. Das ist natürlich extrem teuer, zu migrieren, weil wir dann auch noch anfangen müssen, den Speicher entsprechend drüber zu schieben und wir haben quasi keine Caches mehr, die warm bleiben dadurch. Diese ganzen Informationen sind auch erreichbar, ich kann das mal zeigen, z.B. hier in Sys-Devices-System-CPU-CPU0, da gibt es ein Unterverzeichnis-Tropology und da gibt es sowas wie Core-Sibling-List oder Thread-Sibling-List und da sehen wir halt z.B. hier die Thread-Sibling-List, da sehen wir, dass die CPUs 0 und 1, die in diesem System angemeldet sind, die gehören tatsächlich zu einem Core und sind quasi zusammengebunden. Wiederum Core-Sibling-List ist 0 bis 3, das heißt alle CPUs sind tatsächlich in einem Core und das ist auf jedem System abrufbar. So, um jetzt Load-Balancing zwischen diesen Scheduling-Domains zu machen, hat der komplett fair Scheduler einen sogenannten Load-Wert, der pro CPU gehalten wird, daran und Pro Scheduling-Domain natürlich, daran kann er dann erkennen, wo ein Load im Balance besteht und kann entsprechend die Tasken hin und her schieben, um dieses Load und Gleichgewicht auszugleichen. Das macht er in Intervallen und dann geht halt durch alle Scheduling-Domains und versucht das möglichst gut auszugleichen. Dabei vermeidet er natürlich die Scheduling-Domains, die ganz oben liegen, weil das am teuersten wäre, z.B. von einem Prozessor auf den nächsten zu migrieren. Noch ein Thema, was sehr wichtig ist bei aufwachenden Tasks oder bei neuen Tasks. Jeder Scheduler muss dort entscheiden, wo dieser Task als erstes anfängt zu laufen. Wenn diese Entscheidung schlecht ist, muss er dann irgendwann hinterher migrieren und das kostet sehr viel mehr Zeit, als wenn wir den gleich richtig platzieren. Entsprechend wird hier ein bisschen mehr Aufwand getrieben, um herauszufinden, wo das am besten platziert wird, der Task. CFS, also der komplett fair Scheduler, untersucht dann z.B. auch sowas, wie wo gehört dieser Task hin, ist das vielleicht ein Thread von einem Prozess. Ist das sinnvoll, den zusammen zu packen mit den anderen Threads dieses Prozesses etc. Dabei muss natürlich noch gewährleistet werden, dass wir die Load nicht extrem beeinflussen. Wichtig ist natürlich auch sowas wie schlafende Tasks. Wenn ein Task lange auf einen Time erwartet und dann irgendwann aufwacht, dann ist die virtuelle Laufzeit von diesem Prozess natürlich extrem weit entfernt von allen Laufzeiten der anderen Tasks, die gerade aktiv sind. Entsprechend ist da nur so ein halbes Fairness-Konzept benutzt beim Completely Fair Scheduler, wo dann sowas gemacht wird, wie diese virtuelle Laufzeit darf nicht tiefer als ein gewisser Wert sein und sonst geben wir ihm halt sowas wie die Hälfte der Laufzeit, die er geschlafen hat, um da so ein bisschen Fairness reinzubringen, aber um auch nicht die anderen Tasks quasi aussterben zu lassen, dadurch, dass wir einen Task haben, der extrem geringe virtuelle Laufzeit hat. Und dann gibt es natürlich noch so Fragen wie ist es sinnvoll, den aktuell aufgewägten Task mit auf die aktuelle CPU zu bringen und den aktuellen laufenden Task verdrängen. Das ist zum Beispiel wichtig, wenn wir I.O. machen und möglichst schnell den Durchsatz erhöhen wollen, dann müssen wir natürlich darüber nachdenken, ob wir den Tasks der aktuell läuft verdrängen. Hier sind noch ein paar Files, wenn ihr selber nachgucken wollt in eurem System, was da alles so gibt. Das sind alles Files, die so ein bisschen das Verhalten des Schedulers zeigen und ein paar Informationen preisgeben. Ganz oben die Topologie, das habe ich eben so ein bisschen gezeigt schon, da wird halt alles beschrieben, was die Topologie angeht. Context switches, das ist ganz interessant, das kann ich auch nochmal gerade. Ich habe hier schon so ein Prozess laufen lassen, so ein Watch der slash proc slash start nach Context wrapped und das jede Sekunde wiederholt. Und das sind halt die Anzahl der Tasks, die gerade gewechselt wurden innerhalb einer Sekunde. Also diese veränderte Zahl da. Das ist selbst bei einem System, wo ich gerade nichts mache, extrem hoch. Ich sehe gerade so 500 bis 1000 pro Sekunde Taskswitches. Ja, damit muss der Scheduler natürlich klar kommen. Dann gibt es natürlich ein paar Debugging-Hilfen für Entwickler oder auch Interessierte in slash proc slash sketch debug und pro Prozess gibt es auch noch mal sowas, ebenfalls in proc, dann die Process ID und Scat. Dann gibt es Scheduling Features, die man auch zum Teil anpassen kann durch neues Kompilieren. Da wird dann sowas eingestellt, sowas wie wenn wir ein Prozess forken, soll der sofort laufen oder soll der erst laufen, wenn der Parentprozess wieder runter ist. Dann gibt es noch mehr Einstellungen unter Prozess, Kernel, Scat. Da kann man sowas einstellen, wie die minimale Granularität, wie lange ein Task auf der CPU sein Roll, bevor er verdrängt werden kann oder ein anderer kommt. Dann gibt es auf manchen Systemen, auf Debian ist aktuell leider nicht einkompiliert, Scheduling Statistics, die sind unter slash proc slash sketch start, sind aber auch sehr interessant eigentlich. Und wenn man ganz detailliert wissen will, was passiert beim Scheduler, kann man mit Trace Command und Kernel schagen, jeden einzelnen Task switch tracen und anzeigen lassen. Ja, kommen wir mal ein bisschen zur Realität und was wir dort beobachten können. Die Benchmarks, die jetzt kommen oder die Plots davon, sind alle auf einem Core i7 2600K gemacht worden mit Hyper Threading, also ein normaler Quadcore mit 3,5 GHz oder sowas. Jeder Benchmark wurde mindestens dreimal wiederholt, um eine Genauigkeit zu erreichen. Außerdem wurde darauf geachtet, dass die Werte einigermaßen stimmen. Die Plots enthalten dann auch Error Bars. Ich habe in den ersten Plots verschiedene Scheduler verglichen. Da gibt es einmal 3,18.0, das ist der completely fair Scheduler, 3,18.0 plus, das ist eine leicht veränderte Variante von mir mit einer partiell sortierten Liste anstatt des Thread Black Trees. Entsprechend ist die Komplexität davon log n auf o von 1 runtergegangen und dann noch den Brain Fax Scheduler von ConColivus, das ist der CK1 plus. Ja, erstmal zur Realität und Hyper Threading. Wie man hier schön sieht, das ist eine Quadcore-Maschine mit 8 echten Threads. Entsprechend ist Threads gleich 4 zu Threads gleich 8, ist halt der Unterschied zwischen Hyper Threading und nicht Hyper Threading. Wie man sieht, macht CFS das kein Problem und man sieht noch einen kleinen Performance-Unterschied. Achso, was man hier überhaupt sieht, das ist die Compile Laufzeit eines normalen Kernels. Ich glaube, das war 3,0. Entsprechend ist das aufgetragen auf die Laufzeit in Sekunden und unten sieht man noch die Threads, die verwendet wurden, also 1, 2, 4, 8 und 16. Wie man sieht, hier von 1 auf 2 zum Beispiel haben wir die Laufzeit fast halbiert, von 2 auf 4 wieder fast halbiert und dann von 4 auf 8 ist das nicht mehr ansatzweise halbiert. Das ist dann halt der Benefit, den wir von Hyper Threading haben. Was man auch sieht, ist, wenn ein Scheduler Hyper Threading nicht beachtet, so nämlich der Concolivus BrainFox Scheduler, der hatte in der Version noch keine Erkennung, ob ein Prozessor Hyper Threading macht oder nicht. Entsprechend sind da die Tasks wahrscheinlich nicht so gut platziert wie bei den anderen Schedulern und er braucht hier deutlich länger im Vergleich zu allen anderen Threads Zahlen. Entsprechend ist es deutlich wichtig, dass man darauf achtet, dass solche Hardware auch wirklich richtig erkannt wird und wie richtig benutzt wird. Dann zu einem etwas mehr Benchmark oder Disk abhängigen Benchmark. Hier geht es um den Compression Speed von Sevenzip und was wir hier sehen ist auch, dass hier der Scheduler am maßgeblichen Einfluss darauf hat, wie schnell die Tasks sind. Hier ist auf der Y-Achse jetzt die Compression Speed in Kilobyte pro Sekunde. Unten ist wieder die Thread Anzahl und ja wieder die drei verschiedenen Scheduler. Was man auch sieht, ist, dass wenn man mehr Threads auf den Tasks wirft, dass es irgendwann schlechter wird. Warum das so ist, kann auch mit Disk IO zusammenhängen. Dann Inter Process Communication, das ist ganz oft benutzt in ganz vielen verschiedenen Programmen. Zum Beispiel Chromium hat keine Threads, sondern tatsächliche Prozesse und benutzt Inter Prozess Kommunikation, um unter den Prozessen untereinander zu kommunizieren. Hackbench ist halt ein Extremum von Inter Process Kommunikation. Was Hackbench macht, ist eine Menge an Tasks zu erzeugen, die untereinander kommunizieren und sie machen nichts anderes als untereinander zu kommunizieren. Entsprechend ist das einzige, was ein Tasks tut, eine Menge von Nachrichtensenden, dann schlafen und warten, dass Antworten kommen. Und das ist natürlich ein echter Queeltest für ein Scheduler. Was man hier sieht, ist, dass zum Beispiel der kompletlich fair Scheduler bei ungefähr sechs Sekunden liegt. Bei der Ausführung dieses Benchmarks, der Scheduler mit einer partiell sortierten Liste, also O von 1, liegt bei ungefähr vier Sekunden und der Konkolivus ist halt extrem viel schlechter bei ungefähr 13 Sekunden. Das hat alles nicht zu bedeuten, weil alle anderen, also es ist halt immer, wenn man etwas am Scheduler ändert, dann macht man manche Benchmarks besser und andere wieder schlechter. Das ist ein bisschen frustrierend manchmal, aber man muss halt darauf achten, dass man nicht alles so viel schlechter macht. Hier ist eine Dual Socket Maschine nochmal, also 24 CPUs, eigentlich 12 Kerne. Und diesmal geplottet über verschiedene Kernel-Versionen. Also nicht mal über verschiedene Kernel-Versionen ist solch ein Benchmark tatsächlich stabil. Das ist auch sehr schwer einzuhalten, aber man sieht halt auch gut, dass wir hier zwischen fünf Sekunden, fünf und sechs Sekunden Unterschiede haben, zum Beispiel zwischen 317 und 318. Es ist ganz extrem gewesen. Das ist aber noch lange keine Grundsverbotenrückung, weil Hackbench ist ein sehr synthetischer Benchmark. Ja, Context Switch Madness. Wie ich bereits erwähnt habe, ist Hackbench sehr fordernd in Bezug auf den Scheduler, macht extrem viele Context Switches. Entsprechend, was wir hier sehen können, ist das zum Teil bei verschiedenen Kernel-Versionen auf dem 24 CPU-System bis zu 1,2 mal 10 hoch 8 Context Switches gemacht wurden innerhalb des Benchmarks und das innerhalb von 40 Sekunden, was ungefähr auf 3 Millionen Context Switches pro Sekunde rauskommt. Das heißt, unser Scheduler wird 3 Millionen mal in eine Sekunde aufgerufen, der Code. Entsprechend muss der Code extrem optimiert werden. Es ist halt sehr schwer, das alles dann auch tatsächlich im Code noch lesbar zu halten. Sie versuchen es nicht, es ist halt nicht unbedingt möglich. Okay, ich kann ... ping, hallo, hallo. So, ja, entsprechend ist halt alles super optimiert. Man findet, wenn man den Kernel Code liest, extrem viele Likely und Unlikely Makros zum Beispiel, die sowas machen, wie ist es sehr wahrscheinlich, dass diese Bedingungen ein Wahr ergibt oder ein Unwahr nur um zu optimieren, ob unsere Branch Prediction im Prozessor dann die richtigen Voraussagen trifft. Auch wichtig, was ich erwähnt hatte, ist Interaktivität. Und hier ist ein Beispiel, wie ein Scheduler es falsch macht oder nicht sehr gut für diesen Fall optimiert ist. Der BrainFuck-Scheduler, hatte ich ja erwähnt, ist hauptsächlich für kleine Systeme mit wenigen Hasks ausgestattet oder ausgelegt. In diesem Benchmark haben wir extrem viele Threads auf diesen Scheduler draufgeworfen, wieder mit Hackbench. Und was wir sehen ist, dass der Scheduler piekt auf 1,2 Sekunden Latents. Das heißt, wenn ein Task aufgewacht ist, muss er noch 1,2 Sekunden warten, bis er dran ist. Und das natürlich extrem schlecht, wenn man zum Beispiel Network IO macht, dann ist quasi unser Task tot danach. Ja, Aussicht. Was kann man noch so am Scheduler tun und was wird gemacht? Vor einigen Wochen kam erst wieder ein weiterer Versuch, Power Aware Scheduling zu machen, was wir bislang noch nicht wirklich haben. Entsprechend ist der Scheduler im Moment nicht darauf ausgelegt, überhaupt darauf zu achten, Power zu achten. Also im Moment wird nur nach Durchsatz optimiert und gescheduled. Außerdem fehlt auch noch ein generalisierter Support für sowas wie ARM Big Little, wo wir halt verschiedene Systeme oder verschiedene CPUs haben, die unterschiedlich tatsächlich sind. Das ist ein großer Punkt, wo noch dran gearbeitet wird. Und über die Zeit kommen natürlich immer mehr komplexe Architekturen. Es kommen sowas wie FPGAs, wo dann ganz flexibel Sachen drin sind und wo dann auch Linux drauflaufen soll. Und naja, das sind halt ganz offene Fragezeichen, die noch bearbeitet werden müssen. Ja und damit, dank für eure Aufmerksamkeit, habt ihr irgendwelche Fragen. Gute Frage. Es geht darum, was bei folgenden Prozessen, also bei neuen Prozessen ist, wie da die Virtual Runtime aussieht, weil die Virtual Runtime ja monoton steigend ist. Der Completely Fair Scheduler maintain eine Variable, die die minimale Virtual Runtime enthält. Und ich bin mir nicht sicher, ob genau die benutzt wird für neue tags oder ob das, ob da irgendwie noch was abgerechnet wird. Aber auf jeden Fall wird die minimale, aktuelle Virtual Runtime benutzt, um davon die für die neuen Tasken zu berechnen. In welchem? Nein, nein, die minimale Virtual Runtime ist natürlich die von dem aktuell laufenden Task. Also die minimale Virtual Runtime ist die von allen Tasken, die gerade am laufen sind. Entsprechend ist der aktuell laufende Task quasi die minimale Virtual Runtime und der neue Prozess bekommt die gleiche und würde entsprechend als nächstes dann laufen. Ist das verständlich? Also ich kann das nochmal versuchen. Wenn wir jetzt einen Deemen haben, der eine Virtual Runtime von, ich nehme mal kleine Zahlen von 10 hat und der ist gerade am laufen quasi, dann haben wir auf der Run Queue eine minimale Virtual Runtime von 10, weil das ist ja der Task, der gerade läuft. Wie ich beschrieben hatte, wird ja beim completely fair Scheduler immer der Task genommen, der die kleinste Virtual Runtime hat. Entsprechend ist die minimale Virtual Runtime auf der Run Queue 10 und der neue Task wird ebenfalls dann 10 bekommen oder eine, entweder 9 oder 11, irgendwie sowas. Ich bin mir nicht ganz sicher, da müssten wir doch gleich mal in den Code gucken. Ansonsten kommen wir einfach gleich nochmal zu mir, dann können wir gucken. Die Frage war, ob die Virtual Runtime am Decayen ist, quasi wieder verfällt. Nein, das ist nicht der Fall. Die Virtual Runtime ist immer monoton steigend bei allen Prozessen. Wenn irgendwann ein neuer Prozess kommt, der eine zu geringe hat, dann wird das von unten her begrenzt, sodass das keine Probleme macht. Aber die Virtual Runtime ist immer monoton steigend, bis es einen Überlauf gab. Wenn der neue Task die Virtual Runtime 10 bekommt, dann fängt er auch da an, weiter zu zählen. Nein, Theo, ich hatte da was ausgerechnet und bin auf 200 Tage gekommen. Achso, das wird dividiert. Also die Frage war, ob oder wann der Überlauf tatsächlich stattfindet. Bei Nanosekunden, bei einem U in 64, müssen wir nochmal nachrechnen, aber ich dachte, ich hätte ausgerechnet, dass man irgendwo bei 200 Tagen liegt. Also ich habe es auch bei Benchmax schon mal erlebt, dass tatsächlich die Context-Witches übergelaufen sind, nicht die Virtual Runtime. Ja, weitere Fragen. Die Frage war, wie Real-Time Scheduling-Classes zu diesem Thema eingeordnet sind. Es gibt verschiedene Scheduling-Classes, diese faire Klasse quasi und dazu zusätzlich noch die Real-Time Klasse, die im Moment durch einen FIFO-Scheduler implementiert ist. Und das ist in Linux so aufgebaut, dass es quasi verschiedene Klassen gibt, die untereinander liegen. Die unterste Klasse ist die IDLE-Klasse. Darüber kommt die faire Klasse und da drüber die Real-Time Klasse. Und Linux geht dann von oben durch und versucht, in jeder Klasse einen Task zu finden, der lauffähig ist. Und wenn die Real-Time Klasse einen Task hat, der lauffähig ist, wird da als erstes reingesprungen und der Task von der Real-Time Klasse genommen. Wenn die Real-Time Klasse keinen hat, wird weitergegangen zur fairen Klasse. Wenn die auch keinen hat, geht es in die IDLE-Klasse, wo dann ein IDLE-Prozess gescheduled wird. Noch irgendjemand Fragen? Okay, dann vielen Dank.