Archiv

Archiv für Februar, 2009

Das ROOT Framework aus softwarearchitektonischer Sicht

6. Februar 2009 admin Keine Kommentare

Das Fachgebiet der experimentellen Teilchenphysik beschäftigt sich mit der Suche und dem Nachweis neuer Teilchen und der Vermessung ihrer Eigenschaften. In großen Beschleunigern werden stabile Teilchen mit hohen Energien zur Kollision gebracht. Aufgrund der Äquivalenz von Energie und Masse können dabei neue Partikel erzeugt werden, deren Spuren und Energiedepositionen mit geeigneten Detektoren gemessen werden. Aus diesen Messungen lassen sich Parameter wie Impuls, Ladung, Masse etc. ableiten. Die dabei aufgezeichneten Daten müssen von Physikern verknüpft, analysiert und visualisiert werden.

Das Softwareframework ROOT, das dabei zum Einsatz kommt, ist ein Musterbeispiel für misslungene Softwarearchitektur. Die Behauptung, es handle sich um ein System, das objektorientierte Paradigmen konsequent umsetzt, ist nicht haltbar. Betrachten wir als Beispiel die Vererbungshierarchie der Histogrammklassen.

th2_inh

Die erste Frage, die sich stellt, ist, warum das zweidimensionale Histogramm TH2 vom eindimensionalen TH1 erbt. Dies wäre durchaus verständlich, wenn TH2 lediglich gemeinsame Funktionalität von TH1 übernehmen würde. In Wirklichkeit besitzt jedoch schon TH1 eine virtuelle Funktion GetYAxis() und sogar ein GetZAxis(), da in der weiteren Hierarchie auch TH3 von TH2 und damit von TH1 erbt.

Viel interessanter als dieser (schon recht verwunderliche) Umstand ist jedoch die Tatsache, dass alle Histogrammklassen direkt oder indirekt von den “Attributklassen” TAttLine, TAttFill und TAttMarker erben. Diese beschreiben Eigenschaften von Linien, Flächen oder Symbolen wie Strichstärke, Farbe, Muster o.Ä. Hier wird die eigentlich einer Vererbung zugrunde liegende is-a-Relation ad absurdum geführt. Ist ein Histogramm eine Linie? Ist es eine Füllfläche? Oder ist es vielleicht ein Marker? Nein, es besitzt vielmehr einen Satz von Linien-, Flächen- und Markereigenschaften. Die korrekte Umsetzung dieser Relation würde darin bestehen, den Histogrammklassen Membervariablen vom Typ TAttLine, TAttFill und TAttMarker zu geben. Um Schreibarbeit bei der späteren Verwendung der Objekte zu sparen, wurde jedoch kurzerhand die Vererbung gewählt. Was die Konsequenzen davon sind, lässt sich leicht an dem folgenden Vererbungsdiagramm aus der ROOT Dokumentation erkennen:

tattline_inh

Man beachte insbesondere die Anmerkung “…and more”. Dieser Vererbungswahnsinn ist nicht nur unpraktisch, da er das Einbinden der entsprechenden Header in jeder der erbenden Klassendefinitionen erfordert (und im Übrigen auch die Verwendung von Pimpl unmöglich macht), sondern sogar gefährlich, da er die starke Typisierung von C++ durch übertriebenen Polymorphismus aufweicht. Dies ist jedoch nur ein willkürlich herausgegriffenes Beispiel. Im gesamten Framework wird konsequent vererbt, wo Vererbung nicht notwendig und teilweise sogar unerwünscht ist.

Ein weiterer sträflich vernachlässigter Punkt ist die Trennung von Daten und Darstellung. Warum muss TH1 überhaupt Informationen über Strichstärke und Füllfarbe speichern, wenn seine eigentliche Aufgabe die Speicherung der Histogrammdaten ist? Diese beiden disjunkten Aufgabenbereiche in einer Klasse zu kombinieren mag vielleicht den Vorteil haben, nur mit einem Objekt interagieren zu müssen. Schon bei der Serialisierung stellt sich aber die Frage, ob man wirklich zu jedem Histogramm vorgegebene Darstellungsattribute abspeichern will. Sinnvoller wären Painter Objekte, die die Darstellungsinformationen enthalten und beliebige Histogrammdaten zeichnen können. Auch diese Objekte könnte man für eine spätere Wiederverwendung serialisieren und nötigenfalls Histogramme mit zugehörigen Paintern verknüpfen.

Es gibt jedoch auch positive Aspekte des ROOT Frameworks, die hervorgehoben werden sollten. Für die Serialisierung von Objekten bietet ROOT einen relativ komplexen Mechanismus, der auf der Generierung von Klassen-Metadaten mit Hilfe eines speziellen Codeprozessors basiert. Die generierten Klassen beschreiben Membervariablen und Methoden der zu serialisierenden Klassen und erlauben so eine automatische Persistierung bzw. Erstellung von Objekten aus persistierten Daten. Gleichzeitig erlaubt dieser Mechanismus auch den Zugriff auf die Klasseninformationen, um Benutzerinteraktion über einen speziellen C++ Interpreter zu ermöglichen. Alle Klassen, für die Metadaten generiert wurden, können so im Interpreter instanziiert und verwendet werden.

KategorienProgrammierung Tags:

Liliypond: TeX für Noten

5. Februar 2009 admin Keine Kommentare

Das Satzsystem TeX / LaTeX erfreut sich insbesondere im akademischen Umfeld großer Beliebtheit, da es eine Trennung von Inhalt und Formatierung erlaubt, wie sie mit Textverarbeitungsprogrammen nicht möglich ist. Seit geraumer Zeit existiert ein System, das vergleichbare Funktionalität für den Notensatz anbietet. Der Benutzer verfasst dazu eine Textdatei, die die zu setzenden Noten in Form einer Art Markup Language beschreibt. Lilypond erstellt daraus direkt den fertigen Satz.

Im direkten Vergleich mit den kommerziellen Notensatzprogrammen Finale und Sibelius muss sich Lilyponds Satz nicht verstecken. Im Gegenteil, das Ergebnis der kostenfreien Lösung wirkt eher wie von einem professionellen Notensetzer erstellt als das von Sibelius. Finale bildet hier das Schlusslicht. Seine Resultate wirken sehr künstlich und unnatürlich.

Den großen Funktionsumfang eines Finale kann Lilypond im Hinblick auf den Notensatz nicht erreichen (zumindest ist dieser nicht leicht zugänglich). Es liegt in etwa auf einer Höhe mit Sibelius. Spezielle Notationen wie feathered beams o.Ä. können zwar über Umwege bewerkstelligt werden, richtig integriert scheint die Funktionalität aber weder in Sibelius noch in Lilypond.

Der größte Nachteil von Lilypond ist die fehlende Möglichkeit zur akustischen Wiedergabe. Dies behindert den kompositorischen Prozess erheblich. Das mag weniger problematisch sein, wenn bereits fertiggestellte, handschriftliche Partituren gesetzt werden müssen, ist aber ein gravierender Nachteil für den explorativen Komponisten.

Während bei TeX das Fehlen einer WYSIWYG Ansicht als Vorteil geschätzt wird, ist es für den Notensetzer eher eine Behinderung. Eine Trennung von Inhalt und Formatierung ist in der Notenschrift nicht einfach, da sie in vielen Fällen unmittelbar zusammenhängen.

Lilypond ist durchaus in die engere Auswahl einzubeziehen, wenn eine günstige und qualitativ hochwertige Notensatzlösung benötigt wird. Aufgrund der fehlenden Flexibilität und der mangelnden Unterstützung explorativer oder feedbackbasierender Kompositions- und Satztechniken ist das Einsatzgebiet jedoch sehr eingeschränkt.

Als hilfreich hat sich allerdings die Integration von Lilypond in LaTeX erwiesen (Stichwort lilypond-book), die es ermöglicht, direkt Lilypond Codefragmente in LaTeX Dokumenten zu verwenden. Insbesondere für das Verfassen musikwissenschaftlicher und -theoretischer Dokumente oder Lehrmaterialien ist diese Kombination den bisherigen Vorgehensweisen (Satz in Sibelius oder Finale, Kopieren der Grafik, Einfügen im Textverarbeitungs- oder DTP-Programm) weit überlegen.

KategorienMusik Tags:

Funktionale Programmierung und Template Metaprogramming

5. Februar 2009 admin Keine Kommentare

Funktionale Programmiersprachen sind in den letzten Jahren immer beliebter geworden, denn viele Abläufe lassen sich funktional einfacher und in weniger Zeilen formulieren als in imperativen Sprachen (eine Kombination dieser Paradigmen bietet übrigens die Programmiersprache F#, die von Microsoft entwickelt wurde).

Betrachten wir zum Vergleich die Berechnung der Exponentialfunktion (für ganzzahlige Exponenten). Eine sehr naive Implementierung in C++ würde wie folgt aussehen:

long exp(long base, long exponent)
{
	long result = 1;
	for(long i=0; i<exponent; ++i)
	{
		result *= base;
	}
 
	return result;
}

Diese Formulierung mag zwar einfach sein, ist aber nicht besonders intuitiv. Die rekursive Definition erscheint eingängiger:

long exp(long base, long exponent)
{
	if(exponent == 1)
		return base;
	else
		return exp(base, exponent-1) * base;
}

Die Schleife wurde ersetzt durch einen rekursiven Aufruf der Funktion. Lediglich das verbliebene if verunreinigt die funktionale Definition. Betrachten wir das selbe Beispiel in der funktionalen Programmiersprache Erlang:

exp(B, 1) ->
	B;
exp(B, E) ->
	exp(B, E-1) * B.

Hier wird ebenfalls eine Funktion exp definiert, allerdings wird diese für den zweiten Parameter “überladen”. Die in C++ notwendige if Abfrage wird dadurch in die Funktionsdefinition absorbiert (streng genommen ist die Abfrage E == 1 immer noch vorhanden, sie wird aber implizit vom Erlang Interpreter vorgenommen).

Weitgehend unbekannt unter den meisten C++ Programmierern ist die Tatsache, dass sich mit Hilfe von Templates funktionale Paradigmen abbilden lassen, die statische Code-Generierung und/oder Berechnungen zur Compile-Zeit zulassen. Die obige rekursive Definition der Exponentialfunktion lässt sich beispielsweise folgendermaßen implementieren:

template<int B, int E>
struct Exp
{
	enum { value = Exp<B, E-1>::value * B };
};
 
template<int B>
struct Exp<B, 1>
{
	enum { value = B };
};

Durch Verwendung von Exp<2, 4>::value im Code berechnet der Compiler rekursiv den Wert der Exponentialfunktion. Wichtig anzumerken ist allerdings, dass dieser Wert lediglich als Konstante verwendet werden kann, denn aufgrund des statischen Charakters von Templates ist eine dynamische Berechnung so nicht möglich.

Man mag sich nun fragen, welchen Mehrwert Template Metaprogramming bietet, wenn keine dynamischen Berechnungen damit möglich sind. In unserem Beispiel mag das auf den ersten Blick nicht deutlich werden, aber betrachten wir dazu das folgende Codefragment:

#define THREETOTHEPOWEROFSIX	729
#define FIVETOTHEPOWEROFSEVENTEEN	762939453125
 
long stupid_function(int a)
{
	return a * pow(3, 6) * pow(5, 17);
}
 
long normal_function(int a)
{
	return a * THREETOTHEPOWEROFSIX * FIVETOTHEPOWEROFSEVENTEEN;
}
 
long nice_function(int a)
{
	return a * Exp<3, 6>::value * Exp<5, 17>::value;
}

Welche Funktion ist performanter und leichter verständlich? Weitere interessante Betrachtungen des Template Metaprogramming folgen in einem der nächsten Artikel.

KategorienProgrammierung Tags:

Akkumulierende Mean Berechnung in C++

5. Februar 2009 admin Keine Kommentare

Für die Berechnung einer Threadpool Idle Statistik muss jeder Worker-Thread den Zeitpunkt des Arbeitsbeginns und den Zeitpunkt des Arbeitsendes (= Beginn der Idle Phase) loggen. Die Differenz dieser Timestamps ergibt die Verarbeitungsdauer eines einzelnen Work-Items bzw. komplementär das jeweilige Idle Intervall. Für die Berechnung des Mittelwerts der Idle-Intervalle müssen die einzelnen Samples akkumuliert werden. In einer Server-Applikation kann eine permanente Akkumulation durch Aufsummierung problematisch werden, da je nach Größe der einzelnen Samplewerte und Laufzeit des Systems die Wertebereiche von 64 bit Variablen überschritten werden können. Dieses Problem lässt sich durch Mittelung über eine fixe Anzahl von Samplewerten umgehen.

Dazu wird zunächst eine fixed_size_queue Datenstruktur mit fester Maximalgröße eingeführt. Diese erbt sämtliche Funktionalität von std::queue und überschreibt lediglich die push() Funktion, um beim pushen in eine volle Queue ein altes Element zu droppen.

template<class T, class Sequence = std::deque<T> >
class fixed_size_queue : public std::queue<T, Sequence>
{
public:
	typedef T value_type;
	typedef typename Sequence::size_type size_type;
 
	typedef typename Sequence::const_iterator const_iterator;
 
	explicit fixed_size_queue(size_type maxSize)
		: maxSize_(maxSize)
	{}
 
	void push(const value_type& value)
	{
		if(this->size() == maxSize_)
			this->pop();
 
		std::queue<T, Sequence>::push(value);
	}
 
	const_iterator begin() const { return this->c.begin(); }
	const_iterator end() const { return this->c.end(); }
 
protected:
	size_type maxSize_;
};

Unter Verwendung dieser Klasse lässt sich nun ein Akkumulator definieren. Dieser wird im Konstruktor auf ein wählbares Sample-Integrationsintervall festgelegt. Nachdem Samples in die interne fixed_size_queue eingefügt wurden, kann über die Funktion value() der aktuelle Mittelwert abgerufen werden.

/**
 * @brief Sliding window sample accumulator for calculating the mean value
 *
 * Please note that SampleType can be any type or class that is convertible
 * to double.
 */
template
<
	typename SampleType,
	template <class> class MeanCalculator = Average
>
class MeanAccumulator
{
public:
	typedef SampleType sample_type;
	typedef typename MeanCalculator<sample_type>::mean_type mean_type;
 
	MeanCalculator<sample_type> mean_calc_;
 
public:
	explicit MeanAccumulator(
			typename fixed_size_queue<sample_type>::size_type maxSize)
		: samples_(maxSize)
	{}
 
	void operator()(sample_type sample)
	{
		samples_.push(sample);
	}
 
	const mean_type value() const { return mean_calc_(samples_); }
 
protected:
	fixed_size_queue<sample_type> samples_;
};

Um die Berechnung des Mittelwerts generisch zu halten, wurde eine Policy für dessen Berechnung eingeführt. Die Standard Policy wurde dabei auf Average festgelegt, das wie folgt definiert ist:

template<typename SampleType>
class Average
{
public:
	typedef SampleType sample_type;
	typedef double mean_type;
	typedef fixed_size_queue<sample_type> queue_type;
 
	const mean_type operator()(
			const queue_type& samples) const
	{
		mean_type sum = 0.0;
 
		typename queue_type::const_iterator it = samples.begin();
		for(; it != samples.end(); ++it)
		{
			// Divide each summand by size to reduce chances of overflow
			sum += static_cast<mean_type>(*it) / samples.size();
		}
 
		return sum;
	}
};

Bei der Berechnung des Mittelwerts wird jedes Sample auf den mean_type (double) konvertiert. Damit ist auch die Verwendung von Klassentypen als Sampletyp möglich, sofern diese einen operator double implementieren. Die Division durch die Anzahl der Samples erfolgt in jedem Summanden. Dies reduziert weiter die Wahrscheinlichkeit eines Overflows.

KategorienProgrammierung Tags: