Beobachtete Daten
Dieser Lauf löste keinen Zeitunterschied zwischen den beiden Plain-Pfaden auf.
Eine Operation erzeugt eine vollständige EXTF-v13-Datei aus Verwaltungssatz, 125-spaltiger Überschrift und 99.999 Buchungszeilen. In den Zeit- und Allokationsspalten ist weniger besser. Die ±-Werte sind die Halbbreiten der JMH-99,9-%-Konfidenzintervalle über zehn Messstichproben unter der Normalverteilungsannahme von JMH.
| Exportpfad | Zeit / Datei | Abgeleitete Zeit / Zeile | Abgeleitete Zeilen / Sekunde | Allokierte Bytes / Datei | Abgeleitete allokierte Bytes / Zeile |
|---|---|---|---|---|---|
Plain Forward-onlyDatevStreamWriter |
670,193 ± 34,910 ms | 6,702 µs | 149.209 | 980.975.803 ± 10.710.036 B | 9.809,86 B |
Plain RetainedDatevFile |
670,000 ± 15,068 ms | 6,700 µs | 149.252 | 1.011.067.254 ± 10.710.046 B | 10.110,77 B |
Advanced Retainedadvanced.DatevFile |
742,976 ± 51,878 ms | 7,430 µs | 134.593 | 966.056.224 ± 10.710.041 B | 9.660,66 B |
Die Mittelwerte der beiden Plain-Pfade unterscheiden sich nur um 0,03 % und damit weit weniger als ihre ausgewiesene Unsicherheit; dieser Lauf trägt daher keine zeitliche Rangfolge. Forward-only allokierte in dieser Arbeitslast rund 3,0 % weniger kumulative Bytes als Plain Retained. Advanced Retained zeigte den niedrigsten Allokationswert und den höchsten beobachteten Zeitmittelwert; dieser einzelne Lauf begründet jedoch keine allgemeingültige Rangfolge.
Produktentscheidung
Zuerst den Lebenszyklus wählen, nicht die kleinste Benchmark-Zahl.
Forward-only für einmalige Exporte mit hohem Volumen
DatevStreamWriter validiert, serialisiert und übergibt jede vollständige Zeile, ohne sie in einer bibliothekseigenen Sammlung zu speichern. Der lebende, zurückgehaltene Zeilenzustand ist damit proportional zu einer Zeile statt zur Gesamtzahl. Diese Entwurfseigenschaft – keine Geschwindigkeitsbehauptung – ist der stärkste Grund für Forward-only bei einem einmaligen Export mit 99.999 Zeilen.
Retained verwenden, wenn Zeilen verfügbar bleiben müssen
Die speichernden APIs erlauben Prüfung, Iteration und verzögerte Ausgabe. Dieser Lebenszyklus ist sinnvoll, wenn der Aufrufer Zeilen erneut prüfen oder ausgeben muss; dieser Lauf löste gegenüber Forward-only keinen Zeitunterschied auf. Die ausgerichteten Zeilen bleiben zwangsläufig bis zur Freigabe des Dateiobjekts im Speicher.
Allokation ist nicht gleich belegter Heap
JMH gc.alloc.rate.norm erfasst alle während einer Operation allokierten Bytes, einschließlich kurzlebiger Validierungs- und Serialisierungsobjekte. Es misst nicht den maximal lebenden Heap. Das Advanced-Retained-Ergebnis zeigt den Unterschied: Es allokierte hier kumulativ am wenigsten, obwohl es ausgerichtete Zeilen speichert. Zur Quantifizierung des Spitzenverbrauchs wäre eine separate Live-Set- oder Heap-Occupancy-Untersuchung erforderlich.
Gemessene Arbeitslast
Eine dünn belegte, aber nicht triviale feste Zeile.
- Format: vollständiger DATEV-Buchungsstapel / EXTF v13, Windows-1252 und CRLF.
- Größe: 125 Spalten; 99.999 Buchungszeilen als unterstütztes Dateimaximum.
- Belegung: sechs nicht leere Felder je Zeile: Betrag, Soll/Haben-Kennzeichen, Konto, Gegenkonto, Belegdatum und Buchungstext.
- Maskierung: Der Buchungstext lautet
Müller; Beleg "42" €und beansprucht Windows-1252-, Trennzeichen- und Anführungszeichenbehandlung. - Validierung: metadatenbezogene strenge semantische Validierung bei jedem Append. Plain verwendet
DatevValidator, Advanced den eingebauten ModusSTRICT. - Ausgabe: Alle Pfade werden bytegenau verglichen und erzeugen exakt 33.602.459 Bytes: 2.795 feste Verwaltungs-/Überschriftsbytes plus 336 Bytes je Buchungszeile.
Für jede Zeile wird dieselbe unveränderliche Map mit sechs Einträgen übergeben. Zeilenausrichtung, Validierung und Serialisierung liegen im Messintervall; die vorgelagerte Datensatzerzeugung und fachliche Kontierungslogik nicht.
Messvertrag
Umgebung und JMH-Konfiguration.
| Gemessene Bibliothek | v0.2.0 · Commit 06fa7ae |
|---|---|
| Messdatum | 12. August 2026 |
| Rechner | Apple M1 Pro, 10 logische CPUs, 32 GiB RAM, aarch64 |
| Betriebssystem | macOS 26.5.2 (Build 25F84) |
| JVM | Eclipse Temurin 17.0.19+10; -Xms1g -Xmx1g -XX:+AlwaysPreTouch |
| Messgerüst | JMH 1.37, Average-Time-Modus, ein Thread, zwei Forks |
| Iterationen | Je Fork: drei × 1 s Warmup, danach fünf × 1 s Messung |
| Profiler | JMH gc; normalisierte Allokation in Bytes je Operation |
| Ziel | Ein vorab dimensionierter, wiederverwendeter ByteArrayOutputStream, außerhalb jeder Messoperation zurückgesetzt |
| I/O | Byte-Serialisierung und Schreiben ins Ziel enthalten; Dateisystem, Netzwerk und Zielallokation ausgeschlossen |
Jeder gemessene Aufruf erstellt seinen Exporter und führt alle 99.999 Appends sowie die abschließende Ausgabe aus. Unveränderliche Testdaten, Zielkapazität und der erste Bytegleichheitsvergleich werden vor der Messung vorbereitet. Die letzte Ausgabe wird nach dem Lauf erneut geprüft.
Selbst ausführen
Die Maximalzeilen-Task fixiert die wichtigen Eingaben.
git clone https://github.com/mrtyldr/datev-exporter.git
cd datev-exporter
git checkout cb91f5dd8b174bb4e10d98a8a7cc93f007483042
./gradlew --no-daemon :datev-exporter-benchmarks:jmhMaxRows
Die Task wählt die Adoptium-Java-17-Toolchain und verwendet diese JMH-Einstellungen:
-p rowCount=99999
-wi 3 -w 1s
-i 5 -r 1s
-f 2 -t 1
-jvmArgs "-Xms1g -Xmx1g -XX:+AlwaysPreTouch"
-prof gc -rf json
Das erzeugte JSON liegt unter datev-exporter-benchmarks/build/results/jmh/max-rows-gc.json. Die veröffentlichte Zusammenfassung bewahrt jede rohe Zeit- und Allokationsstichprobe:
Nicht verallgemeinern
Was dieser Lauf nicht belegt.
- Es ist ein Lauf auf einem Entwickler-Laptop ohne dedizierte Host-Isolation, CPU-Pinning oder thermische Kontrolle.
- Die zehn Stichproben schätzen den eingeschwungenen Zustand dieser Arbeitslast; sie sind keine Produktions-Latenzperzentile.
- Zeilen pro Sekunde ist aus der durchschnittlichen Dateizeit berechnet und kein mehrthreadiger Durchsatztest.
- Die dünn belegte feste Zeile bildet nicht jede Felddichte, Wertlänge, Validierungsverletzung oder vorgelagerte Objektallokation ab.
- Das In-Memory-Ziel schließt Dateisystem, Netzwerk, Verschlüsselung, Kompression und aufruferseitige Pufferung aus.
- Der Profiler misst kumulative Allokation, nicht maximalen RSS, maximal lebenden Heap oder Retained Size.
- Ein Univocity-Pfad ist nicht enthalten: Der Adapter hat eine andere Ausgabegrenze und kann die hier gemessene vollständige Datei aus Verwaltungssatz und Buchungen nicht erzeugen.
Diese Zahlen dienen zum Verständnis der Implementierung und als reproduzierbare Vergleichsbasis auf der eigenen Zielhardware – nicht als SLA. Die Kompatibilitätsnachweise und Grenzen sind separat zu bewerten; Geschwindigkeit belegt keine DATEV-Importannahme.