Jul 14 2016

Zeit-Trennzeichen bei DateTime

Autor: . Abgelegt unter C# .Net

Diese Woche hat es das .NET geschafft mich zu überraschen, ein Programm ist beim Kunden mit italienischen Windows immer wieder abgestürzt. Nach langem Suchen hat ein Kollege das Problem erkannt, was durch das folgende Beispiel veranschaulicht wird:

var ci = new CultureInfo("it-IT");
var dateTime = DateTime.Now;
var str = dateTime.ToString("dd.mm.yyyy hh:mm:ss", ci);
Console.WriteLine(str);

Ausgabe (.NET 3.5): 14.07.2016 20.45.30
Ausgabe (.NET 4.0): 14.07.2016 20:45:30

Weiterlesen »

Kommentare deaktiviert für Zeit-Trennzeichen bei DateTime

Jun 13 2016

Performance Vortrag aus 2015

Autor: . Abgelegt unter C# .Net

Letztes Jahr habe ich einen Vortrag zum Thema Performance gehalten, der Vermitteln sollte warum dieses Thema jeden (.NET-)Entwickler betrifft.

Nachdem ich mir jetzt die Zeit genommen habe um auch die Sprechernotizen aka „Tonspur“ auf zuschreiben, konnte ich den Vortrag online stellen:

https://github.com/koepalex/performance_talk_2015

Vielleicht ist der Vortrag für jemanden Hilfreich 🙂

Kommentare deaktiviert für Performance Vortrag aus 2015

Feb 24 2016

Ubuntu und ZFS, Problem nach Update

Autor: . Abgelegt unter ubuntu,zfs

ZFS ist ein tolles, modernes Dateisystem, welches sich wachsender Beliebtheit erfreut. Leider ist es noch nicht direkt im Ubuntu enthalten, sondern wird als APT-Paket (ubuntu-zfs) nachinstalliert. Eine Anleitung dafür findet sich auf ubuntuusers.de.

Nach einem Update hat oft zfs nicht mehr funktioniert. Es kam zur Fehlermeldung, dass zfs nicht gefunden werden konnte und man bitte mittels

modprobe zfs

Das entsprechende Kernelmodule laden soll. Der Befehl schlug dann auch immer Fehl mit einer Fehlermeldung wie „zfs.ko not found“.

Weiterlesen »

Kommentare deaktiviert für Ubuntu und ZFS, Problem nach Update

Jan 06 2014

AutoHotkey

Autor: . Abgelegt unter Allgemein

Durch Scott Hanselman’s Blog Artikel 2014 Ultimate Developer and Power Users Tool List for Windows. bin ich auf AutoHotkey aufmerksam geworden. Es biete wie AppleScript unter Mac OS X, die Möglichkeit verschiedene (lästige) Aufgaben zu automatisieren bzw. Programme um Funktionalitäten zu erweitern.


Damit könnte man zum Beispiel einen beliebiges Programm mit einer Eingabemaske um Snippets erweitern. Als Besonderheit können AutoHotkey-Skript auch in eine ausführbare Datei kompiliert werden und somit auf Rechnern ohne AutoHotkey verwendet werden.

Als praktisches Beispiel für ein AutoHotkey-Skript finde ich die Hotkeys für die Markdown-Syntax sinnvoll. Es gibt ein paar Punkte welche beim Markdown schreiben für Frust sorgen:

  • Leerzeilen vor und nach Aufzählungen
  • Quelltext Formatierung

Um Probleme in Zukunft zu vermeiden, habe ich das Github Projekt AutoHotkeyskript für Markdown erstellt. Das dort verfügbare Skript bietet aktuell folgende einfache Hotkeys:

  • alt + i (Beginnt einen Kursiv dargestellten Text)
  • alt + b (Beginnt einen Fett dargestellten Text)
  • alt + c (Beginnt Bereich zur Quelltexteingabe)
  • alt + q (Beginnt ein Zitat)
  • alt + . (Start einer nicht sortierten Liste)
  • alt + , (Start einer sortierten Liste)
  • alt + t (Füge 4 Leerzeichen ein)
  • alt + – (Füge Horizontale Line ein)

Zusätzlich gibt es noch einige verbesserte Hotkeys, welche eine Oberfläche benutzen zur besseren Benutzersteuerung:

  • alt + l (Starte Dialog zum ein Link hinzuzufügen)
  • alt + p (Starte Dialog um ein Bild hinzuzufügen)
  • alt + # (Starte den „Code Beautifier“)

Der Code Beautifier sucht alle <code></code>Sektionen im generierten HTML-Dokument und führt einige Ersetzungen durch:

  1. Ersetze alle Tabs durch 4 Leerzeichen
  2. Ersetze alle Leerzeichen durch &nbsp;
  3. Ersetze alle Zeilenende durch <br />

Viel Spaß beim Einsatz von AutoHotkey.

Kommentare deaktiviert für AutoHotkey

Nov 27 2013

Debugging von Performance Problemen in .NET

Autor: . Abgelegt unter C# .Net

Die Analyse von Speicherproblemen ist eine Aufgabe die bei großen .NET Anwendungen häufiger vorkommt. In C++ wurde gesucht, wer welche Speicherblöcke angefordert und nicht wieder freigegeben hat und im .NET Umfeld wird eben gesucht warum der Gabarge-Collector den Speicher nicht freigeben kann. Oder man sucht warum einige Benutzeraktionen besonders lange benötigen.
Für die Analyse gibt es eine ganze Reihe guter kommerzieller Programme, auf diese möchte ich jedoch nicht eingehen, sondern ein paar kostenlosen Alternativen vorstellen.

PerfView

Dieses Programm von Microsoft ist klein, schnell und leistungsfähig. Zum einen bietet es die Möglichkeit mittels ETW (Event Tracing for Windows) Problemen in der Laufzeit (CPU, I/O, GC) auf die Spur zu kommen. Zum anderen kann es Speicherabbilder von Programmen erzeugen und diese Vergleichen! Ausführliche Informationen gibt es bei Channel9 oder auf Vance Morrison’s Webblog.

WinDbg mit „Son Of Strikes“ Erweiterung

Microsoft liefert die sos.dll mit dem .NET Framework aus. Sie ermöglicht den WinDbg dazu managed Code zu debuggen. Für die den Themenkomplex WinDbg und SOS, gibt es viele gute Artikel/Tutorials von welchen ich zwei empfehlen möchte:

  1. Der Blog von Tess Ferrandez beispielsweise mit den Artikeln „.NET Finalizer Memory Leak“ und „New commands in SOS for 4.0“.
  2. Ein Blogeintrag von Rex Tang in welchem er weitere lesenswerte Artikel zu diesem Thema auflistet.

Auf der MSDN Webseite befindet sich die Befehlsreferenz.

WinDbg mit „SOS EXtension“ Erweiterung

Die von Steve Johnson geschriebene sosex.dll bietet viele nützliche Erweiterungen die dass managed Code debugging im WinDbg deutlich vereinfachen. Beispielsweise kann die SOSEX mittels Befehl !bhi einen HeapIndex erstellen lassen. Dieser Index enthält alle benötigten Informationen über die Objekte auf dem Heap. Um anschließend z.B. alle GC-Roots eines Objektes zu finden (!mroot <adr> -all) muss die SOSEX nicht das gesamte Speicherabbild analysieren sondern nur den HeapIndex. Dieses Vorgehen funktioniert deutlich schneller als die Alternative der SOS (!gcroot -all <adr>), welche jedes mal das Speicherabbild durchsucht.

Eine andere Eigenschaft ist das die SOSEX .prefer_dml 1 verwendet d.h. dass Links in die Ausgabe des WinDbg eingebettet werden. Dadurch wird einem häufig das kopieren bzw. abtippen von teilen der Ausgabe erspart.

Weitere Funktionalitäten der SOSEX sind

  • Anzeige von managed Locks und native CriticalSections (!mlocks)
  • Anzeige von wartenden Thread inkl. dem worauf sie warten (!mwaits)
  • Anzeige von Objekten in der Finalizer Queue (!finq)
  • Anzeige von Objekten in der F-Reachable Queue (!frq)

Lesen sie hier die Beschreibung des Unterschiedes zwischen Finalizer Queue und F-Reachable Quere.

Tip: Herr Johnson aktualisiert SOSEX regelmäßig, auch wenn er dazu keine Notiz auf seinen Blog hinterlässt. Deshalb lohnt es sich immer mal wieder das Zip-Archiv herunter zuladen und zu prüfen ob die enthaltene SOSEX neuer ist als die eigene.

Kommentare deaktiviert für Debugging von Performance Problemen in .NET

Okt 21 2013

Neues vom GC in .NET 4.0 und 4.5

Autor: . Abgelegt unter C# .Net

Heute möchte ich etwas über Erneuerungen im .NET 4.0 / 4.5 sprechen. Das genannte bezieht sich auf den Workstation Garbage-Collector. Die Informationen für den Server Garbage-Collector entnehmen sie bitte den Links. Beginnen wir jedoch zunächst mit einer kurzen Auffrischung.

Wiederholung

Das .NET Framework unterteilt seinen Heap in verschiedene Generationen.

  • In der Gen0 werden fast alle Objekte erstellt. Die Anfangsgröße beträgt rund 256KB.
  • In der Gen1 werden Objekte gespeichert die eine Garbage-Collection überlebt haben. Die Anfangsgröße beträgt rund 2 MB.
  • In der Gen2 werden Objekte gespeichert die mehr als eine Garbage-Collection überlebt haben Die Anfangsgröße beträgt 10 MB.

Die soeben genannten Anfangsgrößen können zur Laufzeit variiert werden. Am stärksten wird sich i.A. die Größe der Gen2 verändern (wachsen), da die Gen2 als Endlager für alle länger benötigten Objekte dient.

Die Gen2 teilt sich logisch in zwei Bereiche auf:

  • SOH der Small Object Heap dieser enthält alle Objekte die durch mindestens zwei Garbage-Collections hierher verschoben wurden.
  • LOH der Large Object Heap welcher alle Objekte mit einer Größe größer gleich 85 KB enthält.

Eine Garbage-Collection läuft in zwei Schritten ab. Die erste Phase Mark markiert alle Objekte die nicht mehr benötigt werden. Im zweiten Schritt Sweep wird der Speicher der markierten Objekte freigegeben. Um zu entscheiden, ob ein Objekt noch benötigt wird, sucht der GC Wurzeln Roots. Wurzeln können u.a. statische Felder, Methodenparameter, lokale Variablen oder CPU Register sein. Hat ein Objekt (oder eine Gruppe von Objekten) keinen Verweis auf eine Wurzel ist es aus dem Programmcode nicht mehr zugreifbar und kann aus dem Speicher entfernt werden. Sollte ein Objekt eine Garbage-Collection überleben und noch nicht in Gen2 sein, wird es um eine Generation verschoben.

Zu einer Fragmentierung der 0. und 1. Generation kann es bei diesem Entwurf nicht kommen. Denn ein Objekt wird entweder verschoben (von 0 nach 1 bzw. von 1 nach 2) oder gelöscht.

Der SOH (der Gen2) kann über die Zeit fragmentieren, deswegen wird in der Sweep-Phase der Heap zusammen gepackt (komprimiert/defragmentiert). Für eine solche Garbage-Collection wird der Main-Thread vom Garbage-Collector-Thread unterbrochen.
Der LOH kann über die Zeit auch fragmentieren hier wird jedoch keine Defragmentierung durchgeführt. Das kann zu Speicherknappheit (OutOfMemory-Abstürzen) in .NET Programmen führen, obwohl insgesamt genügend freier Speicher vorhanden ist.

.NET 4.0

Die Gen0 und Gen1 arbeitet der Garbage-Collector unverändert ab. Die Gen2-Collection läuft jetzt jedoch meist im Hintergrund. Diese nicht blockierende Garbage-Collection hat die Einschränkung, dass der SOH dabei nicht defragmentiert wird. Für eine Defragmentierung ist eine blockierende Garbage-Collection notwendig. Die Zeit in welcher die Garbage-Collection ein Programm blockiert wurde dadurch drastisch reduziert. Mehr zur Hintergrund Garbage-Collection


Eine weitere Optimierung stellt der LowLatency Modus dar, dieser unterbindet jegliche Gen2-Collection komplett. Nur eine low memory Notifikation des Betriebssystems oder ein expliziter Aufruf von GC.Collect mit 2 als Parameter haben eine Gen2-Collection zur Folge. Das aktivieren des LowLatency-Modus ist nur für besonders Zeitkritische Aufgaben zu empfehlen und sollte deshalb nur vorübergehend genutzt werden. Aktiviert wurde der neue Modus mittels:

GCSettings.LatencyMode = GCLatencyMode.LowLatency;

Weiterführende Dokumentation zum LowLatency Mode

.NET 4.5

Der Latency-Modus wurde um die Option SustainedLowLatency erweitert, welche im Gegensatz zum LowLatency zusätzliche Hintergrund Gen2-Collections durchführt. Der neue Modus ist dazu geeignet das Programm über einen längeren Zeitraum im LowLatency laufen zulassen. Der SustainedLowLatency Modus wurde übrigens von Microsoft auf für .NET 4.0 mit dem Update 3 nachgeliefert.


Das Garbage-Collector-Team hat auch etwas für den LOH getan unter anderem das finden von freien Speicherblöcken bei einer Allokation verbessert:

In preparation for LOH allocation requests, the GC builds up a list of free (available) memory blocks after a collection. As part of an allocation, the GC consults this these free memory blocks one by one to determine if any block in the list will satisfy the allocation. If a given memory block is a candidate, it is used for the allocation. In earlier releases of the .NET Framework, once a memory block was rejected as a candidate for an allocation, it was removed as a candidate for subsequent allocations.


Den größten Nutzen werden große Programme wohl davon haben, dass es jetzt möglich ist den LOH zu Komprimieren! Dazu ist nur folgender Quelltext notwendig:

GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
//force Gen0 to Gen2 blocking GC
GC.Collect();


Für weitere Verbesserungen des wie Allokation von Objekten über 2 GB siehe The .NET Framework 4.5 includes new garbage collector enhancements for client and server apps.

Kommentare deaktiviert für Neues vom GC in .NET 4.0 und 4.5

Okt 20 2013

GetHashCode dein Freund und Sorgenkind

Autor: . Abgelegt unter C# .Net

Eine wichtige Methode beim Arbeiten im .NET Umfeld ist GetHashCode. Sie gibt einen 32 Bit Integer zurück der das Objekt identifizieren soll. Der Hash-Code beschreibt also die Identität des Objektes (im Gegensatz zur Speicherreferenz auf das Objekt).

Daraus leitet sich die Frage ab Wann zwei Objekte die selbe Identität besitzen? Im Falle einer Object-Relational-Mapper Klasse beispielsweise, wenn die Instanzen der O/R-Mapper Klasse auf ein und die selbe Zeile(n) der selben Tabelle(n) der selben Datenbank(en) verweisen. Klassischer weise durch eine Kette von Primary-Keys. Der Hash-Code ist auch ein Sorgenkind eines Entwicklers da schon hier die Probleme beginnen. Eine einfach Implementierungen könnte sein:

return (tableKey.GetHashCode()+ key1.GetHashCode() + keyN.GetHashCode());

Diese ist jedoch Falsch denn die Addition von unterschiedlichen Summanden kann die gleichen Summe ergeben. Eine verbesserte Implementierung ist:

return string.Concat(tableKey, key1, keyN).GetHashCode();

Jedoch beinhaltet auch diese Implementierung einen unscheinbaren Fehler.

  • key1: „ab“ keyN: „c“ ergibt „abc“.GetHashCode()
  • key1: „a“ keyN „bc“ ergibt auch „abc“.GetHashCode()

Eine weiter verbesserte Implementierung ist:

return string.Format("{0} : {1} : {2}", tableKey, key1, keyN)
.GetHashCode();

Um jedoch einen noch besseren Hash-Code zu berechnen müsste man die Hash-Code der einzelnen Felder generieren und diese mittels einer Hash-Funktion verarbeiten. Beispiele für Hash-Funktionen sind:

  1. Jenkins
  2. Murmur
  3. CityHash
  4. Goulburn

Empfehlenswert ist diese letzte Variante nur für Programme die mit großen Datenmengen arbeiten müssen oder mit besonders langen Laufzeiten haben (z.B. Server-Anwendungen).


Die Symbole tableKey, key1 und keyN im oberen Beispiel sollten am besten als readonly gekennzeichnet werden und zwar aufgrund der Microsoft GetHashCode Guidelines

„the integer returned by GetHashCode should never change“

Ein veränderlicher Hash-Code ist unter anderem Problematisch wenn:

  1. Ein Objekt in einer Datenstruktur (wie Hashset) enthalten ist.
  2. Das Objekt so modifiziert wird, dass sich sein Hash-Code verändert.
    • Nun ist es eher vom Zufall abhängig ob sich das Objekt noch im richtigen bucket der Datenstruktur befindet. Wenn nicht ist das Objekt in der Datenstruktur verwaist.
    • Existieren andere Instanzen die auf die gleiche Identität zeigen sollten beinhaltet man die Software ab diesem Zeitpunkt einen schwer zu lokalisierenden Fehler.

In der Realität ist es nicht immer möglich den erzeugten Hash-Code stabil zu halten, meist weil man Ressourcen sparend programmieren muss (z.B. Objekte wieder verwenden da teure Ressourcen benutzen). Deswegen hat sich der Microsoft Mitarbeiter Eric Lippert solcher Probleme angenommen und in seinem Blog den sehr lesenswerten Eintrag Guidelines and rules for GetHashCode verfasst. Darin heißt es unter anderem:

„Rule: the integer returned by GetHashCode should never change while the object is contained in a data structure that depends on the hash code remaining stable“


Eine andere Problematik ergibt sich aus der Tatsache, dass der Hash-Code nur 32 Bit groß ist. Der rund 4,2 Milliarden Zahlen fassende Zahlenraum kann nur effektiv ausgenutzt werden, wenn die verwendete Hash-Funktion die Hash-Codes möglichst gleichmäßig über den Zahlenraum verteilt.

Selbst mit einer Optimalen Hash-Funktion gilt jedoch, dass je mehr Hash-Codes ermittelt werden desto höher ist die Wahrscheinlichkeit einer Hash-Kollision. Eine Kollision entsteht wenn zwei unterschiedliche Identitäten existieren, für welche derselbe Hash-Code generiert wird. Eric Lippert hat ermittelt, dass bei nur 9300 generierten Hashes die Chance einer Kollision 1 % beträgt. Für 77000 Objekte beträgt die Chance bereits 50% vgl. Socks, birthdays and hash collisions. Daher ist es eine schlechte Idee, alle Objekte in einer Datenstruktur zu verwalten und somit den Hash-Code als globalen Identifizier zu verwenden.


Die dritte Problematik welche ich ansprechen möchte ist Performance. Falsch Implementierte oder schlecht ausgewählte Hash-Funktionen können die Laufzeit eines Programms deutlich verschlechtern. Die Performance-Verschlechterungen treten u.a. auf wenn eine Hash-Funktion:

  • zu lange braucht um den Hash-Code zu berechnen (Kryptographische Hash-Funktionen eigenen sich i.d.R. nicht für GetHashCode)
  • nicht den gesamten Zahlenraum nutzen, dann dauert das einfügen von Elementen in eine Datenstruktur sehr lange (häufige Kollisionen)

Der letzte Punkt ist eine bekannte Falle bei der Verwendung von structs. Bei einem Struct sollte die GetHashCode-Methode immer überschrieben werden, da die Standardimplementierung nur einen kleinen Teil des Zahlenraums ausnutzt siehe C# FAQ: Why are hashtable lookups so slow with struct keys.

Kommentare deaktiviert für GetHashCode dein Freund und Sorgenkind

Okt 14 2013

Muss es immer die 100% Lösung sein?

Autor: . Abgelegt unter C# .Net

Ich behaupte einfach mal, dass man sich in der Softwareentwicklung schnell in zu aufwendigen Lösungen verliert. Zumindest mir geht es regelmäßig so, ich habe einiges an Software entwickelt und Artikel geschrieben die nie veröffentlicht wurden (obwohl sie dafür gedacht waren). Der Grund für die nicht Veröffentlichung war, dass sie meiner Meinung nach nicht komplett waren oder meinen eigenen Ansprüchen nicht genügten.

Das soll nicht bedeutet das die Artikel zu schreiben oder die Software zu entwickeln Zeitverschwendung war. Ganz im Gegenteil es ist immer beeindruckend wie viel man lernen kann, sobald man sich mit etwas anderen beschäftigt als den Arbeitsthemen. Die dabei gewonnenen Erkenntnisse sind, vielleicht auch für andere Interessant (ist schon ab und zu vorgekommen 😉). Daher resultiert für mich die Frage Muss es immer die 100% Lösung sein? wobei die simple Antwort Nein ist. Um Unklarheiten vorzubeugen damit ist nicht gemeint, dass man nur 80% der Anforderungen erfüllen soll. Vielmehr geht es um Erkenntnisaustausch/-gewinn.

Denn wenn ich heute etwas Veröffentliche und jemand anderes kann Aufgrund meiner Erkenntnis ein Problem lösen, dann ist das hervorragend. Der Erkenntnisaustausch ist nicht davon Abhängig ob die Quelltext-Beispiele praxisnah sind oder ob sich der Text besonders gut liest. Solange die Veröffentlichung neue Information beinhaltet, ist sie es Wert auch veröffentlicht zu werden.


Beispiel: Ein Blogartikel darf (meiner Auffassung nach) auch unvollkommene Informationen enthalten, denn der Beitrag enthält i.d.R. trotzdem einen Mehrwert. Dieser Mehrwert kann (und soll) den Lesern Nutzen bringen. Der Nutzen kann jedoch nur erreicht werden, wenn der Blogartikel auch veröffentlicht wird.


Als Analogie für die Softwareentwicklung sollten Projekte bereits in einem sehr frühen Stadium als Open Source veröffentlicht werden. Weil es fraglich ist ob man jemals alleine so Zeit in das Projekt investieren kann, dass es den eignen Standards für eine Veröffentlichung genügt. Hingegen ist das Projekt erstmal Veröffentlicht kann es (selbst in diesem unvollständigen Zustand) Dritten (weiter-)helfen.

Kommentare deaktiviert für Muss es immer die 100% Lösung sein?

Okt 13 2013

Pipe aus eigenen Programmen nutzen

Autor: . Abgelegt unter C# .Net

Mittels der von Unix Systemen bekannten Pipe (|) ist es auch unter Windows (via CMD-Line) möglich einzelne Kommandos zu verbinden. Die Pipe ermöglicht es die Ausgabe vom dem vorherigen Kommando direkt als Input des aktuellen Kommandos zu verwenden. Beispielsweise den Inhalt einer Datei einem Skript zu übergeben:

type input.md | perl Markdown.pl > output.html

Logisch gesehen Ersetzt der Inhalt der Pipe ein einzelnes Konsolen-Argument. Um diese Funktionalität auch in den eigenen (Konsolen-) Programme verwenden zu können, sind im Allgemeinen nur zwei Erweiterungen notwendig.

1

Das Lesen der Eingabedaten aus der Standardeingabe:

string pipedData = Console.In.ReadLine ();
if (string.IsNullOrEmpty (pipedData)) {
Console.WriteLine ("no piped argument");
} else {
dataModel.Input = pipedData;
}

2

Eine Veränderung in der Logik welche die Konsolen-Argumente interpretiert. Die Logik muss Wissen dass eines der Argumente (wahrscheinlich das Hauptargument) nun optional ist. Da der Wert des Argumentes aus der Pipe gelesen wird.

Tipp: es empfiehlt sich die Verwendung einer Open-Source Bibliothek wie der Command Line Parser Library. Bei dieser muss um ein Argument als Optional zu Kennzeichnen, der Attributparameter Required auf false gesetzt werden.

Kommentare deaktiviert für Pipe aus eigenen Programmen nutzen

Aug 24 2013

Eindruck und Gedanken zu Mac OS X

Autor: . Abgelegt unter MacOS

Durch den Wunsch ein neues Notebook zu kaufen, stand ich vor der Wahl Linux, Windows oder Mac OS X. Da ich auf Arbeit Windows einsetze und früher in meiner Freizeit viel mit Linux (Ubuntu, Debian,  Linux Mint, Fedora) bzw. BSD (FreeBSD) gearbeitet habe ist die Wahl des Betriebssystems auf Mac OS X gefallen. Und die Wahl der Hardware auf ein Mac Book Pro 13,3″ gefallen. Ich gebe zu das es mich immer interessiert hat zu Erfahren, warum Niemanden den ich kenne, sein Mac OS X Gerät missen möchte. In diesem Beitrag werde ich etwas über meinen Ersten Eindruck erzählen.

Weiterlesen »

Kommentare deaktiviert für Eindruck und Gedanken zu Mac OS X

Ältere Einträge »