Unterthema: Ausstattung der C-Compiler
Unterthema: Paralleles C
Unterthema: Benchmarkergebnisse
Unterthema: Ergebnisse auf einen Blick
An sich sollte man meinen, daß C-Compiler sich doch weitgehend ähneln müssen, gerade wenn sie beide auf einem PC mit mindestens einer Transputer-Karte (kompatibel zu einer TEK 4/8 oder einem B004-Board von Inmos) laufen. Doch (leider) gibt es viele Unterschiede, was Portierungen nicht eben einfach macht, insbesondere bei der Implementierung von `Parallelität´ kocht jeder seine eigene Suppe.
Die Dokumentation ist sinnvoll gegliedert, gut zu lesen und läßt selten Fragen offen - wenn man des Englischen kundig ist. Fragen waren in der Regel schnell durch einen Blick in das passende Handbuch zu klären. Gute Dokumentation - das ist eine persönliche Auffassung - zeichnet sich auch dadurch aus, daß man schnell in ihr das findet, was man sucht. Differenzen zwischen Software und Beschreibung waren nicht festzustellen; das ist ja selbst bei Produkten dieser Preislage nicht immer der Fall.
Auch die Installation machte keine Probleme. Nach 16 Minuten des Diskettenwechselns, der Beantwortung einiger Fragen, sowie einigen Eintragungen in der autoexec.bat und config.sys, konnte mit einem Probelauf begonnen werden. Das erste `Hallo Welt´ lief sofort. Leider waren einige Fragen der automatischen Installation erst nach gründlichem Lesen des `Delivery Manuals´ zu beantworten. Man sollte den Benutzer lieber bei der Installation weniger fragen und mehr installieren. Wenn etwas fehlt, ist dies für den Anfänger nur schwer zu beheben.
Als sehr nützlich hat sich das `Handbook´ herausgestellt. Da in ihm alle Optionen und Funktionen beschrieben sind, hat es in den meisten Fällen zur Sättigung des Informationshungers ausgereicht. Eine Idee, die auch andere Hersteller aufgreifen sollten.
Leider ließ das Werk die Klarheit und Ausführlichkeit des Inmos-Handbuchs vermissen. Einige beschriebene Optionen kannte der Compiler nicht (man mußte die Änderungen einem Readme-File entnehmen). Nicht selten war mir nur noch in Erinnerung, etwas gelesen zu haben; zu finden war es dann erst nach längerem Suchen.
Auch die Beschreibung der Installation war nicht vollständig. Ein Hinweis fehlte, was alles in die autoexec.bat einzutragen ist. Dementsprechend gelang die komplette Installation nicht auf Anhieb. Einem kundigen PC-Benutzer sollte sie jedoch keine Schwierigkeiten machen. Nach sechs Minuten war der Compiler in der Lage, sein `Confidence testing´ erfolgreich zu durchlaufen.
Sehr ausführlich und gut gelungen sind die Beschreibungen zu den Erweiterungen von C, die Parsec für die Implementation der Parallelität in C eingefügt hat. (Die Firma nennt die so erweiterte Sprache `Parallel C´, und ihr `Par C´ die erste Implementation dieser Sprache). Die in diesem Kapitel angewandte Sorgfalt sollte Parsec/ACE auf alle Kapitel legen. Da beide Compiler keine Online-Hilfen bieten, sind die Handbücher die einzige Informationsquelle bei Fragen, Fehlern und Unklarheiten.
Wie die vielen Disketten schon vermuten lassen, sollte man etwas größeres als einen XT mit Floppy besitzen, um die Compiler benutzen zu können. Das Inmos-C benötigt gute sieben Megabyte Plattenplatz, Par-C begnügt sich mit zwei Megabyte. Beiden gemeinsam ist, daß ein schneller Rechner nicht schaden kann. Er braucht nicht viel Speicher zu haben (ein MByte reicht), aber flott sollte er sein. Der Test-Rechner war ein 386er mit 25 MHz Taktfrequenz; damit läßt sich arbeiten.
Der Compiler allein macht es nicht. Heutzutage benötigt man für eine zügige Entwicklung neben dem Übersetzer noch diverse Hilfeprogramme, die die Arbeit erleichtern, Fehler verhindern und finden helfen. Besonders wichtig sind hierbei ein Debugger und ein Make. Wer schon einiges entwickelt hat, weiß, daß er diese beiden Tools neben dem Compiler am häufigsten braucht. Weiter hilfreich sind ein Editor, der auf den Compiler abgestimmt ist, ein separater oder integrierter Assembler, ein Librarian (Library-Verwaltungsprogramm), ein Profiler, Cross-Referenz-Tools, Object-Analyse-Programme, ein Programm zur Volltextsuche (grep zum Beispiel) und eventuell eine Oberfläche (wie bei Borlands Turbo-Familie).
Wie kann man einen Compiler dieser Preisklasse so `nackt´ verkaufen? Damit lassen sich zwar Programme erzeugen, aber kaum entwickeln, wenn man unter diesem Begriff zumindest noch das Debuggen faßt. Da es sich bei den produzierten Programmen um solche für Transputer handelt, kann man auch nicht auf andere (PD-)Tools zurückgreifen; gar nicht zu denken an bestimmt auftretende Probleme in einem Transputer-Netz.
Der Source-Level-Debugger sucht seinesgleichen. Er hat diverse Funktionen, die auch die Suche nach Fehlern im Netz stark vereinfachen. So kann er beispielsweise Breakpoints verteilt über viele Prozessoren setzen, und stets die Source zu einem erreichten Breakpoint präsentieren. Hat man nur einen Transputer, so muß man allerdings mit dem Postmortem-Modus vorlieb nehmen, da der Debugger immer einen Transputer für sich beansprucht. Hierzu existiert ein Programm namens `idump´, das auf Wunsch auch das komplette Netz in ein Postmortem-File verwandelt, das anschließend entwanzt werden kann.
Obgleich auf dem PC-Sektor Entwicklungsoberflächen nicht selten sind (und Inmos mit dem TDS keinen schlechten Wurf getan hatte), lassen sich alle - sowohl die Inmos- als auch die Parsec-Tools - nur über den COMMAND.COM `bedienen´. Bedienungskomfort erhält man erst nach dem Schreiben diverser Makefiles und Batch-Dateien.
Auch in der Art, wie ein Programm auf mehrere Transputer aufgeteilt wird, unterscheiden sich die beiden Kandidaten. Inmos wählt den konventionellen (statischen) Weg, eine Konfigurationsdatei zu erstellen, die ein Transputer-Netz und die darauf zu verteilenden Prozesse beschreiben. Dafür hat man extra eine einfache, C-ähnliche Sprache entwickelt, um trotz der statischen Beschreibung möglichst flexibel zu bleiben.
Nach dem Linken ist noch ein Konfigurations- und Sammeldurchlauf vonnöten, wobei für jeden Transputer angegeben werden muß, welche Prozesse auf ihm laufen, welche Verbindungen nach außen wie heißen, und so weiter. Nach dem Konfigurieren sind alle gebundenen und konfigurierten Dateien zu einem bootfähigen Programm zusammenzuholen: das erledigt das mitgelieferte Programm `icollect´. Das Konfigurieren kann entfallen, wenn man nur einen Transputer hat.
Diese Methode ist weitaus flexibler als die von Inmos, da man vor dem Start des Programms schnell noch ein paar Karten hinzustecken kann, um einer Problemlösung Beine zu machen. Sie hat aber auch einen Nachteil: auf jedem Transputer muß eine Kopie des gesamten Programms vorhanden sein. Wird das Programm zu groß, muß man alle Transputer mit mehr RAM versorgen.
Die Tabellenwerte zeigen, daß beide Compiler recht lange brauchen (das Inmos-C fällt beim Linken kleiner Dateien besonders unangenehm auf). Der Flaschenhals - so könnte man vermuten - sei hierbei das 8-Bit-Link-Interface zum Transputer. Doch bis auf den Compiler und den Debugger des Inmos-C habe ich in diesem Test nur die PC-gestützten Tools verwendet, die ausschließlich auf dem PC laufen. Zwar liefert Inmos alle Programme auch in einer für Transputer ausführbaren Form aus, diese sind jedoch noch viel langsamer. So gelang es nicht, mit dem Linker für den Transputer ein leeres Programm unter 36 Sekunden zu linken (einem Inmos-Mitarbeiter gelang dies nach eigener Aussage sehr wohl, doch das ist eine andere Geschichte).
Entgegen einiger Andeutungen im Handbuch laufen alle Programme des Par-C ohne Transputer. Beim Linken merkt man dies besonders. Der Linker arbeitet recht flott. Er hat auch keine 1,2 MByte große Library zweimal zu beackern wie der Inmos-Linker, denn alle Libraries des Par-C-Compilers machen zusammen nicht einmal 100 KByte Code aus. Hier kann man sehen, was ein paar Optimierungen bringen können.
Die Programmgrößen gehen in Ordnung, denn der Transputer wird als CPU ohne Betriebsystem genutzt. In jedem Programm muß das komplette Server-Protokoll zwischen Transputer und Host-PC eingebaut sein. Wenn da noch ein `printf()´ und ein paar kleinere Routinen zusammenkommen, sind 20 KByte nicht zuviel. Ein `Hello World´ auf einer Sparc `wiegt´ dagegen 105 KByte. Wer will sich da noch beklagen?
Setzt man jedoch die 680 Zeilen der Whetstone-Source in Beziehung zu den 25 Sekunden, die es vom Source zum ausführbaren Programm braucht, so läßt das erahnen, wie viel Zeit man an einem PC mit Warten verbringen muß, wenn man an größeren Projekten arbeitet. Auf meinem 68030-System benötigt ein Compiler-Durchlauf inklusive Linken 7 Sekunden, Turbo-C läuft da noch einiges schneller. Wo bleibt die Zeit?
Die Ausführungszeiten sind leider nicht so, wie die langen Arbeitszeiten der Compiler vermuten lassen. Insbesondere die Dhrystone-Werte machen stutzig: Für eine 32-Bit-CPU bei 25 MHz sollten schon 6000 bis 7000 Steinchen drin sein. Auch die kWhetstones sind nicht überragend. In einem Werbeblatt zum Par-C-Compiler waren bessere Zeiten zu finden, doch trotz des Verlegens von Code und Data ins interne RAM ließ sich der Wert nicht über 1000 kWhetstones bringen.
Auch Inmos vermeldete weit bessere `Stone-`Ergebnisse. Nun haben Hersteller sicher die Möglichkeit und auch die Neigung, sich optimale Konfigurationen hinzustellen und zu nutzen, so daß auch Benchmarks die besten Ergebnisse liefern.
Oft stößt man im Handbuch zum Par-C auf den Begriff ANSI-C. Doch leider sind nur der Preprocessor und die Libraries (fast) ANSI-C-kompatibel. Der Compiler ist es nicht. Er unterstützt keine Prototypes und erlaubt auch nicht die Angabe der Parameter-Typen direkt hinter dem Funktionsnamen vor den Parameter-Bezeichnern. In der Library sind aber keineswegs alle ANSI-C-Funktionen zu finden. Wie so viele Hersteller hat sich Par-C die `locale´-Funktionen gespart; setjmp.h mit den Deklarationen zweier Rücksprungfunktionen fehlt ebenfalls.
Aus diesem Grund mußten einige Test-Programme an Par-C angepaßt werden. ANSI-C war und ist ein wichtiger Schritt in die richtige Richtung und sollte von keinem C-Compiler-Hersteller mehr mißachtet werden. Es ist nicht klar, warum Parsec dies noch nicht berücksichtigt hat.
Das Inmos-C dagegen glänzt durch absolute Kompatibilität und unterstützt darüber hinaus auch 16-Bit-Transputer. Dies macht das Inmos-C besonders für Maschinensteuerungen interessant, da in einer genormten Hochsprache programmiert werden kann.
Die Idee ist faszinierend, aber die Implementierung noch nicht. Par-C mangelt es an manchem: an umgebenden Tools, an noch besserer Dokumentation, an ANSI-C-Konformität, an Fehlerfreiheit. Auch so Kleinigkeiten wie das Fehlen einer Meldung, wenn das Transputer-Errorflag gesetzt ist - das Transputer-Programm steht und es passiert nichts -, zeigen, daß an diesem Produkt noch gearbeitet werden muß.
Inmos-C dagegen ist bieder, aber gut. Es ist nicht schnell, bietet auch nicht alles, was man gerne hätte - aber das, was man bekommt, ist grundsolide, funktionell und bestens beschrieben; etwas mit dem man arbeiten und auf das man sich verlassen kann. Man sollte sich in England dazu durchringen, noch ein Make mitzuliefern und dem Linker ein wenig Beine zu machen. Sonst bleibt wenig an dem Produkt auszusetzen.
Daß Inmos das andere Konzept bei der Implementation von Parallelität gewählt hat, ist auf den zweiten Blick auch nicht so bedeutsam. Denn Parallelität auf einem einzigen Transputer macht wenig Sinn, und bei mehreren ist auch beim Par-C-Konzept die Eleganz dahin. Dann nämlich reicht das `par´-Statement allein auch nicht mehr aus und man muß mit geeigneten Programmkonstruktionen (if, for ...) entscheiden, auf welchem Prozessor gerade welcher Code laufen soll. Und damit bleibt letztendlich doch alles am Programmierer hängen, egal welches C er nun benutzt - auch wenn es ihm im einen Fall leichter gemacht wird. (bb)
[2] David Göhler, C-Zaren, Lattice 5.0 und Aztec 5.0 für den Amiga im Vergleich, c't 5/90, S. 92
Par-C 1.31 | Inmos-C 1.42f | ||
Compiler | |||
Kommando-Zeile | + | + | |
integrierte Oberfläche | - | - | |
ruft Linker | + | - | |
Inline-Assembler | + | + | |
T2-Support | - | + | |
FPU-Direkt-Support | + | + | |
Assembler-Output | + | + | |
ANSI-kompatibel | - | + | |
Editor | - | - | |
separater Assembler | + | - | |
Cross-Referenz-Lister | + | + | |
Librarian | - | + | |
Assembler-Debugger | - | ++ | |
Source-Level-Debugger | - | ++ | |
Make | - | - 2) | |
Objekt/Library-Analyse | - | + | |
weitere Analyse-Programme | - | + | |
Profiler | + 1) | - | |
Hilfsprogramme wie grep,diff... | - | - | |
Installations-Programm | + | + | |
Dokumentation | +- | ++ | |
in Deutsch | - | - | |
Bibliotheken ANSI-kompatibel | - | + |
- nicht vorhanden,
+ vorhanden
++ vorhanden und sehr gut
+- vorhanden, aber mit Mängeln
1) im Source zum Übersetzen
2) es existiert jedoch ein Makefile-Generator
Inmos ging den ersten Weg, Parsec mit Par-C den zweiten. Der Erstgenannte hat den Vorteil, den Pfad der Tugend - des reinen C - nicht zu verlassen, dafür aber den Nachteil, so manche Verrenkung beim Programmieren in Kauf nehmen zu müssen. Was in Occam eine einfache Anweisung war, führt in C jeweils zu einem Funktionsaufruf. Dies ist häufig nicht nur schlechter zu lesen, es zwingt den Programmierer oft, selbst Ressourcen zu allozieren, zu testen, ob er sie erhalten hat, und zuletzt wieder freizugeben. In Occam war das nie ein Thema.
Par-C dagegen ist die Implementierung von `Parallel C´ - ein Standard-C mit drei Erweiterungen: der `par´-Anweisung, dem Datentyp `channel´ und der `select´-Anweisung. Sie entsprechen weitgehend den Occam-Äquivalenten.
Möchte man bei einer Anweisung keine Funktion aufrufen, sondern mehrere Anweisungen nacheinander ausführen lassen - jedoch parallel zu allen anderen innerhalb des par-Blocks -, so ist, wie in C üblich, ein Block mit geschweiften Klammern zu benutzen. Ein Beispiel:
par { lieswas(ein, zwei); tuwas(); { i = 5; j = i + 5; } schreibwas(drei); }In der komplizierten Form hat das par die Syntax der for-Anweisung. Dieses Beispiel erzeugt acht parallele Prozesse, die alle die Funktion `BerechneSpalte´ aufrufen:
par (i=0; i <8; i++) { { BerechneSpalte(array,i); } }Jeder Prozeß bekommt aber sein eigenes `i´. Die Kompatibilität zum for geht sogar soweit, daß ein
par (;;)erlaubt ist, was aber stets zu einem Runtime-Error führt - wer hat schon unendlich viel Speicherplatz?
channel reinraus;Nun kann man die Variable `reinraus´ wie bei jedem anderen elementaren Datentyp (wie int, long, char ...) auch in Zuweisungen und Funktionsaufrufen benutzen. Steht `reinraus´ links in einer Zuweisung, wird der Wert der Zuweisung in den Kanal geschrieben, steht sie rechts, wird sie aus dem Kanal gelesen. Es lassen sich auch Felder aus Kanälen definieren, oder Kanäle in Strukturen unterbringen. Es geht alles, was mit einem elementaren Datentyp zu machen ist.
Eine mögliche Kette von parallellaufenden Prozessen, die verzahnt Daten weiterreichen, sieht so aus:
channel feld[10]; int i;
par { berechne_irgendwas(0, 0L, feld[0]); par (i = 1; i <= 9; i++) { berechne_irgendwas(i, feld[i-1], feld[i]); } berechne_irgendwas(9, feld[9], 0L); }
Wie in Occam ist es möglich, mehrere Kanäle gleichzeitig abzuhorchen. Kommt etwas an irgendeinem Kanal an, verzweigt select in die passende Routine, die die ankommenden Daten entgegennimmt. Anders als in Occam wird hierbei jedoch das Byte, welches den Kanal aktiviert und die select-Anweisung dazu bringt, in eine bestimmte Funktion zu verzeigen, nicht von der select-Anweisung geschluckt. Es muß von der aufgerufenen Funktion gelesen werden. Dies ist aber in den meisten Fällen von Vorteil, da ein verschicktes Byte meist auch eine auszuwertende Bedeutung hat.
Auf ein Beispiel müssen wir hier verzichten, da die Syntax und die Funktion von select zu viele Erklärungen erfordern würde.
Name | Source | Comp-Zeit | Object | Link-Zeit1) | Code |
Leer | 21 | 4 | 274 | 14 | 20393 |
Hello | 71 | 5 | 345 | 14 | 24517 |
Ackermann | 820 | 7 | 745 | 16 | 29877 |
Quicksort | 1028 | 7 | 1217 | 15 | 30564 |
Dhrystone | 17717 | 262) | 7794 | 20 | 41235 |
Whetstone | 15626 | 15 | 4422 | 14 | 33342 |
Laufzeiten | |
Ackermann (3,9) | 40,12 |
QuickSort (50000) | 8,80 |
kWhetstones | 1218 |
Dhrystones 2.1 | 3225 |
1) Zeiten für `ilink´ und `icollect´
2) Compiler zweimal gestartet (für dhry_1 und dhry_2)
Parsec Par-C
Name | Source | Comp-Zeit | Object | Link-Zeit1) | Code |
Leer | 17 | 3 | 64 | 3 | 1161 |
Hello | 71 | 4 | 104 | 3 | 14723 |
Ackermann | 709 | 5 | 612 | 4 | 20968 |
Quicksort | 1144 | 6 | 878 | 4 | 21632 |
Dhrystone | 17717 | 21 | 6358 | 4 | 26186 |
Whetstone | 15626 | 20 | 11876 | 4 | 25097 |
Laufzeiten | |
Ackermann (3,9) | 107,0 |
(83,5) 1) | |
QuickSort (50000) | 15,1 |
(12,1) 1) | |
kWhetstones | 906 |
Dhrystones 2.1 | 3030 |
1) mit Code und Data im internen RAMGrößenangaben in Byte, Zeiten in Sekunden; Dhry- und Whetstones in Durchläufen (höhere Werte = besseres Ergebnis)Host-Rechner: 25 MHz AT mit 80386, DOS 3.3, VGA 16 Bit, 4 MByte RAM
Transputer: TEK4/8 mit T800, 25 MHz, 1 MB RAM (120 ns)
[ + ] volle ANSI-C-Implementierung
[ + ] sehr gute Dokumentation
[ + ] unterstützt T2-Transputer
[ + ] zuverlässig
[ + ] viele Tools inklusive Debugger
[ + ] Server auch als Source
[ - ] langsam
[ - ] Make fehlt
[ - ] Bedienungskomfort
Parsec/ACE Par-C
[ + ] gutes Sprachkonzept
[ + ] flotter Linker
[ + ] geringer Platzbedarf (kleine Libraries)
[ + ] separater Assembler
[ + ] Server auch als Source + Library
[ - ] diverse Entwicklungstools inklusive Debugger fehlen
[ - ] nicht ANSI-C-kompatibel
[ - ] Bedienungskomfort
[ - ] keine T2-Unterstützung
[ - ] Dokumentation etwas unübersichtlich