Virtual machines

We all know that Java uses a „virtual machine“ that is it simulates a non-existing hardware which is the same independent of the real hardware, thus helping to achieve the well known platform independence of Java. Btw. this is not about virtualization like VMWare, VirtualBox, Qemu, Xen, Docker and similar tools, but about byte code interpreters like the Java-VM.

We tend to believe that this is the major innovation of Java, but actually the concept of virtual machines is very old. Lisp, UCSD-Pascal, Eumel/Elan, Perl and many other systems have used this concept long before Java. The Java guys have been good in selling this and it was possible to get this really to the mainstream when Java came out. The Java guys deserve the credit for bringing this in the right time and bringing it to the main stream.

Earlier implementations where kind of cool, but the virtual machine technology and the hardware were to slow, so that they were not really attractive, at least not for high performance applications, which are now actually a domain of Java and other JVM languages. Some suggest that Java or other efficient JVM languages like Scala would run even faster than C++. While it may be true to show this in examples, and the hotspot optimization gives some theoretical evidence how optimization that takes place during run time can be better than static optimization at compile time, I do not generally trust this. I doubt that well written C-code for an application that is adequate for both C and Java will be outperformed by Java. But we have to take two more aspects into account, which tend to be considered kind of unlimited for many such comparisons to make them possible at all.

The JVM has two weaknesses in terms of performance. The start-up time is relatively long. This is addressed in those comparisons, because the claim to be fast is only maintained for long running server applications, where start-up time is not relevant. The hotspot optimization requires anyway a long running application in order to show its advantages. Another aspect that is very relevant is that Java uses a lot of memory. I do not really know why, because more high level languages like Perl or Ruby get along with less memory, but experience shows that this is true. So if we have a budget X to buy hardware and then put software written in C on it, we can just afford to buy more CPUs because we save on the memory or we can make use of the memory that the JVM would otherwise just use up to make our application faster. When we view the achievable performance with a given hardware budget, I am quite sure that well written C outperforms well written Java.

The other aspect is in favor of Java. We have implicitly assumed until now that the budget for development is unlimited. In practice that is not the case. While we fight with interesting, but time consuming low level issues in C, we already get work done in Java. A useful application in Java is usually finished faster than in C, again if it is in a domain that can reasonably be addressed with either of the two languages and if we do not get lost in the framework world. So if the Java application is good enough in terms of performance, which it often is, even for very performance critical applications, then we might be better off using Java instead of C to get the job done faster and to have time for optimization, documentation, testing, unit testing.. Yes, I am in a perfect world now, but we should always aim for that. You could argue that the same argument is valid in terms of using a more high-level language than Java, like Ruby, Perl, Perl 6, Clojure, Scala, F#,… I’ll leave this argument to other articles in the future and in the past.

What Java has really been good at is bringing the VM technology to a level that allows real world high performance server application and bringing it to the main stream.
That is already a great achievement. Interestingly there have never been serious and successful efforts to actually build the JavaVM as hardware CPU and put that as a co-processor into common PCs or servers. It would have been an issue with the upgrade to Java8, because that was an incompatible change, but other than that the JavaVM remained pretty stable. As we see the hotspot optimization is now so good that the urge for such a hardware is not so strong.

Now the JVM has been built around the Java language, which was quite legitimate, because that was the only goal in the beginning. It is even started using the command line tool java (or sometimes javaw on MS-Windows 32/64 systems). The success of Java made the JVM wide spread and efficient, so it became attractive to run other languages on it. There are more than 100 languages on the JVM. Most of them are not very relevant. A couple of them are part of the Java world, because they are or used to be specific micro languages closely related to java to achieve certain goals in the JEE-world, like the now almost obsolete JSP, JavaFX, .

Relevant languages are Scala, Clojure, JRuby, Groovy and JavaScript. I am not sure about Jython, Ceylon and Kotlin. There are interesting ideas coming up here and there like running Haskell under the name Frege on the JVM. And I would love to see a language that just adds operator overloading and provides some preprocessor to achieve this by translating for example „(+)“ in infix syntax to „.add(..)“ mainstream, to allow seriously using numeric types in Java.

Now Perl 6 started its development around 2000. They were at that time assuming that the JVM is not a good target for a dynamic language to achieve good performance. So they started developing Parrot as their own VM. The goal was to share Parrot between many dynamic languages like Ruby, Python, Scheme and Perl 6, which would have allowed inter-language inter-operation to be more easily achievable and using libraries from one of these languages in one of the others. I would not have been trivial, because I am quite sure that we would have come across issues that each language has another set of basic types, so strings and numbers would have to be converted to the strings and numbers of the library language when calling, but it would have been interesting.

In the end parrot was a very interesting project, theoretically very sound and it looked like for example the Ruby guys went for it even faster than the the Perl guys, resulting in an implementation called cardinal. But the relevant Perl 6 implementation, rakudo, eventually went for their own VM, Moar. Ruby also did itself a new better VM- Many other language, including Ruby and JavaScript also went for the JVM, at least as one implementation variant. Eventually the JVM proved to be successful even in this area. The argument to start parrot in the first place was that the JVM is not good for dynamic languages. I believe that this was true around 2000. But the JVM has vastly improved since then, even resulting in Java being a serious alternative to C for many high performance server applications. And it has been improved for dynamic languages, mostly by adding the „invoke_dynamic“-feature, that also proved to be useful for implementing Java 8 lambdas. The experience in transforming and executing dynamic languages to the JVM has grown. So in the end parrot has become kind of obsolete and seems to be maintained, but hardly used for any mainstream projects. In the end we have Perl 6 now and Parrot was an important stepping stone on this path, even if it becomes obsolete. The question of interoperability between different scripting languages remains interesting…

Share Button

Auf GPU rechnen

Heutige Rechner haben bekanntlich sehr leistungsfähige CPUs mit „Quadcore“ und mehr.
Aber auch die Grafikkarten sind ziemlich toll geworden und haben zum Teil auch so große eigene Lüfter wie die CPU.
Sie haben eine Menge eigenes Memory und sie können recht komplizierte Operationen selbsttätig rechnen, natürlich nur für die grafische Darstellung.

Nun stellt sich die Frage, ob man das auch sonst zum Rechnen benutzen kann. Man hört Geschichten, dass die Grafikkarte um Größenordnungen leistungsfähiger als die CPU sei. Warum schaffen das die Grafikkartenhersteller so leicht nebenbei, besser als die CPU in deren Kernaufgabe zu sein und außerdem noch Grafik darzustellen?

Grafikkarten bieten die Möglichkeit, massiv parallel zu rechnen, aber nach dem Prinzip SIMD. Das heißt, dass ein oder sehr wenige Befehlsströme verarbeitet werden und eine große Zahl von Recheneinheiten jeweils den identischen Code mit verschiedenen Daten ausführt. Für Vektoroperationen ist das optimal. Und es erklärt auch die brachiale Leistung der Grafikchips, weil man den aufwändigen Teil, Maschinencode zu interpretieren, nur einmal (oder wenige Male) baut und nur die eigentliche Recheneinheit mehrfach. Man bekommt so mit derselben Siliziummenge mehr Rechenleistung, aber mit der Einschränkung, SIMD (oder fast SIMD) zu sein. Aber man hat diese Geräte in den Rechnern drin. Es gibt sogar „Grafikkarten“, die nur rechnen können, aber bei denen man die eigentlichen Grafikchips weggelassen hat. Und viele Rechner haben den Grafikkern mit auf dem selben Chip wie die CPU-Kerne, so dass man eine einfache Grafik zur Verfügung hat, aber man baut dann doch zusätzlich noch eine externe Grafikkarte ein, die noch besser ist. Selbst ohne die Grafikfähigkeit zu gefährden hat man also eine GPU zum Rechnen übrig.

Nun ist das Programmieren dieser Geräte nicht so einfach. Grundsätzlich muss man sich mit einem völlig neuen Programmiermodell herumschlagen. Viele Algorithmen lassen sich speziell für SIMD entwickeln, aber sie sind anders als was wir gewohnt sind und automatisch lässt sich da nur beschränkt etwas optimieren. In der Regel muss man also mit C das Programm schreiben und kann dabei auf OpenCL bauen. Das ist ein nützlicher Standard, der einiges erleichtert, aber es bleibt schwierig. Für manche Zwecke lohnt es sich aber, den Entwicklungsaufwand zu treiben.

Vor ein paar Tagen habe ich drei Vorträge über Ansätze gehört, F# zu verwenden, um die GPU zu programmieren. Man kann also unter Einhaltung gewisser Regeln F#-Programme schreiben, die sich etwa in OpenCL übersetzen lassen und dann compiliert die GPU nutzen:

  • FSCL: Homogeneous Programming and Execution on Heterogeneous Platforms (by Gabriele Cocco, Pisa University)
  • Professional GPU Development with F# and the Alea Compiler Suite (by Daniel Egloff, QuantAlea)
  • New Abstractions for Radically Simplified GPU Programming in F# and .NET (by Luc Bläser, HSR, based on joint work with QuantAlea)

All diese Ansätze sollten schon heute oder zumindest in zukünftigen Implementierungen auch für Mono laufen und damit für Linux zur Verfügung stehen.

Es würde wohl den Rahmen dieses Artikels sprengen, im Einzelnen darauf einzugehen, aber vielleicht kann ich noch Links bekommen, die auf weiterführendes Material zu den einzelnen Lösungsansätzen führen.

Share Button

Non-Blocking I/O

In Posix-Systemen (Linux, Unix, MacOS X,…) basieren die I/O-Operationen hauptsächlich auf den Systemaufrufen read(..) und write(..). Die meisten anderen I/O-Operationen lassen sich darauf zurückführen und auch I/O von anderen Programmiersprachen als C dürfte letztlich indirekt zu read() und write() führen. read() ist eine Funktion, die einen (numerischen) Filedeskriptor, einen Pointer auf einen hinreichend großen Speicherbereich („Buffer“) und eine Größenangabe annimmt und im Idealfall die angegebene Anzahl von Bytes liest. Das funktioniert gemäß dem Unix-Prinzip „everything is a file“ ganz einfach mit Dateien (Files), Geräten (Devices), (Pseudo-)terminals, Pipes (Fifos), TCP-Streams etc. Wenn ein Fehler auftritt, wird ein negativer Wert zurückgegeben und man muss errno abfragen, um den Fehler zu finden. Den negativen Fehlercode direkt zurückzugeben wäre besser gewesen, aber das kann man nun nicht mehr ändern. Eine 0 bedeutet, dass man das Ende erreicht hat. Dies wird nicht durch den Inhalt der übertragenen Daten ausgedrückt, denn jede beliebige Bytesquenz wird akzeptiert und verarbeitet, sondern separat übermittel. Wenn weniger als die gewünschten Bytes da sind, dann wird entsprechend weniger zurückgegeben. Der Rückgabewert gibt an, wieviele Bytes tatsächlich gelesen wurdn. Das sind alles Situationen, in denen das read sofort anfangen kann, auch wenn die eigentliche Operation etwas länger dauern kann. Bei Dateien kann der Fall, dass 0 Bytes gelesen werden, nur am Dateiende und natürlich bei Fehlern auftreten. Bei Pipes und TCP-Streams ist aber möglich, dass man auf Daten wartet und keine da sind. Mit 0 Bytes gibt sich das read normalerweise nicht zufrieden und wwartet stattdessen auf Daten. Das ist eine vernünftige Sache, da es in der Regel das ist, was man will. So vereinfacht sich die Programmierung. Write verhält sich entsprechend, bei Pipes kann man auch nur schreiben, wenn auf der Gegenseite die Daten abgenommen werden. Ein Prinzip das heute in der Scala- und Akka-Welt als die größte Innovation des Jahres gerühmt wird und den Namen „Backpressure“ bekommen hat.

Non-Blocking funktioniert auch mit read(). Mit open() oder nachträglich mit fcntl() wird eingestellt, dass das I/O non-blocking ist. read() versucht nun, Bytes zu lesen und wenn das klappt ist alles wie beim blocking I/O. Wenn das read() keine Daten findet, wartet es nicht auf Daten, sondern kommt sofort mit -1 zurück und in errno steht EAGAIN oder EWOULDBLOCK o.ä. write() entsprechend.

Nun hätte man die Möglichkeit, regelmäßig einen Filedescriptor abzufragen und wenn Daten kommen, darauf zu reagieren und sonst etwas anderes zu tun. Ohne Multithreading…

Interessant ist der Fall, dass man mehrere Filedescriptoren gleichzeitig hat und von diesen Daten lesen oder schreiben will. Man weiß nicht wann welche Zugriffe möglich sind. Das wäre mit einem Thread pro Filedescriptor machbar. Oder mit non-Blocking-I/O und Polling, also einer Schleife, die dauernd alle Filedescriptoren durchprobiert.. Besser ist es aber select() oder pselect() oder poll() zu verwenden, die semantisch sehr ähnlich funktionieren, aber aus historischen Gründen koexistieren. Wer eigenen Code schreibt, kann sich eine der drei Funktionen aussuchen und damit arbeiten, wer aber Code von anderen Leuten lesen und ändern muss, sollte alle drei kennen. Dies ist eine sehr elegante Lösung, weil man noch ein Timeout dazunehmen kann. Die Spezialfälle 0 und unendlich für das Timeout werden auch sinnvoll unterstützt.

Das „non-Blocking-Prinzip“ wird bei der Posix-Systemprogrammierung oft angeboten, z.B. beim Locken von Mutexen, Semaphoren oder beim File-Locking.

Man sollte non-blocking-I/O nicht mit assynchronem I/O verwechseln. Dazu kommt vielleicht ein anderer Artikel..

Share Button

Closures in C

English

Geht so etwas überhaupt?

Ein Element sind die Funktionspointer. Es ist immer recht schwierig, die Signatur davon richtig zu treffen, aber ein typedef hilft.

Die nächste Schwierigkeit ist, dass C normalerweise keine inneren Funktionen erlaubt und dass man auch keinen Kontext einbinden kann.

Das lässt sich lösen:

Die Funktion hat einen weiteren Parameter für ein Context-Struct, in dem die Variablen eingebunden werden.

struct closure;
typedef int (*fun_type)(const struct closure *context, const int param);

Das sieht so aus, dass dort die Variable(n) und ein Funktionspointer enthalten sind:

struct closure {
  int x;
  fun_type fun;
};

Nun muss man die Funktionen ja in für Closures gemachten Programmiersprachen trotzem noch hinschreiben, aber anonym und am richtigen Ort.
In C muss man alle möglichen Funktionen regulär, aber mit der fun_type-Signatur von oben definieren. Die Referenzen auf die eingebundenen Variablen müsse vom context-Parameter kommen, statt implizit verfügbar zu bleiben, z.B.:

int f(const struct closure *context, const int param) {
  return (context->x) + param;
}

Die Funktion 2. Ordnung, die die Closure zurückgibt, kann man nun auch definieren, man muss nur die C-Schreibweise akzeptieren und statt die Funktion an Ort und Stelle zu definieren eine der vorher definierten Funktionen referenzieren. Auch das ist äquivalent zu Closures:

struct closure *adder(int x) {
  struct closure *result = malloc(sizeof(struct closure));
  result->x = x;
  result->fun = f;
  return result;
}

Und so kann man das ganze dann verwenden:

int main(int argc, char *argv[]) {
  int retcode;
  if (argc < 2) {     usage(argv[0], "not enough parameters");   }   int x = atoi(argv[1]);   int y = atoi(argv[2]);   struct closure *cl = adder(x);   int i;   for (i = 0; i < y; i++) {     printf("cl(%d)=%d\n", i, cl->fun(cl, i));
  }
}

Das komplette Beispiel ist auf github.

In Scala sieht das natürlich viel kürzer aus:

object Closure {
  def main(args : Array[String]) : Unit = {
    val x : Int = args(0).toInt
    val y : Int = args(1).toInt
    val f : ((Int) => Int) = adder(x);
    val arr = (1 to y).map(f)
    println(arr.toString)
  }

  def adder(x : Int) : ((Int) => Int) = {
    (y => x+y)
  }
}

Auch das ist auf Github

Share Button

Dämonisierung von Prozessen

Auf Unix- und Linux-artigen Systemen laufen immer einige sogenannte Daemon-Prozesse. Diese laufen im Hintergrund, haben also keine Verbindung mit einem Terminal.
Beim Start kann man eine sogenannte Daemonisierung verwenden. Man startet von dem interaktiv gestarteten Prozess einen Child-Prozess. Dieser hat noch Verbindung zum ersten und damit zum Terminal. Nun startet man von diesem den eigentlichen Daemon-Prozess und beendet den Child-Prozess. Nun ist die Verbindung zum Parent-Prozess gekappt und der Daemon-Prozess wird damit zum Child-Prozess des Init-Prozesses. Wenn sich der Daemon beendet, wird durch init regelmäßig wait() aufgerufen und damit verhindert, dass dieser als Zombie-Prozess noch lange im System unterwegs ist. Nun muss der Daemon-Prozess aber informiert werden, wann der Child-Prozess beendet ist.
Dies kann wie folgt erfolgen:

/* (C) IT Sky Consulting GmbH 2014
 * http://www.it-sky-consulting.com/
 * Author: Karl Brodowsky
 * Date: 2014-02-27
 * License: GPL v2 (See https://de.wikipedia.org/wiki/GNU_General_Public_License )
 */

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>

int daemonized = 0;

void signal_handler(int signo) {
  daemonized = 1;
}

int main(int argc, char *argv[]) {
  int fork_result;
  int ret_code;
  int status;
  int pid = getpid();

  /* set pgid to pid */
  setpgid(pid, pid);
  signal(SIGUSR1, signal_handler);

  /* first fork() to create child */
  fork_result = fork();
  if (fork_result == 0) {
    printf("In child\n");

    /* second fork to create grand child */
    fork_result = fork();
    if (fork_result != 0) {
      /* exit child, make grand child a daemon */
      printf("exiting child\n");
      exit(0);
    }
    
    /* in daemon (grand child) */
    pid = getpid();
    while (! daemonized) {
      pause();
    }

    printf("daemon has pid=%d pgid=%d ppid=%d\n", pid, getpgid(pid), getppid());

    /* do daemon stuff */
    sleep(30);
    printf("done with daemon\n");
    exit(0);
  }
  printf("parent waiting for child\n");
  wait(&status);
  printf("child terminated\n");
  kill(-getpid(), SIGUSR1);
  printf("parent done\n");
}

Durch das Setzen einer Prozessgruppen-ID ist es möglich, den Parent-Prozess, den Child-Prozess und den Daemonprozess auch über diese gemeinsame Gruppen-Id anzusprechen, ohne die eigentliche pid zu kennen. Negative Werte als Funktionsparameter für Funktionenen, die dort eine Prozess-Id (pid) erwarten, werden oft als pgid (Prozessgruppen-ID) interpretiert. Wenn der Child-Prozess beendet ist, wird das wait im Parent-Prozess beendet und dieser schickt ein Signal an den Daemon-Prozess, das von diesem ignoriert wird, aber zur Beendigung des Wait-Prozesses führt.

Für ein kleines Beispiel mag es noch akzeptabel sein, nach stdout zu schreiben, aber die Ausgaben eines Daemons sollten natürlich am Ende in einer Log-Datei landen. Das kann durch Ausgabeumleitung oder noch schöner durch Verwendung von syslog geschehen, ist aber ein Thema für sich.

Share Button

Systemprogrammierung

So etwas muss man ja heute selten machen, Applikationsentwicklung ist eher die Tätigkeit, mit der man sich herumschlägt, wenn man kundenspezifische „Business-Software“ entwickelt oder anpasst. Bei der Systemprogrammierung schreibt man Software, die direkt auf Betriebssystemfunktionen und Hardware zugreift oder Teile von Betriebssystemen im weiteren Sinn, also nicht unbedingt nur Teile des Linux-Kernels oder Kernelmodule, sondern auch so etwas wie ls, mv oder auch Datenbanken und Webserver.

Aber gelegentlich kommt es doch in Projekten vor, dass man solche Kenntnisse einsetzen muss und dann ist es auch gut, sie zu haben. Für ein Projekt in der Vergangenheit, als es darum ging, Serversoftware für Billingsysteme zu entwickeln, die in C für Solaris geschrieben wurde, mit anderen Komponenten kommunizieren sollte und dann auch noch Performance bringen sollte, die ausreicht, um wenigstens jeweils in 24 Stunden mindestens die Daten zu verarbeiten, die durch die Telefonate innerhalb von 24 Stunden angesammelt wurden, was sich noch alle paar Monate verdoppelte.

Auch um bei Fahrkartenautomaten einer großen Bahngesellschaft die betriebssystemnahe Funktionalität zum Betrieb und zur Wartung von Fahrkartenautomaten zur Verfügung zu stellen, war es erforderlich, sich in diesem Bereich bewegen zu können.

Zur Zeit halte ich eine Vorlesung an einer Fachhochschule (ZHAW) in Zürich über Systemprogrammierung. Deshalb wird hier vielleicht auch gelegentlich einmal der eine oder andere Artikel zu Themen aus dem Gebiet auftauchen.

Die Beispielprogramme, die ich zu dem Thema erstelle, sind als Open-Source-Software in github und unter den Bedingungen der GPL v2 für jeden Interessenten verfügbar.

Share Button