Die Erstellung von PDFs mit umfangreichen Tabellen stellt Entwickler immer wieder vor große Herausforderungen, insbesondere wenn die Anzahl der Zellen stark ansteigt. Die Standardmethoden zur Tabellendarstellung in PDF-Dokumenten sind häufig recht langsam, was speziell bei großen Tabellen zu erheblichen Wartezeiten führt. Als ich vor kurzem mit dem iText-Framework an einer Lösung arbeitete, gelang es mir in nur einem Nachmittag, die Rendering-Zeit für Tabellen um beeindruckende 95 % zu reduzieren. Dabei profitiere ich heute von einem tieferen Verständnis der Funktionsweise von Tabellen in PDFs und gezielten Optimierungen innerhalb der iText-Rendering-Engine. Dieser Erfahrungsbericht erklärt den Ablauf, die Ursachen der Performanceprobleme und die vorgenommenen Verbesserungen im Detail.
PDF-Tabellen sind technisch betrachtet keine eingebaute Funktion im PDF-Format. Das PDF-Spezifikationsdokument stellt lediglich grundlegende Zeichenbefehle zur Verfügung: Koordinaten setzen, Linien zeichnen, Text an Positionen platzieren. Mit diesen Bausteinen wird durch das Layouting eine optisch ansprechende Tabelle generiert. Das bedeutet jedoch auch, dass die Gestaltung komplexer Tabellen mit vielen Zellen eine aufwendige Koordination erfordert, bei der viele Einzeloperationen durchgeführt werden müssen. iText bietet eine Abstraktionsschicht, die diesen Prozess vereinfacht.
Entwickler können Tabellen mit wenigen Zeilen Code erzeugen und iText kümmert sich um die Umwandlung in die PDF-Befehle. Während dieses Komforts zeigen sich jedoch Engpässe, sobald die Menge der Zellen steigt und komplexere Funktionen wie Border-Collapse (zusammengefallene Rahmenlinien) genutzt werden. Um herauszufinden, wie die Performance skaliert, baute ich zunächst ein Benchmark, das Tabellen mit 100, 1.000, 10.000 und letztlich 50.
000 Zellen erzeugte und dabei die Zeit maß. Die Ergebnisse waren erwartungsgemäß nicht linear: Das Erzeugen von 50.000 Zellen nahm über 35 Sekunden in Anspruch, fast quadratisch im Vergleich zu kleineren Tabellen. Diese Werte waren ein guter Ausgangspunkt, um mit Profiling den Hauptverursacher des langen Renderings zu finden. Die Analyse zeigte, dass der Großteil der Zeit in zwei Kernmethoden der iText-Rendering-Engine verbracht wurde, beide zuständig für die Berechnung der zusammengefallenen Tabellenrahmen, also „CollapsedTableBorders“.
Das Framework prüft hier für jede Zelle, welcher Rahmen als oberste Linie erscheinen soll – was bei aufgeklappten Rändern nicht notwendig ist. Nutzt man die Einstellung, dass Tabellengrenzen „separat“ behandelt werden sollen, statt zusammenzufallen, verkürzt sich die Renderingzeit signifikant. Trotzdem fiel auf, dass genau diese Border-Collapse-Funktion eine Leistungsschwäche darstellt, die man beheben sollte. Ausgehend von der Methode für vertikale Rahmen stellte ich fest, dass die Liste der berechneten Rahmen für äußere Tabellenränder bei jeder Zeile immer wieder neu erzeugt wurde, obwohl sie sich nicht veränderte. Ein einfacher Cache-Mechanismus, der die Ergebnisliste einmal erzeugt und anschließend wiederverwendet, optimierte den Prozess enorm.
Nach der Implementierung dieser Änderung reduzierte sich die Generierungszeit für 50.000 Zellen von 35 Sekunden auf knapp über einer Sekunde – ein unglaublicher Performancegewinn bei minimalem Aufwand. Neben der Optimierung der Rahmenkalkulation galt es auch, das Thema Barrierefreiheit zu berücksichtigen. Getaggte PDFs enthalten zusätzliche Strukturinformationen, welche von Screenreadern genutzt werden können, um Tabelleninhalte semantisch korrekt zu interpretieren. Aktiviert man in iText das Tagging, erhöht sich der Mehraufwand allerdings drastisch.
Die Performanceeinbußen bei großen Tabellen waren enorm – teilweise um den Faktor 100 zu der ungetaggten Variante. Dieses Problem ließ mich tiefer in den Code eintauchen. Die Ursache war ein aufwändiges Sicherheits-Check-Verfahren beim sogenannten „Flushing“ – dem Prozess, bei dem bereits fertiggestellte Seiten aus dem Arbeitsspeicher auf die Festplatte geschrieben werden, um Speicherverbrauch gering zu halten. In diesem Check wurde geprüft, ob man noch Änderungen an bereits endgültig verarbeiteten Strukturelementen vornehmen darf. Die ursprüngliche Implementierung dazu war allerdings ineffizient, weil sie für jede einzelne Tag-Element-Liste aufwendig ganze PDF-Objektstrukturen parste.
Durch die Umstellung auf eine reine Low-Level-Prüfung des Speicherstatus der PDF-Objekte, ohne komplexes Parsen, war dieser Schritt viermal schneller. Neben dieser zentralen Änderung folgten weitere Kleinoptimerungen: Das Caching von sich nicht ändernden Schlüsseln oder Werten, das Vermeiden von doppelten Funktionsaufrufen und die Bündelung von Befehlssätzen reduzierte unnötigen Overhead und verbesserte weitere Flaschenhälse. Sehr interessant war die Verbesserung des Algorithmus, der für das Einfügen neuer Tags in die Tagging-Hierarchie zuständig war. Ursprünglich suchte das System beim Einfügen von Tag-Elementen stets von vorn durch die gesamte Liste von Geschwistern, obwohl klar war, dass neue Elemente immer am Ende hinzugefügt werden. Eine kleine Änderung, die den Suchalgorithmus umdrehte und von hinten nach vorne durch die Liste die richtige Einfügestelle suchte, führte zu enormen Zeitersparnissen – hin zu einer fast 40-fachen Beschleunigung beim Tagging großer Tabellen.
Dieses Beispiel zeigt eindrucksvoll, wie wichtig Profiling-Tools sind, um ineffiziente Codepfade zu identifizieren und anschließend mit gezielten Maßnahmen zu optimieren. Dabei reichen oft kleine Änderungen mit intelligenter Cacherung aus, um enorme Effekte zu erzielen. Für Entwickler, die mit Tabellen in PDFs arbeiten, sind diese Erkenntnisse ein wichtiger Hinweis, um die eigene Software schneller und ressourcenschonender zu gestalten. Die Verbesserungen wurden im Frühjahr 2023 in die offizielle iText Core Version 9.1.