home *** CD-ROM | disk | FTP | other *** search
/ DOS/V Power Report 2004 March / VPR0403.ISO / talks / 057 / paper.dkb < prev    next >
Encoding:
Extensible Markup Language  |  2003-09-18  |  41.4 KB  |  972 lines

  1. <?xml version="1.0" encoding="iso-8859-1"?>
  2. <!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
  3. "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
  4.  
  5. <article lang="de">
  6.   <articleinfo>
  7.     <title>Skalierbare High-Performance Netzwerkprogrammierung unter Linux</title>
  8.  
  9.     <author>
  10.       <firstname>Felix</firstname>
  11.       <surname>von Leitner</surname>
  12.       <affiliation>
  13.         <address><email>felix-linuxtag@codeblau.de</email></address>
  14.       </affiliation>
  15.     </author>
  16.  
  17.     <abstract>
  18.       <para>Hochperformanter, gut skalierender Netzwerk-Code ist heute
  19.       eines der wichtigsten Verkaufsargumente fr
  20.       Server-Betriebssysteme.  Die Standard-Interfaces von POSIX und der
  21.       Open Group eignen sich nur bedingt fr hohe Skalierbarkeit.</para>
  22.       <para>Daher haben alle Anbieter in diesem Marktsegment eigene
  23.       Interfaces entwickelt.  Dieser Artikel beleuchtet die Unterschiede
  24.       stellvertretend an der Evolution dieser Interfaces unter
  25.       Linux.</para>
  26.     </abstract>
  27.   </articleinfo>
  28.  
  29.   <sect1>
  30.     <title>Grundlagen der Netzwerk-Programmierung</title>
  31.  
  32.     <para>Unter Unix werden Ger舩e und IPC-Mechanismen wie Dateien
  33.     angesprochen.  Man fnet eine Datei mit
  34.     <computeroutput>open()</computeroutput>, liest mit
  35.     <computeroutput>read()</computeroutput>, schreibt mit
  36.     <computeroutput>write()</computeroutput> und schlie゚t die Datei
  37.     wieder mit <computeroutput>close()</computeroutput>.  Hier ist ein
  38.     kurzer Beispiel-Code:</para>
  39.  
  40.     <programlisting>
  41.     char buf[4096];
  42.     int fd=open("index.html", O_RDONLY);
  43.     int len=read(fd,buf,sizeof buf);
  44.     write(outfd,buf,len);
  45.     close(fd);
  46.     </programlisting>
  47.  
  48.     <para>Man beachte, da゚ <computeroutput>open()</computeroutput> einen
  49.     Integer zurckliefert, d.h. eine Zahl.  Diese dient als Referenz auf
  50.     ein Kernel-Objekt; man kann damit in seinem Programm nichts tun, als
  51.     sie dem Kernel gegenber als Referenz zu benutzen.  Damit Rechnen
  52.     ist sinnlos, und es gibt auch keine Mlichkeit, zu einem solchen
  53.     File Deskriptor den Dateinamen zu erfragen.</para>
  54.  
  55.     <para>Fr jeden Proze゚ fangen die Deskriptoren bei 0 an, und 0 bis 2
  56.     sind fr Eingabe, Ausgabe und Fehlermeldungen reserviert.  POSIX
  57.     sagt, da゚ der Kernel als neuen Deskriptor immer den kleinsten noch
  58.     nicht vergebenen zuweist.  Mehrere Prozesse knen (verschiedene)
  59.     Deskriptoren fr die selbe Datei haben.</para>
  60.  
  61.     <para>Das API ist offensichtlich:
  62.     <computeroutput>read()</computeroutput> kehrt zurck, wenn es Daten
  63.     gelesen hat.  <computeroutput>write()</computeroutput> kehrt zurck,
  64.     wenn die Daten geschrieben wurden.  Danach kann man den Puffer
  65.     anderweitig verwenden.</para>
  66.  
  67.     <para>Netzwerkprogrammierung funktioniert analog.  Die Deskriptoren
  68.     verweisen nicht auf Dateien, sondern auf Sockets (Steckdose).  Einen
  69.     Socket richtet man mit <computeroutput>socket()</computeroutput>
  70.     ein.  Hier ist ein Code-Beispiel:</para>
  71.  
  72.     <programlisting>
  73.     char buf[4096];
  74.     int len;
  75.     int fd=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
  76.     struct sockaddr_in si;
  77.  
  78.     si.sin_family=PF_INET;
  79.     inet_aton("127.0.0.1",&si.sin_addr);
  80.     si.sin_port=htons(80);
  81.     connect(fd,(struct sockaddr*)si,sizeof si);
  82.     write(fd,"GET / HTTP/1.0\r\n\r\n");
  83.     len=read(fd,buf,sizeof buf);
  84.     close(fd);
  85.     </programlisting>
  86.  
  87.     <para>Dieser Code fnet eine TCP-Verbindung zum Rechner 127.0.0.1 auf
  88.     Port 80 und fragt nach /.  Es handelt sich hier also um einen
  89.     simplen HTTP-Client.</para>
  90.  
  91.     <para>Das Socket-API trennt das Erstellen des Sockets von dem
  92.     Erstellen einer Verbindung.  Der Hintergrund sind Server, die sich
  93.     nie mit einer Gegenstelle verbinden, sondern zu denen die
  94.     Gegenstellen sich verbinden.  Hier ist ein Mini-Server:</para>
  95.  
  96.     <programlisting>
  97.     int len,s,fd=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
  98.     struct sockaddr_in si;
  99.  
  100.     si.sin_family=PF_INET;
  101.     inet_aton("127.0.0.1",&si.sin_addr);
  102.     si.sin_port=htons(80);
  103.     bind(fd,(struct sockaddr*)si,sizeof si);
  104.     listen(fd,16);
  105.     s=accept(fd,(struct sockaddr*)&si,&len);
  106.     </programlisting>
  107.   </sect1>
  108.   <sect1>
  109.    <title>Handhabung mehrerer paralleler Verbindungen</title>
  110.  
  111.    <para>Wie kann ein Server mehr als einen Client verwalten?
  112.    Offensichtlich ist das Modell mit dem blockierenden
  113.    <computeroutput>read()</computeroutput> und
  114.    <computeroutput>write()</computeroutput> nicht geeignet, um in einem
  115.    Proze゚ mehr als eine Verbindung zu halten — man knte immer
  116.    nur Daten ber eine Verbindung zur Zeit schicken.  Schlimmer noch,
  117.    sobald der erste Client mal nichts schickt, bleibt das
  118.    <computeroutput>read()</computeroutput> und damit die anderen
  119.    Clients h舅gen.</para>
  120.  
  121.    <para>Die traditionelle Lung dafr ist, pro Verbindung einen neuen
  122.    Proze゚ zu starten.</para>
  123.  
  124.    <sect2>
  125.     <title>Ein Proze゚ pro Verbindung</title>
  126.  
  127.     <para>Unter Unix ist das glcklicherweise sehr einfach;
  128.     man kann den gerade laufenden Proze゚ einfach klonen, man mu゚ nicht
  129.     ein weiteres Programm dafr von der Festplatte laden, und der neue
  130.     Proze゚ bernimmt auch alle Variablen und offenen File
  131.     Deskriptoren.</para>
  132.  
  133.     <para>Trotzdem hat dieses Modell Nachteile und Probleme,
  134.     insbesondere bei der Skalierbarkeit.  Typische Betriebssysteme sind
  135.     fr den Fall von ein paar Dutzend bis zu Hundert Prozessen
  136.     optimiert.</para>
  137.  
  138.     <para>Dazu kommt, da゚ pro Proze゚ normalerweise bedeutende Mengen
  139.     Systemspeicher verbraucht werden, insbesondere bei dynamisch
  140.     gelinkten Programmen.  10.000 Clients gleichzeitig bringen auch
  141.     grere Server zum Swappen, selbst wenn die Clients gar keine Daten
  142.     anfordern.  Manche Systeme (Solaris, Windows) brauchen au゚erdem sehr
  143.     lange fr die Proze゚erzeugung und eignen sich daher fr dieses
  144.     Modell nicht.</para>
  145.  
  146.     <para>Auch stellt sich bei diesem Modell die Frage, wie man Timeouts
  147.     realisieren soll.  Wenn ein Client nach dem Verbindungsaufbau fr 20
  148.     Minuten nichts sagt, sollte der Server in der Lage sein, das zu
  149.     erkennen und die Verbindung zu schlie゚en.
  150.     </para>
  151.  
  152.    </sect2>
  153.    <sect2>
  154.     <title>Skalierbares Scheduling</title>
  155.     <para>
  156.     Das Betriebssystem verwaltet eine Liste von Prozessen
  157.     und unterbricht periodisch (blicher Wert: 100 Mal pro Sekunde) den
  158.     laufenden Proze゚, um andere laufen zu lassen.  Dieser Teil des
  159.     Systems, der den neuen Proze゚ ausw臧lt, hei゚t Scheduler.
  160.     </para>
  161.  
  162.     <para>
  163.     Wenn ein Proze゚ wegen eines <computeroutput>read()</computeroutput>
  164.     oder <computeroutput>write()</computeroutput> blockiert, w臧lt der
  165.     Scheduler auch einen neuen Proze゚.  Wenn also hunderte von Prozessen
  166.     konkurrierend lesen und schreiben und dabei st舅dig blocken, dann
  167.     verbringt das System einen signifikanten Teil der CPU-Zeit im
  168.     Scheduler.
  169.     </para>
  170.  
  171.     <para>
  172.     Typischerweise laufen auf einem System eine paar Dutzend Prozesse,
  173.     von denen ein oder zwei tats臘hlich laufen wollen, w臧rend die
  174.     anderen gerade auf eine Eingabe oder ein Signal warten.  Es ist
  175.     also sinnvoll, wenn das System zwei Proze゚listen hat — eine
  176.     fr die laufen wollenden Prozesse und eine fr die auf Eingabe
  177.     wartenden.  Die erste Liste wird dann sehr kurz sein und der
  178.     Scheduler spart sich das Anschauen der gro゚en Gesamtliste.
  179.     </para>
  180.  
  181.     <para>
  182.     Unix hat einen Mechanismus, um interaktive Prozesse zu bevorzugen
  183.     und Batch-Prozesse zu benachteiligen.  Dazu wird sekndlich fr alle
  184.     Prozesse der <computeroutput>nice</computeroutput>-Wert neu
  185.     berechnet.  Dafr schaut sich der Scheduler alle Prozesse an, und
  186.     mu゚ dabei bei einer sehr gro゚en Proze゚anzahl sehr viele Daten lesen.
  187.     Das fhrt besonders bei 舁teren oder Desktop-CPUs zum Cache
  188.     Trashing.
  189.     </para>
  190.  
  191.     <para>
  192.     Ein weiteres typisches Problem ist auch, da゚ die Proze゚tabelle mit
  193.     einem Kernel Lock geschtzt wird.  Bei einem System mit mehr als
  194.     einem Prozessor wird also kann also der zweite Prozessor w臧rend der
  195.     Nice-Berechnung keinen Proze゚wechsel vornehmen.  Dieses Problem
  196.     len kommerzielle Unix-Varianten gewnlich, indem sie die
  197.     Datenstruktur auf die Prozessoren verteilen, so da゚ jeder Prozessor
  198.     eine eigene Run Queue hat.
  199.     </para>
  200.  
  201.     <para>
  202.     Es gibt noch weiteren Spielraum fr Optimierungen im Scheduler.  Man
  203.     kann z.B. die Run Queue sortiert vorhalten, und hierfr bieten sich
  204.     diverse schlaue Datenstrukturen wie Heaps an, die den Aufwand von
  205.     O(n) auf O(log n)<footnote>Vereinfacht gesagt hei゚t O(n), da゚ der
  206.     Aufwand linear mit der Gre der Datenstruktur w臘hst (10.000
  207.     Prozesse: Aufwand 10.000), w臧rend bei O(log n) der Aufwand nur
  208.     logarithmisch steigt (10.000 Prozesse: Aufwand 14).</footnote> senken
  209.     knen.
  210.     </para>
  211.  
  212.     <para>
  213.     Der heilige Gral der Scheduler ist allerdings der O(1)<footnote>O(1)
  214.     zeigt an, da゚ der Aufwand fr den Scheduler konstant ist, unabh舅gig
  215.     von der Anzahl der Prozesse im System</footnote>-Scheduler.  Im
  216.     Gesamtsystem wird die Leistung nie vlig unabh舅gig von der Anzahl
  217.     der Prozesse sein knen, weil die Leistung heutiger Prozessoren
  218.     stark davon abh舅gig ist, da゚ alle h舫fig benutzten Daten im
  219.     Prozessor-Cache Platz finden.
  220.     </para>
  221.  
  222.     <para>
  223.     Der Linux 2.4 Scheduler hat eine einzelne unsortierte Run Queue mit
  224.     allen lauff臧igen Prozessen ("runnable"), und eine Liste mit allen
  225.     blockenden Prozessen und Zombies.  Alle Operationen auf der Run
  226.     Queue sind hinter einem Spinlock geschtzt.  Der Scheduler bemht
  227.     sich um CPU-Affinit舩<footnote>Damit ist gemeint, da゚ bei einem
  228.     SMP-System ein Proze゚ nicht st舅dig von einem Prozessor zum n臘hsten
  229.     wechselt, weil jeder Prozessor seinen eigenen Cache mitbringt, der
  230.     dann nicht optimal ausgenutzt wird.</footnote>
  231.     </para>
  232.  
  233.     <para>
  234.     Fr Linux gibt es diverse neue Scheduler, mit Heaps und multiplen
  235.     Run Queues, aber der erstaunlichste Scheduler ist der O(1)-Scheduler
  236.     von Ingo Molnar.  Dieser Scheduler ist in den 2.5 "Hacker-Kerneln"
  237.     Standard.
  238.     </para>
  239.  
  240.     <para>
  241.     Der O(1)-Scheduler hat pro CPU zwei Arrays mit jeweils einer
  242.     verketteten Liste.  Pro mlicher Proze゚-Priorit舩 gibt es eine
  243.     Liste in diesen Arrays.  Da alle Eintr臠e in diesen Listen die
  244.     gleiche Priorit舩 haben, mssen die Listen nicht weiter sortiert
  245.     werden.  Das eine Array pro CPU hat die lauff臧igen Prozesse, das
  246.     andere ist leer.  Das Ein- und Austragen eines Prozesses in einer
  247.     doppelt verkettete Liste ist O(1).  Der Scheduler nimmt sich also
  248.     immer die volle Liste, l葹t jeden Proze゚ darin einmal laufen, tr臠t
  249.     den Proze゚ dann in dieser Liste aus und in der anderen Liste ein,
  250.     und wenn die Liste leer ist, tauscht er die Zeiger auf die Arrays
  251.     aus.  Die <computeroutput>nice</computeroutput> Berechnung wird in
  252.     diesem Scheduler umgekehrt: jetzt werden nicht mehr interaktive
  253.     Prozesse belohnt, sondern Batch-Prozesse werden bestraft
  254.     (interaktive Prozesse blocken, Batch-Prozesse werden vom Scheduler
  255.     unterbrochen).
  256.     </para>
  257.  
  258.     <para>
  259.     Dieser geniale Scheduler skaliert praktisch unabh舅gig von der
  260.     Anzahl der Prozesse.  In Tests hat man selbst bei 100.000
  261.     gestarteten Threads keine Verlangsamung beobachtet.
  262.     </para>
  263.    </sect2>
  264.    <sect2>
  265.     <title>Kontextwechsel und Proze゚erzeugungskosten</title>
  266.     <para>
  267.     Der Kontextwechsel ist auf heutigen Prozessoren eine der langsamsten
  268.     Operationen berhaupt.  Ein Kontextwechsel findet statt, wenn ein
  269.     Proze゚ zu einem anderen gewechselt wird (der ワbergang von einem
  270.     Proze゚ zum Betriebssystem ist auch sehr aufwendig, aber dafr gibt
  271.     es h舫fig sehr effizienten Hardware-Support).
  272.     </para>
  273.  
  274.     <para>
  275.     Bei einem Kontextwechsel mu゚ das Betriebssystem die Register sichern
  276.     und laden, den TLB flushen und bei manchen Architekturen mu゚ auch
  277.     manuell die Pipeline geleert werden und die Caches geflusht werden.
  278.     Auf einem blichen Desktop-System mit laufendem mp3-Player kommen
  279.     10-70 Kontextwechsel pro Sekunde vor.  Wenn ein HTTP-Benchmark ber
  280.     Loopback l舫ft, sind es 10.000, ber ein Ethernet-Interface sogar
  281.     20.000.  Offensichtlich z臧lt hier jede Mikrosekunde.  Architekturen
  282.     mit wenig Registern wie x86 haben hier einen Erbvorteil gegenber
  283.     Architekturen mit vielen Registern oder komplizierten Anordnungen
  284.     wie SPARC oder IA-64.
  285.     </para>
  286.  
  287.     <para>
  288.     Der Speicherbedarf eines leeren Prozesses ist bei aktuellen
  289.     Linux-System in der Tat erschreckend gro゚.  Schuld ist aber nicht
  290.     Linux, sondern die libc unter Linux, die GNU libc.  Diese verbraucht
  291.     mit gro゚em Abstand von allen libc-Implementationen den meisten
  292.     Speicher und hat die hhsten Startup-Kosten.
  293.     </para>
  294.  
  295.     <para>
  296.     Ich habe daher eine eigene libc fr Embedded-Systeme und
  297.     Server-Anwendungen wie CGI-Programme und Servlets geschrieben, und
  298.     damit erreicht man ein statisches Hallo-Welt-Binary von unter 300
  299.     Bytes.  Das Ausfhren dieses Programmes inklusive
  300.     <computeroutput>fork()</computeroutput>,
  301.     <computeroutput>exec()</computeroutput> und
  302.     <computeroutput>wait</computeroutput> kostet auf einem Athlon ca.
  303.     200.000 CPU-Zyklen.  Ein 2 GHz Athlon kann also rein rechnerisch pro
  304.     Sekunde 10.000 Prozesse erzeugen.
  305.     </para>
  306.    </sect2>
  307.    <sect2>
  308.     <title>Timeout-Handling</title>
  309.     <para>
  310.     Die einfachste Methode zum Timeout-Handling ist
  311.     <computeroutput>alarm()</computeroutput>.
  312.     <computeroutput>alarm(10)</computeroutput> schickt dem aktuellen
  313.     Proze゚ nach 10 Sekunden ein Signal; wenn man dieses nicht explizit
  314.     abf舅gt, wird der Proze゚ dann vom System beendet.  Fr triviale
  315.     Netzwerk-Servlets ist das wie geschaffen.
  316.     </para>
  317.  
  318.     <para>
  319.     Unix bietet aber auch andere Methoden, um einen Timeout zu
  320.     implementieren.  Die bekannteste (und 舁teste — eingefhrt
  321.     1983 mit 4.2BSD) ist
  322.     <computeroutput>select()</computeroutput>:
  323.     </para>
  324.  
  325.     <programlisting>
  326.     fd_set rfd;
  327.     struct timeval tv;
  328.     FD_ZERO(&rfd); FD_SET(0,&rfd); /* fd 0 */
  329.     tv.tv_sec=5; tv.tv_usec=0;     /* 5 Sekunden */
  330.     if (select(1,&rfd,0,0,&tv)==0) /* read, write, error, timeout */
  331.       handle_timeout();
  332.     if (FD_ISSET(0, &rfd))
  333.       can_read_on_fd_0();
  334.     </programlisting>
  335.  
  336.     <para>
  337.     Das erste Argument ist die Nummer des grten bergebenen
  338.     Filedeskriptors plus 1 (das ist die eine Ausnahme, bei der Rechnen
  339.     mit den File Deskriptoren doch sinnvoll ist).  Die n臘hsten drei
  340.     Argumente sind Bitfelder; man setzt das 23. Bit im 1. Array
  341.     (<computeroutput>readfds</computeroutput>, read file descriptors),
  342.     wenn auf auf dem File Deskriptor 23 Daten lesen mhte.  Das 2.
  343.     Array ist fr Schreiben, das 3. fr Fehlerbehandlung.  Als letztes
  344.     Argument bergibt man noch, wie lange man hhstens warten mhte.
  345.     </para>
  346.  
  347.     <para>
  348.     Mit <computeroutput>select()</computeroutput> lassen sich
  349.     offensichtlich nicht nur Timeouts implementieren, sondern man kann
  350.     auch mehrere Deskriptoren auf einmal zum Lesen oder Schreiben
  351.     benutzen, d.h. man kann einen Server schreiben, der mehr als eine
  352.     Verbindung gleichzeitig behandeln kann.
  353.     </para>
  354.  
  355.     <para>
  356.     <computeroutput>select()</computeroutput> hat aber auch wichtige
  357.     Nachteile:
  358.     <itemizedlist>
  359.     <listitem><para><computeroutput>select()</computeroutput> sagt
  360.     nicht, wie lange es denn tats臘hlich gewartet hat.  Man mu゚ also
  361.     noch mal <computeroutput>gettimeofday()</computeroutput> o..
  362.     aufrufen.</para></listitem>
  363.     <listitem><para><computeroutput>select()</computeroutput> arbeitet
  364.     mit Bitfeldern.  Die Gre h舅gt vom Betriebssystem ab.  Wenn man
  365.     Glck hat, kann man so 1024 Deskriptoren ansprechen, h舫fig noch
  366.     weniger.</para></listitem>
  367.     </itemizedlist>
  368.     </para>
  369.  
  370.     <para>
  371.     Unter Linux gibt <computeroutput>select()</computeroutput> doch
  372.     zurck, wie lange es gewartet hat, indem es den Timeout-Wert um den
  373.     Betrag senkt, den es gewartet hat.  Das ist nicht nur unportabel, es
  374.     besch臈igt auch portable Software, die sich darauf verl葹t, da゚ der
  375.     Timeout-Wert konstant bleibt.  Die Bitfeld-Gre ist unter Linux
  376.     1024, bei NetBSD 256.
  377.     </para>
  378.  
  379.     <para>
  380.     Das Problem mit <computeroutput>select()</computeroutput> ist
  381.     schlimmer als es aussieht, weil es manchmal von irgendwelchen
  382.     Unter-Bibliotheken benutzt wird.  Apache hat an dieser Stelle z.B.
  383.     Probleme mit den DNS-Bibliotheken bekommen und h舁t sich daher
  384.     von Hand die Filedeskriptoren bis 15 frei, indem neue Deskriptoren
  385.     nach oben verschoben werden (das kann man mit
  386.     <computeroutput>dup2</computeroutput> machen).  DNS ht sonst
  387.     einfach zu funktionieren auf, sobald man mal alle Deskriptoren bis
  388.     1024 belegt hat.
  389.     </para>
  390.  
  391.     <para>
  392.     Eine Variation auf <computeroutput>select()</computeroutput> ist
  393.     <computeroutput>poll()</computeroutput>, eingefhrt 1986 mit System V
  394.     Release 3.  Man benutzt es ziemlich 臧nlich wie
  395.     <computeroutput>select()</computeroutput>:
  396.     </para>
  397.  
  398.     <programlisting>
  399.     struct pollfd pfd[2];
  400.     pfd[0].fd=0; pfd[0].events=POLLIN;
  401.     pfd[1].fd=1; pfd[1].events=POLLOUT|POLLERR;
  402.     if (poll(pfd,2,1000)==0) /* 2 records, 1000 milliseconds timeout */
  403.       handle_timeout();
  404.     if (pfd[0].revents&POLLIN) can_read_on_fd_0();
  405.     if (pfd[1].revents&POLLOUT) can_write_on_fd_1();
  406.     </programlisting>
  407.  
  408.     <para>
  409.     <computeroutput>poll()</computeroutput> hat kein Limit auf die
  410.     Anzahl oder Gre der Deskriptoren, dafr wird es auf manchen
  411.     Museumsexponaten nicht untersttzt.  Es gibt sogar ein aktuelles
  412.     Unix-Derivat, das kein <computeroutput>poll()</computeroutput>
  413.     untersttzt, und das es damit unmlich macht, auf ihm portabel
  414.     skalierbare Serversoftware laufen zu lassen: MacOS X.  Von der
  415.     Benutzung von MacOS X kann daher nur abgeraten werden, bis jemand
  416.     dem Hersteller fundamentales Unix-Wissen vermitteln konnte.
  417.     </para>
  418.  
  419.     <para>
  420.     W臧rend man mit <computeroutput>poll()</computeroutput> berhaupt
  421.     erst mal in die Lage versetzt wird, Programme mit 10.000 offenen
  422.     Client-Verbindungen zu schreiben, kann ein solches Programm mit
  423.     <computeroutput>poll()</computeroutput> nicht effizient laufen.  Der
  424.     Kernel mu゚ das gesamte Array aus dem User-Space lesen, die
  425.     zugehigen Socket-Objekte aus seinen Datenstrukturen heraussuchen,
  426.     auf neuen Daten oder Schreibbarkeit prfen, und dann im User-Space
  427.     das entsprechende Bit setzen.  Heutige Prozessoren sind weitgehend
  428.     von der Speicherbandbreite limitiert; das langwierige Durchgehen von
  429.     gro゚en Arrays im User-Space ist daher sehr ineffizient, insbesondere
  430.     wenn sich an dem Inhalt des Arrays seit dem letzten Aufruf von
  431.     <computeroutput>poll()</computeroutput> gar nichts ge舅dert hat.
  432.     </para>
  433.  
  434.     <para>
  435.     Das klingt auf den ersten Blick unvermeidlich, aber wenn man sich
  436.     typische Server-Lasten mit 10.000 Verbindungen ansieht, dann sind
  437.     nur wenige Verbindungen davon aktiv, die meisten sind sehr langsam
  438.     oder gar ganz inaktiv.  Ein viel effizienteres Schema w舐e daher,
  439.     wenn man die Verwaltung der Liste der gewnschten Events dem Kernel
  440.     berl葹t und ihm nur Ver舅derungen mitteilt.
  441.     </para>
  442.  
  443.     <para>
  444.     Es ist normalerweise nicht so kritisch, wenn
  445.     <computeroutput>poll()</computeroutput> langsam ist, weil die Events
  446.     ja nicht verloren gehen, sie kommen nur sp舩er an.  Der Durchsatz
  447.     leidet daher nicht so stark wie die Latenz.  Ich habe mit
  448.     <computeroutput>poll()</computeroutput> eine bis zu um den Faktor 20
  449.     here Latenz als mit SIGIO oder epoll gemessen.
  450.     </para>
  451.    </sect2>
  452.    <sect2>
  453.     <title>Das Multithreading-Debakel</title>
  454.     <part>
  455.     In den letzten Jahren hat sich Multithreading als Lung fr alle
  456.     Probleme in den Kfen etabliert, insbesondere fr Skalierbarkeit
  457.     und Performance auch und vor allem bei Netzwerkprogrammierung.  Die
  458.     Realit舩 sieht anders aus.  Die meisten Systeme haben ein hartes
  459.     Limit fr die Anzahl der gleichzeitigen Threads.  Eine bliche
  460.     Grenordnung ist 1024.  Von Skalierbarkeit kann unter solchen
  461.     Umst舅den keine Rede sein.  Auch ansonsten kann man mit Thread
  462.     bizarre Effekte erleben: pthread_create kann keine Threads anlegen,
  463.     selbst wenn nur bereits 4 laufen, oder manchmal sogar wenn gar keine
  464.     anderen Threads laufen, solange vorher viele Threads erzeugt wurden.
  465.     Es ist mir unbegreiflich, wie Leute Threads fr
  466.     Netzwerkprogrammierung verwenden knen.
  467.     </part>
  468.     <part>
  469.     Das Multiproze゚-Modell hat den Vorteil, da゚ es fehlertolerant
  470.     arbeitet.  Wenn ein Server-Proze゚ abstrzt, laufen die anderen
  471.     weiter.  Es gibt auch keine Memory Leaks: wenn sich ein
  472.     Server-Proze゚ beendet, gibt das System alle Resourcen automatisch
  473.     frei.  Bei Threads ist beides nicht so.  Wenn ein Thread einen
  474.     Absturz erzeugt, schie゚t das System den ganzen Proze゚ mit allen
  475.     anderen Threads mit ab.  Multithreading-Programme haben daher h舫fig
  476.     die Eigenschaft, schrankenlos vor sich hin zu wachsen.  Manch
  477.     kommerzielle Software kommt daher mit einem Shell Script, das sie
  478.     nachts automatisch abschie゚t und neu startet, um den Speicherlecks
  479.     entgegen zu arbeiten.
  480.     </part>
  481.  
  482.     <part>
  483.     Man mu゚ sich natrlich auch bei
  484.     <computeroutput>poll</computeroutput>-basierten Servern darum
  485.     kmmern, da゚ man keinen Speicher leckt, insofern ist das nicht den
  486.     Threads alleine anzulasten, aber viele Multithread-Projekte sind aus
  487.     Multiproze゚-Projekten hervorgegangen und kriegen ihre Lecks nie
  488.     wieder in den Griff.
  489.     </part>
  490.    </sect2>
  491.  
  492.    <sect2>
  493.     <title>Asynchroner I/O</title>
  494.     <part>
  495.     <computeroutput>poll</computeroutput> und
  496.     <computeroutput>select</computeroutput> funktionieren nicht auf
  497.     Dateien, nur auf Sockets, Ger舩edateien und Pipes.  Das ist bei
  498.     Festplatten normalerweise nicht so schlimm, aber wenn man einen
  499.     Server schreibt, der per NFS gemountete Dateien frei geben soll,
  500.     dann blockt bei einem NFS-Timeout der gesamte Server, selbst wenn
  501.     die anderen Requests alle Dateien von der lokalen Festplatte
  502.     betreffen.
  503.     </part>
  504.  
  505.     <part>
  506.     Die Unix-Standardisierungsgremien haben sich daher ein Modell fr
  507.     asynchronen I/O ausgedacht, bei dem das
  508.     <computeroutput>read</computeroutput>-トquivalent sofort zurck kehrt
  509.     und man sp舩er ein Signal kriegt, wenn die Operation fertig ist
  510.     (analog natrlich fr <computeroutput>write</computeroutput>).  Das
  511.     Problem ist, da゚ dieses API fast niemand implementiert; Solaris hat
  512.     die Funktionen, die liefern aber alle Fehler zurck (Solaris hat
  513.     auch ein funktionierendes propriet舐es API).  Linux glibc
  514.     implementiert diese Funktionen, indem pro Operation ein Thread
  515.     aufgemacht wird.  Weil das so frchterlich ist, benutzt dieses API
  516.     so gut wie niemand.
  517.     </part>
  518.  
  519.     <part>
  520.     Asynchroner I/O h舩te dabei durchaus Vorzge gegenber normalen
  521.     Lesen.  Wenn man z.B. 1000 Blke aus einer Datenbank lesen will,
  522.     und man liest sie alle sequentiell, dann mu゚ das System sie in der
  523.     gew臧lten Reihenfolge lesen.  Da das User-Space Programm keine
  524.     Ahnung haben kann, wie die Blke physikalisch auf der Platte
  525.     verteilt sind, mu゚ das System unnig die Schreib-Lese-Kfe hin-
  526.     und herbewegen.  Asynchroner I/O hingegen erlaubt dem System, die
  527.     Blke in der effizientesten Reihenfolge zu lesen.  Es gibt Ans舩ze,
  528.     asynchronen I/O auch in Linux anst舅dig zu implementieren.
  529.     </part>
  530.    </sect2>
  531.  
  532.    <sect2>
  533.     <title>Linux 2.4: SIGIO</title>
  534.     <part>Linux 2.4 kann poll-Events auch ber Signals mitteilen.  Die
  535.     Idee ist, da゚ man mit einem <computeroutput>fcntl</computeroutput>
  536.     auf den File Deskriptor dem System mitteilt, da゚ man an
  537.     Status舅derungen interessiert ist, und dann kriegt man immer ein
  538.     Signal, wenn sich an dem Deskriptor etwas tut.  Der Vorteil ist, da゚
  539.     das im System mit O(1) implementierbar ist.  Hier ist Beispiel-Code:
  540.     </part>
  541.  
  542.     <programlisting>
  543.     int sigio_add_fd(int fd) {
  544.       static const int signum=SIGRTMIN+1;
  545.       static pid_t mypid=0;
  546.       if (!mypid) mypid=getpid();
  547.       fcntl(fd,F_SETOWN,mypid);
  548.       fcntl(fd,F_SETSIG,signum);
  549.       fcntl(fd,F_SETFL,fcntl(fd,F_GETFL)|O_NONBLOCK|O_ASYNC);
  550.     }
  551.  
  552.     int sigio_rm_fd(struct sigio* s,int fd) {
  553.       fcntl(fd,F_SETFL,fcntl(fd,F_GETFL)&(~O_ASYNC));
  554.     }
  555.     </programlisting>
  556.  
  557.     <part>
  558.     Signal-Handler bekommen traditionell die Signal-Nummer bergeben.
  559.     Bei Linux gibt es noch ein zweites Argument, eine struct, in der
  560.     u.a. der File Deskriptor drin steht, so da゚ man fr 1000
  561.     Deskriptoren Signals ber den gleichen Handler bearbeiten lassen
  562.     kann.
  563.     </part>
  564.  
  565.     <part>
  566.     Aus Performance-Grnden kann man auf die Zustellung der Signals auch
  567.     verzichten und stattdessen das gew臧lte Signal blocken.  Es ist dann
  568.     trotzdem noch mlich, dieses Signal zu empfangen, indem man in
  569.     einer Schleife <computeroutput>sigtimedwait</computeroutput>
  570.     aufruft.  Da kann brigens man auch noch einen Timeout angeben, was
  571.     das Timeout-Handling vereinfacht.
  572.     </part>
  573.  
  574.     <part>
  575.     <acronym>SIGIO</acronym> ist ein sehr elegantes API, das allerdings ein Umdenken
  576.     erfordert.  Da man nirgendwo sagt, an welchen Events man
  577.     interessiert ist, kriegt man alle Events zugestellt.  Bei
  578.     <computeroutput>select</computeroutput> und
  579.     <computeroutput>poll</computeroutput> ist es so, da゚ man auf ein
  580.     Event auch einfach nicht reagieren kann.  Der n臘hste
  581.     <computeroutput>poll</computeroutput>-Aufruf signalisiert dann das
  582.     selbe Event noch einmal.  So kann man sehr sch fr Fairness
  583.     sorgen und Rate Limiting implementieren.  <acronym>SIGIO</acronym> hingegen teilt nur
  584.     das erste Event mit.  Wenn man darauf nicht reagiert, wird man nie
  585.     wieder auf diesen Deskriptor hingewiesen werden.
  586.     </part>
  587.  
  588.     <part>
  589.     Die Angelegenheit ist sogar noch etwas schwieriger: Wenn man bei
  590.     <computeroutput>poll</computeroutput> ein Lese-Event bekommt,
  591.     <computeroutput>read</computeroutput> aufruft, 100 Bytes liest, und
  592.     wieder <computeroutput>poll</computeroutput> aufruft, funktioniert
  593.     das.  Bei <acronym>SIGIO</acronym> mu゚ man <computeroutput>read</computeroutput> so
  594.     lange aufrufen, bis man explizit
  595.     <computeroutput>EAGAIN</computeroutput> als Fehlermeldung kriegt
  596.     ("sp舩er nochmal probieren").  Diese Fehlermeldung kann
  597.     <computeroutput>read</computeroutput> nur erzeugen, wenn man
  598.     <phrase>non-blocking I/O</phrase> fr diesen File Deskriptor
  599.     aktiviert hat:
  600.     </part>
  601.  
  602.     <programlisting>
  603.     #include <fcntl.h>
  604.  
  605.     fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0) | O_NDELAY);
  606.     </programlisting>
  607.  
  608.     <part>
  609.     Das <computeroutput>poll</computeroutput>-Modell nennt man
  610.     <phrase>level triggered</phrase>, das
  611.     <acronym>SIGIO</acronym>-Modell hei゚t <phrase>edge
  612.     triggered</phrase>.  In der Praxis ist ersteres fr Programmierer
  613.     einfacher zu verstehen.  Das <acronym>SIGIO</acronym>-Modell hat
  614.     aber den Vorteil, da゚ sich das Programm l舅ger am Stck mit der
  615.     gleichen Verbindung besch臟tigt, so da゚ die zugehigen Daten alle
  616.     im Cache sind.  So ist es geringfgig effizienter.  Man kann
  617.     auch mit <computeroutput>poll</computeroutput> arbeiten, als w舐e es
  618.     edge triggered, aber nicht umgekehrt.
  619.     </part>
  620.  
  621.     <programlisting>
  622.     for (;;) {
  623.       timeout.tv_sec=0;
  624.       timeout.tv_nsec=10000;
  625.       switch (r=sigtimedwait(&s.ss,&info,&timeout)) {
  626.       case -1: if (errno!=EAGAIN) error("sigtimedwait");
  627.       case SIGIO: puts("SIGIO!  overflow!"); return 1;
  628.       }
  629.       if (r==signum) handle_io(info.si_fd,info.si_band);
  630.     }
  631.     </programlisting>
  632.  
  633.     <para>
  634.     Aber auch <acronym>SIGIO</acronym> hat Nachteile: der Kernel
  635.     reserviert pro Proze゚ einen Speicherbereich fr die Liste der
  636.     ausstehenden Events.  Wenn dieser Speicherbereich voll ist, wrden
  637.     Events verloren gehen.  Der Kernel signalisiert daher dann mit SIGIO
  638.     (mit dem Signal namens SIGIO, nicht dem Konzept), da゚ die Queue
  639.     vollgelaufen ist.  Das Programm mu゚ dann die Queue manuell leeren,
  640.     indem fr den Signal Handler kurzzeitig
  641.     <computeroutput>SIG_DFL</computeroutput> eingetragen wird, und sich
  642.     die Events mit <computeroutput>poll</computeroutput> abholen.
  643.     </para>
  644.  
  645.     <para>
  646.     In der Praxis hei゚t das, da゚ man bei SIGIO eben doch noch ein Array
  647.     mit <computeroutput>struct pollfd</computeroutput> verwalten mu゚; das
  648.     mhte man sich ja aber eigentlich gerade sparen, deshalb benutzt
  649.     man ja SIGIO!  Das klingt vielleicht auf den ersten Blick nicht nach
  650.     Arbeit, aber pollfds mssen kontinuierlich hintereinander liegen;
  651.     man kann nicht in der Mitte des Arrays einen leeren Eintrag haben,
  652.     sonst bricht <computeroutput>poll</computeroutput> mit
  653.     <computeroutput>EBADFD</computeroutput> ab.  Wenn mein Server also
  654.     viele Verbindungen offen hat, und in der Mitte wird eine
  655.     geschlossen, mu゚ ich mein Array anpassen.  Das hei゚t aber auch, da゚
  656.     ich den zugehigen Eintrag im Array nicht einfach finden kann,
  657.     indem ich den Deskriptor als Index nehme!  Daraus folgt, da゚ man
  658.     O(1)-Finden des zugehigen Eintrags nur ber eine Indirektion
  659.     mit einem zweiten Array implementieren kann, und der Code wird dann
  660.     entsprechend unbersichtlich.
  661.     </para>
  662.  
  663.     <para>
  664.     Es gibt noch einen Nachteil von SIGIO: man hat einen Syscall pro
  665.     mitgeteiltem Event.  Bei poll ist zwar die Abarbeitung des Syscalls
  666.     teuer, aber dafr hat man da nicht viele von.  Unter Linux ist der
  667.     Aufruf eines Syscalls sehr effizient gelt, daher mu゚ man sich da
  668.     nicht viel Sorgen machen; die kommerziellen Unixen haben da
  669.     teilweise mehr als eine Grenordnung grere Latenzen.  Aber
  670.     unabh舅gig davon, ob das jetzt sehr oder nur m葹ig viel
  671.     verschwendete Leistung ist, man kann sich auch ein API vorstellen,
  672.     bei dem man mehr als ein Event auf einmal gemeldet bekommen kann.
  673.     </para>
  674.    </sect2>
  675.    <sect2>
  676.     <title>Solaris: /dev/poll</title>
  677.     <para>
  678.     Bei Solaris kann man seit zwei-drei Jahren poll beschleunigen, indem
  679.     man das Device <computeroutput>/dev/poll</computeroutput> fnet,
  680.     dort sein <computeroutput>pollfd</computeroutput>-Array hinein
  681.     schreibt, und dann mit einem <computeroutput>ioctl</computeroutput>
  682.     auf Events wartet.
  683.     </para>
  684.  
  685.     <para>
  686.     Der <computeroutput>ioctl</computeroutput> sagt einem, wie viele
  687.     Events anliegen, und so viele <computeroutput>struct
  688.     pollfd</computeroutput> liest man dann von dem Device.  Man hat
  689.     also nur fr die Deskriptoren Aufwand, bei denen auch tats臘hlich
  690.     Events anliegen.  Das spart sowohl im Kernel als auch im User-Space
  691.     gewaltig Aufwand und ist auch mit minimalem Aufwand auf bestehende
  692.     Programme portierbar.
  693.     </para>
  694.  
  695.     <para>
  696.     Es gab Ans舩ze, ein solches Device auch unter Linux zu
  697.     implementieren.  Keiner von ihnen hat es in den Standard-Kernel
  698.     geschafft, und die Patches verhielten sich unter hoher Last
  699.     teilweise undeterministisch, daher ist dieses API auf Solaris
  700.     beschr舅kt.
  701.     </para>
  702.    </sect2>
  703.    <sect2>
  704.     <title>Linux: epoll</title>
  705.  
  706.     <para>
  707.     Fr Linux 2.4 gibt es einen Patch, der
  708.     <computeroutput>/dev/epoll</computeroutput> implementiert.  Die
  709.     Anwendung entspricht weitgehend der Solaris-Variante:
  710.     </para>
  711.  
  712.     <programlisting>
  713.     int epollfd=open("/dev/misc/eventpoll",O_RDWR);
  714.     char* map;
  715.     ioctl(epollfd,EP_ALLOC,maxfds); /* Hint: Anzahl der Deskriptoren */
  716.     map=mmap(0, EP_MAP_SIZE(maxfds), PROT_READ, MAP_PRIVATE, epollfd, 0);
  717.     </programlisting>
  718.  
  719.     <para>
  720.     Man fgt ein Event an, indem man auf das Device einen <computeroutput>pollfd</computeroutput> schreibt.
  721.     Man lcht ein Event, indem man auf das Device einen
  722.     <computeroutput>pollfd</computeroutput> mit
  723.     <computeroutput>events==0</computeroutput> schreibt.  Wie bei Solaris holt man die Events mit einem
  724.     <computeroutput>ioctl</computeroutput> ab:
  725.     </para>
  726.  
  727.     <programlisting>
  728.     struct evpoll evp;
  729.     for (;;) {
  730.       int n;
  731.       evp.ep_timeout=1000;
  732.       evp.ep_resoff=0;
  733.       n=ioctl(e.fd,EP_POLL,&evp);
  734.       pfds=(struct pollfd*)(e.map+evp.ep_resoff);
  735.       /* jetzt hat man n pollfds mit Events in pfds */
  736.     }
  737.     </programlisting>
  738.  
  739.     <para>
  740.     Dank <computeroutput>mmap</computeroutput> findet hier berhaupt
  741.     kein Kopieren zwischen Kernel und User-Space statt.  Dieses API hat
  742.     es nie in den Standard-Kernel geschafft, weil Linus
  743.     <computeroutput>ioctl</computeroutput> nicht mag.  Seine Begrndung
  744.     ist, da゚ das ein Dispatcher sei, und man habe ber die
  745.     Syscall-Nummer ja schon einen Dispatcher, und daher solle man doch
  746.     bitte den benutzen.  Der Autor von epoll hat die Patches daraufhin
  747.     noch einmal mit Syscalls implementiert, und diese API ist seit
  748.     2.5.51 in der dokumentierten Form im Standard-Kernel enthalten.
  749.     </para>
  750.  
  751.     <programlisting>
  752.     int epollfd=epoll_create(maxfds);
  753.     struct epoll_event x;
  754.     x.events=EPOLLIN|EPOLLERR;
  755.     x.data.ptr=whatever; /* hier tr臠t man sich einen Cookie ein */
  756.     epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&x);
  757.     /* 舅dern ist analog */
  758.     epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&x);
  759.     /* lchen ist analog, nur fd mu゚ eingetragen sein */
  760.     epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,&x);
  761.     </programlisting>
  762.  
  763.     <para>
  764.     Die EPOLLIN, ... Konstanten sind im Moment identisch mit POLLIN, aber
  765.     der Autor wollte sich da alle Optionen offen halten.  Das epoll-API
  766.     sagt einem nicht automatisch den Deskriptor zu dem Event.  Wenn man
  767.     das mhte, mu゚ man den Deskriptor als Cookie eintragen.  Der Cookie
  768.     ist ein 64-bit Wert, hier kann man also auch einen Zeiger auf eine
  769.     Datenstruktur eintragen, in der dann neben anderen Daten auch der
  770.     Deskriptor steht.  Seine Events holt man sich dann mit
  771.     <computeroutput>epoll_wait</computeroutput> ab:
  772.     </para>
  773.  
  774.     <programlisting>
  775.     for (;;) {
  776.       struct epoll_event x[100];
  777.       int n=epoll_wait(epollfd,x,100,1000); /* 1000 Millisekunden */
  778.       /* x[0] .. x[n-1] sind die Events */
  779.     }
  780.     </programlisting>
  781.    </sect2>
  782.  
  783.    <sect2>
  784.     <title>Windows: Completion Ports</title>
  785.  
  786.     <para>Auch Microsoft mu゚ skalierbare Netzwerk-Server anbieten
  787.     knen.  Die Microsoft-Lung hierfr ist, da゚ man einen Thread Pool
  788.     aufmachen, und dann in jedem Thread mit einem SIGIO-臧nlichen
  789.     Verfahren die Events benachrichtigt bekommt.
  790.     </para>
  791.  
  792.     <para>
  793.     Dieses Verfahren verbindet die Nachteile von Threads mit den
  794.     Nachteilen von SIGIO.  Leider gibt es unter Windows keine
  795.     Alternativen.  Fr die Neugierigen:
  796.     <computeroutput>select()</computeroutput> wird unter Windows ber ein
  797.     leeres Off-Screen Fenster implementiert, das dann ber den Fenster
  798.     Input-Event Mechanismus die Netzwerk-Events mitgeteilt bekommt.
  799.     </para>
  800.    </sect2>
  801.  
  802.    <sect2>
  803.     <title>FreeBSD: kqueue</title>
  804.  
  805.     <para>
  806.     kqueue ist eine Kreuzung aus epoll und
  807.     <computeroutput>/dev/poll</computeroutput>.  Man kann sich
  808.     aussuchen, ob man nach level oder egde getriggert werden will.
  809.     Au゚erdem hat man noch eine Art SIGIO und File und Directory Status
  810.     Notification eingebaut.  Dabei ist das API leider ziemlich
  811.     unbersichtlich geworden.  Von der Performance her ist kqueue mit
  812.     epoll vergleichbar.
  813.     </para>
  814.    </sect2>
  815.   </sect1>
  816.   <sect1>
  817.    <title>Sonstige Tricks zur Performance-Steigerung</title>
  818.  
  819.    <sect2>
  820.     <title>writev, TCP_CORK und TCP_NOPUSH</title>
  821.  
  822.     <para>
  823.     Ein HTTP-Server schreibt erst einen (kleinen) Antwort-Header und
  824.     dann die Datei in den Socket.  Wenn er fr beides einfach
  825.     <computeroutput>write()</computeroutput> aufruft, erzeugt das ein
  826.     kleines TCP-Paket fr den Header, und danach noch ein TCP-Paket fr
  827.     die Daten.  Wenn man nur eine kleine Datei per HTTP bertragen will,
  828.     h舩te beides vielleicht in ein Paket gepa゚t.
  829.     </para>
  830.  
  831.     <para>
  832.     Offensichtlich kann man einen Puffer nehmen, und in diesen erst den
  833.     Header und dann den Dateiinhalt hineinkopieren, und dann den Puffer
  834.     schreiben.  Das ist aber Verschwendung von RAM-Bandbreite.  Es gibt
  835.     drei andere Lungen: <computeroutput>writev()</computeroutput>,
  836.     <computeroutput>TCP_CORK</computeroutput> (Linux) und
  837.     <computeroutput>TCP_NOPUSH</computeroutput> (BSD).
  838.     </para>
  839.  
  840.     <para>
  841.     <computeroutput>writev()</computeroutput> ist wie ein
  842.     Batch-<computeroutput>write()</computeroutput>.  Man bergibt ein
  843.     Array mit Puffern, <computeroutput>writev()</computeroutput>
  844.     schreibt sie alle hintereinander.  Der Unterschied ist normalerweise
  845.     nicht me゚bar, aber bei TCP-Servern macht er sich bemerktbar.
  846.     </para>
  847.  
  848.     <programlisting>
  849.     struct iovec x[2];
  850.     x[0].iov_base=header; x[0].iov_len=strlen(header);
  851.     x[1].iov_base=mapped_file; x[1].iov_len=file_size;
  852.     writev(sock,x,2); /* returns bytes written */
  853.     </programlisting>
  854.  
  855.     <para>
  856.     <computeroutput>TCP_CORK</computeroutput> ist eine Socket-Option,
  857.     die man mit <computeroutput>setsockopt</computeroutput> vor dem
  858.     Schreiben setzt und nach dem letzten Schreiben wieder zurck setzt.
  859.     Das sagt dem Kernel metaphorisch, da゚ man die TCP-Flasche verkorkt
  860.     h舁t, und erst am Ende lt man den Korken und alles sprudelt auf
  861.     einmal ber das Netz hinaus.
  862.     </para>
  863.  
  864.     <programlisting>
  865.     int null=0, eins=1;
  866.  
  867.     /* Korken reinstecken */
  868.     setsockopt(sock,IPPROTO_TCP,TCP_CORK,&eins,sizeof(eins));
  869.     write(sock,header,strlen(header));
  870.     write(sock,mapped_file,file_size);
  871.     /* Korken ziehen */
  872.     setsockopt(sock,IPPROTO_TCP,TCP_CORK,&null,sizeof(null));
  873.     </programlisting>
  874.  
  875.     <para>
  876.     <computeroutput>TCP_NOPUSH</computeroutput> ist das BSD-トquivalent
  877.     zu <computeroutput>TCP_CORK</computeroutput>.  Der einzige
  878.     Unterschied ist, da゚ man <computeroutput>TCP_NOPUSH</computeroutput>
  879.     vor dem letzten Schreiben zurck setzen mu゚, nicht danach.
  880.     </para>
  881.    </sect2>
  882.    <sect2>
  883.     <title>sendfile</title>
  884.  
  885.     <para>
  886.     Typische hochskalierbare Server lesen immer Daten aus Dateien
  887.     und schreiben sie ber das Netzwerk hinaus.  Dabei h舁t der
  888.     Server-Proze゚ einen Puffer vor, in den er die Daten aus der Datei
  889.     liest, und den er dann ber das Netz schreibt.  Hier werden also
  890.     Daten unnig kopiert.  Es w舐e effizienter, wenn man die Datei
  891.     direkt versenden knte.
  892.     </para>
  893.  
  894.     <para>
  895.     Das haben sich auch diverse Unix-Hersteller gedacht, und Microsoft
  896.     hat einen solchen Syscall auch in Windows eingebaut.  Die meisten
  897.     Server-Netzwerkkarten (alle Gigabit-Karten) beherrschen
  898.     Scatter-Gather I/O, sozusagen
  899.     <computeroutput>writev</computeroutput> in Hardware.  Das
  900.     Betriebssystem kann dann die IP, TCP und Ethernet-Header in einem
  901.     Puffer zusammenbauen, und die Netzwerkkarte holt sich den Inhalt des
  902.     Pakets direkt aus dem Buffer Cache.  Dieses Zusammenspiel nennt man
  903.     <phrase>zero copy TCP</phrase> und es ist sozusagen der heilige Gral
  904.     der Netzwerkprogrammierung.
  905.     </para>
  906.  
  907.     <para>
  908.     Man kann die Daten aus dem Buffer Cache auch per
  909.     <computeroutput>mmap</computeroutput> direkt in den User-Space
  910.     Speicher einblenden, ohne da゚ Daten kopiert werden m゚ten.  Leider
  911.     kann Linux bei einem <computeroutput>write()</computeroutput> auf
  912.     einen solchen Speicherbereich kein zero copy TCP initiieren, dazu
  913.     mu゚ man <computeroutput>sendfile</computeroutput> benutzen.
  914.     Trotzdem macht es Sinn, alle Daten wenn mlich mit
  915.     <computeroutput>mmap</computeroutput> statt
  916.     <computeroutput>read</computeroutput> zu lesen, weil man so im
  917.     System Speicherplatz spart, den das System dann zum Cachen verwenden
  918.     kann.
  919.     </para>
  920.  
  921.     <para>
  922.     Das gro゚e Problem bei <computeroutput>sendfile()</computeroutput> ist,
  923.     da゚ es jeder Anbieter mit einer anderen Semantik und anderen
  924.     Argumenten implementiert hat.  Nicht einmal die freien Unixe haben
  925.     sich an das gleiche Format gehalten.
  926.     </para>
  927.     </sect2>
  928.     <sect2>
  929.      <title>Sonstige Performance-Tweaks</title>
  930.  
  931.      <para>
  932.      Auf heutiger Hardware ist <computeroutput>fork()</computeroutput>
  933.      sehr effizient implementiert.  Die Datenbereiche der Prozesse
  934.      werden nicht mehr kopiert, sondern es werden nur noch die
  935.      Page-Mappings bernommen.  Das ist Grenordnungen effizienter als
  936.      frher, aber bei einem Server-Proze゚ mit 20000 gemappten Dateien
  937.      kann es sich schon um einen me゚baren Zeitraum handeln.  Es macht
  938.      daher auch heute noch Sinn, in seinen Servern (z.B. fr den Aufruf
  939.      von CGIs) <computeroutput>vfork()</computeroutput> vorzuziehen.
  940.      </para>
  941.  
  942.      <para>
  943.      Bei Servern mit sehr vielen offenen Deskriptoren kann
  944.      <computeroutput>open()</computeroutput> plzlich me゚bar langsamer
  945.      werden.  Das liegt daran, da゚ POSIX vorschreibt, da゚ immer der
  946.      kleinste unbelegte Deskriptor genommen werden mu゚.  Das
  947.      implementiert der Kernel, indem er Bitfelder linear durchgeht.  Es
  948.      ist also theoretisch denkbar, da゚ man ein paar Nanosekunden sparen
  949.      kann, wenn man die untersten Dateideskriptoren mit
  950.      <computeroutput>dup2</computeroutput> frei h舁t.  Diese Idee
  951.      geistert seit Jahren ber diverse Mailinglisten; mir ist aber kein
  952.      Fall bekannt, wo das tats臘hlich mal experimentell nachgewiesen
  953.      worden ist.
  954.      </para>
  955.  
  956.      <para>
  957.      Auf neueren x86-Prozessoren gibt es neben dem
  958.      Standard-Syscall-Mechanismus ber Software Interrupt 80 auch noch
  959.      einen anderen Syscall-Mechanismus.  Der Linux Kernel hat relativ
  960.      frischen Support dafr, indem er neben dem Programm im User Space
  961.      noch eine weitere Code Page mit dem neuen Syscall einblendet.  Die
  962.      Adresse der Page wird Programmen ber den ELF AUX Vektor bergeben
  963.      (Daten auf dem Stack hinter <computeroutput>argv</computeroutput>
  964.      und dem Environment).  Meinen Messungen zufolge reduziert sich
  965.      damit die Syscall-Latenz auf die H舁fte.  In den HTTP-Benchmarks
  966.      war die Auswirkung aber mit 2-5% nur knapp ber der statistischen
  967.      Rauschgrenze.
  968.      </para>
  969.     </sect2>
  970.   </sect1>
  971. </article>
  972.