Weil wir es gerade von /proc und /sys hatten... (3)

Es ist mal wieder Zeit für doofe Ideen mit dem /proc-Filesystem. Heute wollen wir uns eine Fragestellung widmen, die den einen oder anderen bestimmt schonmal beschäftigt hat: Es sei ein Programm gegeben und wir wollen “Pi mal Daumen” herausfinden, ob das Programm eher I/O- oder eher CPU-beschränkt ist. Jetzt kann man das Programm natürlich einfach starten und sich anschauen, wie sich der Server verhält, aber gehen wir mal davon aus, daß uns diese Option nicht wirklich offen steht, z.B. weil noch viele andere Dinge auf der Kiste laufen und wir deshalb keine echte Vergleichsbasis haben. Was tun wir also?

Nun, im /proc-Filesystem gibt es für jede PID eine Pseudo-Datei mit Namen status. In dieser vermerkt der Kernel unter anderem die Anzahl der Context Switches, die dieser Prozess mitgemacht hat. Dabei wird zwischen voluntary und nonvoluntary unterschieden. Ersteres tritt meistens dann auf, wenn ein Prozess auf I/O wartet - er gibt dann quasi freiwillig die CPU ab und wird vom Kernel - vereinfacht gesprochen - weiter abgearbeitet, sobald die Daten, die er zum Beispiel von der Platte, vom Netzwerk etc. lesen wollte, zur Verfügung stehen. Im Unterschied dazu wird ein Prozess, der die ganze Zeit Berechnungen auf der CPU ausführt, unfreiwillig unterbrochen. Durch einen Vergleich der beiden Counter können wir also ungefähr bestimmen, was ein Prozess an Resourcen braucht.

Zur Demonstration sehen wir uns zunächst das folgende C-Programm an:

$ cat eatcpu.c
int main() {
  while (1) {
  }
}
$ gcc -o eatcpu eatcpu.c
$ set +H
$ ./eatcpu &
[1] 4580
$ cat /proc/$!/status | grep ctxt_switches
voluntary_ctxt_switches:  0
nonvoluntary_ctxt_switches: 1362
$ fg
./eatcpu
^C
$

Wie wir erwartet haben sehen wir hier eigentlich nur unfreiwillige Kontextwechsel. Das set +H steht da übrigens, da wir sonst statt $! ein (evt. nicht existierendes) bash-Event ansprechen würden. Als zweites Beispiel sehen wir uns dieses Stück Code an:

$ cat slow-reader.c
#include <stdio.h>
#include <errno.h>

int main(int argc, char* argv[]) {
  FILE * readfile;
  readfile = fopen(argv[1], "r");
  if (readfile == NULL) {
    perror("Unable to open file");
    return errno;
  }

  int ch;
  while (1) {
    ch = fgetc(readfile);
    if (ch == EOF)
      break;
  }
  fclose(readfile);
  return 0;
}

Das Programm liest ein einzelnes Zeichen aus einer Datei, dann das nächste usw. Hier würden wir eine deutlich höhere Anzahl an freiwilligen Kontext-Wechseln erwarten. Und tatsächlich:

$ gcc -o slow-reader slow-reader.c
$ ./slow-reader /tmp/bigfile &
[1] 4669
$ cat /proc/$!/status | grep ctxt_switches
voluntary_ctxt_switches:3454
nonvoluntary_ctxt_switches:821

Obwohl wir auch hier eine große Zahl unfreiwilliger Kontextwechsel sehen, so sind die Mehrzahl der Wechsel doch freiwlliger Natur und das Programm damit eher I/O-beschränkt.

Draußen geht die Welt im Regen unter, für mich wird es damit Zeit, mich Ravels Bolero zuzuwenden. Euch noch einen schönen Sonntag!